<commit_before>// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  Drop Down Menu has a functionality similar to that of SelectField

  Clicking the DropDownMenu button displays a menu pane with a
  list of items. The selected item will be displayed on the button.
  User has the option of enabling checkbox for the selected menu item.

  @extends SC.ButtonView
  @version 1.0
  @author Mohammed Ashik
*/
sc_require('views/button');

SC.DropDownMenu = SC.ButtonView.extend(
/** @scope SC.DropDownMenu.prototype */ {

  /**
    An array of items that will be form the menu you want to show.

    @property
    @type {Array}
  */
  objects: [],

  /**
    Binding default for an array of objects

    @property
    @default SC.Binding.multiple()
  */
  objectsBindingDefault: SC.Binding.multiple(),

  /**
    If you set this to a non-null value, then the name shown for each
    menu item will be pulled from the object using the named property.
    if this is null, the collection objects themselves will be used.

    @property
    @type {String}
    @default: null
  */
  nameKey: null,

  /**
    If you set this to a non-null value, then the value of this key will
    be used to sort the objects.  If this is not set, then nameKey will
    be used.

    @property
    @type: {String}
    @default: null
  */
  sortKey: null,

  /**
     Set this to a non-null value to use a key from the passed set of objects
     as the value for the options popup.  If you don't set this, then the
     objects themselves will be used as the value.

     @property
     @type {String}
     @default null
  */
  valueKey: null,

  /**
    If true, the empty name will be localized.

    @property
    @type {Boolean}
    @default YES
  */
  localize: YES,

  /**
    if true, it means that no sorting will occur, objects will appear
    in the same order as in the array

    @property
    @type {Boolean}
    @default YES
  */
  disableSort: YES,

  /**

    @property
    @default ['drop-down-menu']
  */
  classNames: ['drop-down-menu'],

  /**
    Menu item list

    @property
    @type:{Array}
  */
  itemList: [],

  /**
    Current selected menu item

    @property
    @default null
  */
  currentSelItem: null,

  /**
    Property to set the index of the selected menu item. This in turn
    is used to calculate the preferMatrix.

    @property
    @type {Number}
    @default null
  */
  itemIdx: null,

  /**
     Current Value of the dropDown

     @property
     @default null
  */
  value: null ,

  /**
    if this property is set to 'YES', a checbox is shown next to the
    selected menu item.

    @private
    @default NO
  */
  checkboxEnabled: NO,

  /**
    Default value of the drop down.
     This will be the first item from the menu item list.

    @private
  */
  _defaultVal: null,

  /**
    Default value of the drop down.
     This will be the title corresponding to the _defaultVal.

    @private
  */
  _defaultTitle: null,

  /**
    Default value of the drop down.
     This will be the icon corresponding to the _defaultVal.

    @private
  */
  _defaultIcon: null,

  /**
    @private

    The button theme will be popup
  */
  theme: 'popup',

  /**
    Render method gets triggered when these properties change

    @property
    @type{SC.Array}
  */
  displayProperties: ['icon', 'value','controlSize'],

  /**
    Prefer matrix to position the drop down menu such that the
    selected item for the menu item will appear aligned to the
    the button. The value at the second index(0) changes based on the
    postion(index) of the menu item in the menu pane.

    @property
    @type {Array}
    @default [ 0, 0, 2]

  */
  preferMatrix: null,

  /**
    Width of the sprite image that gets applied due to the theme.
     This has to be accounted for while calculating the actual
     width of the button

    @property
    @type {Number}
    @default 32
  */
  DROP_DOWN_SPRITE_WIDTH: 32,

  /**
    Property to set the menu item height. This in turn is used for
    the calculation of prefMatrix.

    @property
    @type {Number}
    @default 20
  */
  CUSTOM_MENU_ITEM_HEIGHT: 20,

  /**
    Binds the button's selection state to the menu's visibility.

    @private
  */
  isSelectedBinding: '*menu.isVisibleInWindow',

  /**
    If this property is set to 'YES', the menu pane will be positioned
    below the anchor.

    @private
    @default NO
  */
  isDefaultPosition: NO,

  /**
    lastMenuWidth is the width of the last menu which was created from
    the objects of this drop down.

    @private
  */
  lastMenuWidth: null,
  /**
    Left Alignment based on the size of the button

    @private
  */
  leftAlign: function() {
    var val = 0 ;
    var controlSize = this.get('controlSize') ;
    if(controlSize === SC.SMALL_CONTROL_SIZE) val = -14 ;
    if(controlSize === SC.REGULAR_CONTROL_SIZE) val = -16 ;
    return val;
  }.property('controlSize'),

  /**
    override this method to implement your own sorting of the menu. By
    default, menu items are sorted using the value shown or the sortKey

    @param{SC.Array} objects the unsorted array of objects to display.
    @returns sorted array of objects
  */
  sortObjects: function(objects) {
    if(!this.get('disableSort')){
      var nameKey = this.get('sortKey') || this.get('nameKey') ;
      objects = objects.sort(function(a,b) {
        if (nameKey) {
          a = a.get ? a.get(nameKey) : a[nameKey] ;
          b = b.get ? b.get(nameKey) : b[nameKey] ;
        }
        return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
      }) ;
    }
    return objects ;
  },

  /**
    render method

    @private
  */
  render: function(context,firstTime) {

    var layoutWidth = this.layout.width ;
    if(firstTime && layoutWidth) {
      this.adjust({ width: layoutWidth - this.DROP_DOWN_SPRITE_WIDTH }) ;
    }

    var objects = this.get('objects') ;
    objects = this.sortObjects(objects) ;

    //Get the namekey, iconKey and valueKey set by the user
    var nameKey = this.get('nameKey') ;
    var iconKey = this.get('iconKey') ;
    var valueKey = this.get('valueKey') ;
    var checkboxEnabled = this.get('checkboxEnabled') ;

    //get the current selected value
    var currentSelectedVal = this.get('value') ;

    // get the localization flag.
    var shouldLocalize = this.get('localize') ;

    //itemList array to set the menu items
    var itemList = [] ;

    //to set the 'checkbox' property of menu items
    var isChecked = YES ;

    //index for finding the first item in the list
    var idx = 0 ;

    objects.forEach(function(object) {
    if (object) {

      //Get the name value. If value key is not specified convert obj
      //to string
      var name = nameKey ? (object.get ?
        object.get(nameKey) : object[nameKey]) : object.toString() ;

      // localize name if specified.
      name = shouldLocalize? name.loc() : name ;

      //Get the icon value
      var icon = iconKey ? (object.get ?
        object.get(iconKey) : object[iconKey]) : null ;
      if (SC.none(object[iconKey])) icon = null ;

      // get the value using the valueKey or the object if no valueKey.
      // then convert to a string or use _guid if one of available.
      var value = (valueKey) ? (object.get ?
        object.get(valueKey) : object[valueKey]) : object ;

      if (currentSelectedVal && value){
        if( currentSelectedVal === value ) {
          this.set('title', name) ;
          this.set('icon', icon) ;
        }
      }

      //Check if the item is currentSelectedItem or not
      if(name === this.title) {

        //set the itemIdx - To change the prefMatrix accordingly.
        this.set('itemIdx', idx) ;
        isChecked = checkboxEnabled ? YES : NO ;
      }
      else {
        isChecked = NO ;
      }

      //Set the first item from the list as default selected item
      if (idx === 0) {
        this._defaultVal = value ;
        this._defaultTitle = name ;
        this._defaultIcon = icon ;
      }

      //Set the items in the itemList array
      itemList.push({
        title: name,
        icon: icon,
        newVal:value,
        isEnabled: YES,
        checkbox: isChecked,
        action: this.displaySelectedItem
      });
    }

    idx += 1 ;

    this.set('itemList', itemList) ;
    }, this ) ;

    if(firstTime) {
      var value = this.get('value') ;
      if(SC.none(value)) {
        this.set('value', this._defaultVal) ;
        this.set('title', this._defaultTitle) ;
        this.set('icon', this._defaultIcon) ;
      }
    }

    //Set the preference matrix for the menu pane
    this.changeDropDownPreferMatrix(this.itemIdx) ;

    sc_super() ;
  },

  /**
    Button action handler

    @private
    @param {DOMMouseEvent} evt mouseup event that triggered the action
  */
  _action: function( evt )
  {
    var buttonLabel = this.$('.sc-button-label')[0] ;
    var menuWidth = this.get('layer').offsetWidth ; // Get the length of the text on the button in pixels
    var scrollWidth = buttonLabel.scrollWidth ;
    var lastMenuWidth = this.get('lastMenuWidth'), menuWidth;
    if(scrollWidth) {
       var offsetWidth = buttonLabel.offsetWidth ; // Get the original width of the label in the button
       if(scrollWidth && offsetWidth) {
          menuWidth = menuWidth + scrollWidth - offsetWidth ; //Add the difference of the offset Height and the scrollHeight to the menu width
       }
    }
    if(!lastMenuWidth || (menuWidth > lastMenuWidth)) {
      lastMenuWidth = menuWidth ;
    }
    this.set('lastMenuWidth',lastMenuWidth) ;
    var currSel = this.get('currentSelItem') ;
    var itemList = this.get('itemList') ;
    var menuControlSize = this.get('controlSize') ;
    var menu = SC.MenuPane.create({

      /**
        Class name - drop-down-menu-item

      */
      classNames: ['drop-down-menu-item'],

      /**
        The menu items are set from the itemList property of DropDownMenu

        @property
      */
      items: itemList,

      /**
        This property enables all the items and makes them selectable.

        @property
      */
      isEnabled: YES,

      preferType: SC.PICKER_MENU,
      itemHeightKey: 'height',
      layout: { width: lastMenuWidth },
      controlSize: menuControlSize,
      itemWidth: lastMenuWidth,
      contentView: SC.View.extend({
      })
    }) ;

    // no menu to toggle... bail...
    if (!menu) return NO ;
    menu.popup(this, this.preferMatrix) ;
    menu.set('currentSelectedMenuItem', currSel) ;
    return YES ;
  },

  /**
     Action method for the drop down menu items

  */
  displaySelectedItem: function() {

    //Get MenuPane, currentSelectedMenuItem & menuItemView
    var menuView = this.get('parentView') ;
    var currSel = menuView.get('currentSelectedMenuItem') ;
    var itemViews = menuView.menuItemViews ;
    var title,newVal ;

    //  Fetch the index of the current selected item
    var itemIdx = 0 ;
    if (currSel && itemViews) {
      itemIdx = itemViews.indexOf(currSel) ;
    }

    // Get the drop down View
    var button = this.getPath('parentView.anchor') ;

    // set the value and title
    var object = menuView.get('items') ;
    var len = object.length ;
    var found = null ;

    while (!found && (--len >= 0)) {
      title = object[len].title ? object[len].title: object.toString() ;
      newVal =  object[len].newVal ? object[len].newVal: title ;

      if (title === this.get('value')) {
        found = object ;
        button.set('value', newVal) ;
        button.set('title', title) ;
      }
    }

    // set the icon, currentSelectedItem and itemIdx
    button.set('icon', this.get('icon')).set('currentSelItem', currSel).
      set('itemIdx', itemIdx) ;
  },

  /**
     Set the "top" attribute in the prefer matrix property which will
     position menu such that the selected item in the menu will be
     place aligned to the item on the button when menu is opened.
  */
  changeDropDownPreferMatrix: function() {
    var preferMatrixAttributeTop = 0 ;
    var itemIdx = this.get('itemIdx') ;
    var leftAlign = this.get('leftAlign') ;
    var defPreferMatrix ;
    var tempPreferMatrix ;

    if(this.get('isDefaultPosition')) {
      defPreferMatrix = [leftAlign, 4, 3] ;
      this.set('preferMatrix', defPreferMatrix) ;
    }
    else {
      if(itemIdx)
        preferMatrixAttributeTop = itemIdx * this.CUSTOM_MENU_ITEM_HEIGHT ;
      tempPreferMatrix = [leftAlign, -preferMatrixAttributeTop, 2] ;
      this.set('preferMatrix', tempPreferMatrix) ;
    }
  }

}) ;

<commit_msg>changes for handling mouseDown evt.<commit_after>// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  Drop Down Menu has a functionality similar to that of SelectField

  Clicking the DropDownMenu button displays a menu pane with a
  list of items. The selected item will be displayed on the button.
  User has the option of enabling checkbox for the selected menu item.

  @extends SC.ButtonView
  @version 1.0
  @author Mohammed Ashik
*/
sc_require('views/button');

SC.DropDownMenu = SC.ButtonView.extend(
/** @scope SC.DropDownMenu.prototype */ {

  /**
    An array of items that will be form the menu you want to show.

    @property
    @type {Array}
  */
  objects: [],

  /**
    Binding default for an array of objects

    @property
    @default SC.Binding.multiple()
  */
  objectsBindingDefault: SC.Binding.multiple(),

  /**
    If you set this to a non-null value, then the name shown for each
    menu item will be pulled from the object using the named property.
    if this is null, the collection objects themselves will be used.

    @property
    @type {String}
    @default: null
  */
  nameKey: null,

  /**
    If you set this to a non-null value, then the value of this key will
    be used to sort the objects.  If this is not set, then nameKey will
    be used.

    @property
    @type: {String}
    @default: null
  */
  sortKey: null,

  /**
     Set this to a non-null value to use a key from the passed set of objects
     as the value for the options popup.  If you don't set this, then the
     objects themselves will be used as the value.

     @property
     @type {String}
     @default null
  */
  valueKey: null,

  /**
    If true, the empty name will be localized.

    @property
    @type {Boolean}
    @default YES
  */
  localize: YES,

  /**
    if true, it means that no sorting will occur, objects will appear
    in the same order as in the array

    @property
    @type {Boolean}
    @default YES
  */
  disableSort: YES,

  /**

    @property
    @default ['drop-down-menu']
  */
  classNames: ['drop-down-menu'],

  /**
    Menu item list

    @property
    @type:{Array}
  */
  itemList: [],

  /**
    Current selected menu item

    @property
    @default null
  */
  currentSelItem: null,

  /**
    Property to set the index of the selected menu item. This in turn
    is used to calculate the preferMatrix.

    @property
    @type {Number}
    @default null
  */
  itemIdx: null,

  /**
     Current Value of the dropDown

     @property
     @default null
  */
  value: null ,

  /**
    if this property is set to 'YES', a checbox is shown next to the
    selected menu item.

    @private
    @default NO
  */
  checkboxEnabled: NO,

  /**
    Default value of the drop down.
     This will be the first item from the menu item list.

    @private
  */
  _defaultVal: null,

  /**
    Default value of the drop down.
     This will be the title corresponding to the _defaultVal.

    @private
  */
  _defaultTitle: null,

  /**
    Default value of the drop down.
     This will be the icon corresponding to the _defaultVal.

    @private
  */
  _defaultIcon: null,

  /**
    @private

    The button theme will be popup
  */
  theme: 'popup',

  /**
    Render method gets triggered when these properties change

    @property
    @type{SC.Array}
  */
  displayProperties: ['icon', 'value','controlSize'],

  /**
    Prefer matrix to position the drop down menu such that the
    selected item for the menu item will appear aligned to the
    the button. The value at the second index(0) changes based on the
    postion(index) of the menu item in the menu pane.

    @property
    @type {Array}
    @default [ 0, 0, 2]

  */
  preferMatrix: null,

  /**
    Width of the sprite image that gets applied due to the theme.
     This has to be accounted for while calculating the actual
     width of the button

    @property
    @type {Number}
    @default 32
  */
  DROP_DOWN_SPRITE_WIDTH: 32,

  /**
    Property to set the menu item height. This in turn is used for
    the calculation of prefMatrix.

    @property
    @type {Number}
    @default 20
  */
  CUSTOM_MENU_ITEM_HEIGHT: 20,

  /**
    Binds the button's selection state to the menu's visibility.

    @private
  */
  isSelectedBinding: '*menu.isVisibleInWindow',

  /**
    If this property is set to 'YES', the menu pane will be positioned
    below the anchor.

    @private
    @default NO
  */
  isDefaultPosition: NO,

  /**
    lastMenuWidth is the width of the last menu which was created from
    the objects of this drop down.

    @private
  */
  lastMenuWidth: null,
  /**
    Left Alignment based on the size of the button

    @private
  */
  leftAlign: function() {
    var val = 0 ;
    var controlSize = this.get('controlSize') ;
    if(controlSize === SC.SMALL_CONTROL_SIZE) val = -14 ;
    if(controlSize === SC.REGULAR_CONTROL_SIZE) val = -16 ;
    return val;
  }.property('controlSize'),

  /**
    override this method to implement your own sorting of the menu. By
    default, menu items are sorted using the value shown or the sortKey

    @param{SC.Array} objects the unsorted array of objects to display.
    @returns sorted array of objects
  */
  sortObjects: function(objects) {
    if(!this.get('disableSort')){
      var nameKey = this.get('sortKey') || this.get('nameKey') ;
      objects = objects.sort(function(a,b) {
        if (nameKey) {
          a = a.get ? a.get(nameKey) : a[nameKey] ;
          b = b.get ? b.get(nameKey) : b[nameKey] ;
        }
        return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
      }) ;
    }
    return objects ;
  },

  /**
    render method

    @private
  */
  render: function(context,firstTime) {

    var layoutWidth = this.layout.width ;
    if(firstTime && layoutWidth) {
      this.adjust({ width: layoutWidth - this.DROP_DOWN_SPRITE_WIDTH }) ;
    }

    var objects = this.get('objects') ;
    objects = this.sortObjects(objects) ;

    //Get the namekey, iconKey and valueKey set by the user
    var nameKey = this.get('nameKey') ;
    var iconKey = this.get('iconKey') ;
    var valueKey = this.get('valueKey') ;
    var checkboxEnabled = this.get('checkboxEnabled') ;

    //get the current selected value
    var currentSelectedVal = this.get('value') ;

    // get the localization flag.
    var shouldLocalize = this.get('localize') ;

    //itemList array to set the menu items
    var itemList = [] ;

    //to set the 'checkbox' property of menu items
    var isChecked = YES ;

    //index for finding the first item in the list
    var idx = 0 ;

    objects.forEach(function(object) {
    if (object) {

      //Get the name value. If value key is not specified convert obj
      //to string
      var name = nameKey ? (object.get ?
        object.get(nameKey) : object[nameKey]) : object.toString() ;

      // localize name if specified.
      name = shouldLocalize? name.loc() : name ;

      //Get the icon value
      var icon = iconKey ? (object.get ?
        object.get(iconKey) : object[iconKey]) : null ;
      if (SC.none(object[iconKey])) icon = null ;

      // get the value using the valueKey or the object if no valueKey.
      // then convert to a string or use _guid if one of available.
      var value = (valueKey) ? (object.get ?
        object.get(valueKey) : object[valueKey]) : object ;

      if (currentSelectedVal && value){
        if( currentSelectedVal === value ) {
          this.set('title', name) ;
          this.set('icon', icon) ;
        }
      }

      //Check if the item is currentSelectedItem or not
      if(name === this.title) {

        //set the itemIdx - To change the prefMatrix accordingly.
        this.set('itemIdx', idx) ;
        isChecked = checkboxEnabled ? YES : NO ;
      }
      else {
        isChecked = NO ;
      }

      //Set the first item from the list as default selected item
      if (idx === 0) {
        this._defaultVal = value ;
        this._defaultTitle = name ;
        this._defaultIcon = icon ;
      }

      //Set the items in the itemList array
      itemList.push({
        title: name,
        icon: icon,
        newVal:value,
        isEnabled: YES,
        checkbox: isChecked,
        action: this.displaySelectedItem
      });
    }

    idx += 1 ;

    this.set('itemList', itemList) ;
    }, this ) ;

    if(firstTime) {
      var value = this.get('value') ;
      if(SC.none(value)) {
        this.set('value', this._defaultVal) ;
        this.set('title', this._defaultTitle) ;
        this.set('icon', this._defaultIcon) ;
      }
    }

    //Set the preference matrix for the menu pane
    this.changeDropDownPreferMatrix(this.itemIdx) ;

    sc_super() ;
  },

  /**
    Button action handler

    @private
    @param {DOMMouseEvent} evt mouseup event that triggered the action
  */
  _action: function( evt )
  {
    var buttonLabel = this.$('.sc-button-label')[0] ;
    var menuWidth = this.get('layer').offsetWidth ; // Get the length of the text on the button in pixels
    var scrollWidth = buttonLabel.scrollWidth ;
    var lastMenuWidth = this.get('lastMenuWidth'), menuWidth;
    if(scrollWidth) {
       var offsetWidth = buttonLabel.offsetWidth ; // Get the original width of the label in the button
       if(scrollWidth && offsetWidth) {
          menuWidth = menuWidth + scrollWidth - offsetWidth ; //Add the difference of the offset Height and the scrollHeight to the menu width
       }
    }
    if(!lastMenuWidth || (menuWidth > lastMenuWidth)) {
      lastMenuWidth = menuWidth ;
    }
    this.set('lastMenuWidth',lastMenuWidth) ;
    var currSel = this.get('currentSelItem') ;
    var itemList = this.get('itemList') ;
    var menuControlSize = this.get('controlSize') ;
    var menu = SC.MenuPane.create({

      /**
        Class name - drop-down-menu-item

      */
      classNames: ['drop-down-menu-item'],

      /**
        The menu items are set from the itemList property of DropDownMenu

        @property
      */
      items: itemList,

      /**
        This property enables all the items and makes them selectable.

        @property
      */
      isEnabled: YES,

      preferType: SC.PICKER_MENU,
      itemHeightKey: 'height',
      layout: { width: lastMenuWidth },
      controlSize: menuControlSize,
      itemWidth: lastMenuWidth,
      contentView: SC.View.extend({
      })
    }) ;

    // no menu to toggle... bail...
    if (!menu) return NO ;
    menu.popup(this, this.preferMatrix) ;
    menu.set('currentSelectedMenuItem', currSel) ;
    return YES ;
  },

  /**
     Action method for the drop down menu items

  */
  displaySelectedItem: function() {

    //Get MenuPane, currentSelectedMenuItem & menuItemView
    var menuView = this.get('parentView') ;
    var currSel = menuView.get('currentSelectedMenuItem') ;
    var itemViews = menuView.menuItemViews ;
    var title,newVal ;

    //  Fetch the index of the current selected item
    var itemIdx = 0 ;
    if (currSel && itemViews) {
      itemIdx = itemViews.indexOf(currSel) ;
    }

    // Get the drop down View
    var button = this.getPath('parentView.anchor') ;

    // set the value and title
    var object = menuView.get('items') ;
    var len = object.length ;
    var found = null ;

    while (!found && (--len >= 0)) {
      title = object[len].title ? object[len].title: object.toString() ;
      newVal =  object[len].newVal ? object[len].newVal: title ;

      if (title === this.get('value')) {
        found = object ;
        button.set('value', newVal) ;
        button.set('title', title) ;
      }
    }

    // set the icon, currentSelectedItem and itemIdx
    button.set('icon', this.get('icon')).set('currentSelItem', currSel).
      set('itemIdx', itemIdx) ;
  },

  /**
     Set the "top" attribute in the prefer matrix property which will
     position menu such that the selected item in the menu will be
     place aligned to the item on the button when menu is opened.
  */
  changeDropDownPreferMatrix: function() {
    var preferMatrixAttributeTop = 0 ;
    var itemIdx = this.get('itemIdx') ;
    var leftAlign = this.get('leftAlign') ;
    var defPreferMatrix ;
    var tempPreferMatrix ;

    if(this.get('isDefaultPosition')) {
      defPreferMatrix = [leftAlign, 4, 3] ;
      this.set('preferMatrix', defPreferMatrix) ;
    }
    else {
      if(itemIdx)
        preferMatrixAttributeTop = itemIdx * this.CUSTOM_MENU_ITEM_HEIGHT ;
      tempPreferMatrix = [leftAlign, -preferMatrixAttributeTop, 2] ;
      this.set('preferMatrix', tempPreferMatrix) ;
    }
  },

  /**
    @private

    Holding down the button should display the menu pane.
  */
  mouseDown: function(evt) {
    if (!this.get('isEnabled')) return YES ; // handled event, but do nothing
    this.set('isActive', YES);
    this._isMouseDown = YES;
    this._action() ;
    return YES ;
  }

}) ;

<|endoftext|>


<commit_before>import addElOrigin from "./addElOrigin"
import unstringTracifyArguments from "./unstringTracifyArguments"
import makeTraceObject from "./makeTraceObject"
import Origin from "../origin"
import _ from "underscore"
import stringTraceUseValue from "./stringTraceUseValue"
import {makeSureInitialHTMLHasBeenProcessed} from "./processElementsAvailableOnInitialLoad"
import processJavaScriptCode from "../../process-javascript-code"

window.fromJSDynamicFiles = {}
window.fromJSDynamicFileOrigins = {}
var tracingEnabled = false;

var originalCreateElement = document.createElement
window.originalCreateElement = originalCreateElement

var appendChildPropertyDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, "appendChild");
window.originalAppendChildPropertyDescriptor = appendChildPropertyDescriptor

var nativeSetAttribute = Element.prototype.setAttribute;
window.nativeSetAttribute = nativeSetAttribute

var nativeClassNameDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, "className");
window.nativeClassNameDescriptor = nativeClassNameDescriptor

var nativeInnerHTMLDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML");
window.nativeInnerHTMLDescriptor = nativeInnerHTMLDescriptor;

var nativeExec = RegExp.prototype.exec;
window.nativeExec = nativeExec;

var nativeFunction = Function
window.nativeFunction = nativeFunction

var nativeJSONParse = JSON.parse
window.nativeJSONParse = nativeJSONParse

var nativeLocalStorageGetItem = localStorage.getItem
window.nativeLocalStorageGetItem = nativeLocalStorageGetItem

export function enableTracing(){
    if (tracingEnabled){
        return
    }
    tracingEnabled = true

    document.createElement = function(tagName){

        var el = originalCreateElement.call(this, tagName)
        addElOrigin(el, "openingTagStart", {
            action: "createElement",
            inputValues: [tagName],
            value: el.tagName
        })
        addElOrigin(el, "openingTagEnd", {
            action: "createElement",
            inputValues: [tagName],
            value: el.tagName
        })
        return el;
    }

    Object.defineProperty(Node.prototype, "appendChild", {
        get: function(){
            return function(appendedEl){
                addElOrigin(this, "appendChild",{
                    action: "appendChild",
                    stack: new Error().stack.split("\n"),
                    inputValues: [appendedEl],
                    valueOfEl: appendedEl,
                    child: appendedEl
                })

                return appendChildPropertyDescriptor.value.apply(this, arguments)
            }
        }
    })

    Element.prototype.setAttribute = function(attrName, value){
        addElOrigin(this, "attribute_" + attrName.toString(), {
            action: "setAttribute",
            inputValues: [attrName, value],
            value: "not gotten around to making this work yet"
        })
        return nativeSetAttribute.apply(this, arguments)
    }

    var nativeRemoveAttribute = Element.prototype.removeAttribute;
    Element.prototype.removeAttribute = function(attrName){
        addElOrigin(this, "attribute_" +attrName.toString(), {
            action: "removeAttribute",
            inputValues: [attrName],
            value: "whateverr"
        })
        return nativeRemoveAttribute.apply(this, arguments)
    }

    Object.defineProperty(Element.prototype, "className", {
        set: function(newValue){
            addElOrigin(this, "attribute_class", {
                action: "set className",
                value: " class='" + newValue.toString() + "'",
                inputValues: [newValue]
            })
            return nativeClassNameDescriptor.set.apply(this, arguments)
        },
        get: function(){
            return nativeClassNameDescriptor.get.apply(this, arguments)
        }
    })

    JSON.parse = function(str){
        var parsedVal = nativeJSONParse.apply(this, arguments)
        for (var key in parsedVal) {
            if (typeof parsedVal[key] !== "string") continue
            parsedVal[key] =  makeTraceObject(
                {
                    value: parsedVal[key],
                    origin: new Origin({
                        value: parsedVal[key],
                        inputValues: [str],
                        inputValuesCharacterIndex: [str.toString().indexOf(parsedVal[key])], // not very accurate, but better than nothing/always using char 0
                        action: "JSON.parse",
                        actionDetails: key
                    })
                }
            )
        }

        return parsedVal
    }

    Object.defineProperty(Element.prototype, "innerHTML", {
        set: nativeInnerHTMLDescriptor.set,
        get: function(){
            makeSureInitialHTMLHasBeenProcessed()

            var innerHTML = nativeInnerHTMLDescriptor.get.apply(this, arguments)
            return makeTraceObject({
                value: innerHTML,
                origin: new Origin({
                    value: innerHTML,
                    action: "Read Element innerHTML",
                    inputValues: []
                })
            })
        }
    })

    localStorage.getItem = function(key){
        var val = nativeLocalStorageGetItem.apply(this, arguments)
        if (typeof val === "string"){
            val = makeTraceObject({
                value: val,
                origin: new Origin({
                    action: "localStorage.getItem",
                    actionDetails: key,
                    value: val,
                    inputValues: [key]
                }),
            })
        }
        return val;
    }

    RegExp.prototype.exec = function(){
        var args = unstringTracifyArguments(arguments)
        return nativeExec.apply(this, args)
    }

    window.Function = function(code){
        var args = Array.prototype.slice.apply(arguments)
        var code = args.pop()
        var argsWithoutCode = args.slice()

        var id = _.uniqueId();
        var filename = "DynamicFunction" + id + ".js"
        var res = processJavaScriptCode(stringTraceUseValue(code), {filename: filename})
        args.push(res.code)

        var fnName = "DynamicFunction" + id
        var smFilename = filename + ".map"
        var evalCode = "function " + fnName + "(" + argsWithoutCode.join(",") + "){" + res.code + "}" +
            "\n//# sourceURL=" + filename +
            "\n//# sourceMappingURL=" + smFilename

        // create script tag instead of eval to prevent strict mode from propagating
        // (I'm guessing if you call eval from code that's in strict mode  strict mode will
        // propagate to the eval'd code.)
        var script = document.createElement("script")
        script.innerHTML = evalCode
        document.body.appendChild(script)

        script.remove();

        fromJSDynamicFiles[smFilename] = res.map
        fromJSDynamicFiles[filename] = evalCode
        fromJSDynamicFiles[filename + "?dontprocess=yes"] = code.value
        fromJSDynamicFileOrigins[filename + "?dontprocess=yes"] = new Origin({
            action: "Dynamic Script",
            value: code.value,
            inputValues: [code.origin]
        })

        return function(){
            return window[fnName].apply(this, arguments)
        }
    }

}

export function disableTracing(){
    if (!tracingEnabled) {
        return;
    }
    window.JSON.parse = window.nativeJSONParse
    document.createElement = window.originalCreateElement
    Object.defineProperty(Node.prototype, "appendChild", window.originalAppendChildPropertyDescriptor);
    Element.prototype.setAttribute = window.nativeSetAttribute
    Object.defineProperty(Element.prototype, "innerHTML", nativeInnerHTMLDescriptor)
    localStorage.getItem = window.nativeLocalStorageGetItem;
    RegExp.prototype.exec = window.nativeExec
    window.Function = nativeFunction
    Object.defineProperty(Element.prototype, "className", window.nativeClassNameDescriptor)

    tracingEnabled = false;
}

window._disableTracing = disableTracing
<commit_msg>Failed attempt at tracing HTMLElement.prototype.dataset assignments.<commit_after>import addElOrigin from "./addElOrigin"
import unstringTracifyArguments from "./unstringTracifyArguments"
import makeTraceObject from "./makeTraceObject"
import Origin from "../origin"
import _ from "underscore"
import stringTraceUseValue from "./stringTraceUseValue"
import {makeSureInitialHTMLHasBeenProcessed} from "./processElementsAvailableOnInitialLoad"
import processJavaScriptCode from "../../process-javascript-code"

window.fromJSDynamicFiles = {}
window.fromJSDynamicFileOrigins = {}
var tracingEnabled = false;

var originalCreateElement = document.createElement
window.originalCreateElement = originalCreateElement

var appendChildPropertyDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, "appendChild");
window.originalAppendChildPropertyDescriptor = appendChildPropertyDescriptor

var nativeSetAttribute = Element.prototype.setAttribute;
window.nativeSetAttribute = nativeSetAttribute

var nativeClassNameDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, "className");
window.nativeClassNameDescriptor = nativeClassNameDescriptor

var nativeInnerHTMLDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML");
window.nativeInnerHTMLDescriptor = nativeInnerHTMLDescriptor;

var nativeExec = RegExp.prototype.exec;
window.nativeExec = nativeExec;

var nativeFunction = Function
window.nativeFunction = nativeFunction

var nativeJSONParse = JSON.parse
window.nativeJSONParse = nativeJSONParse

var nativeLocalStorageGetItem = localStorage.getItem
window.nativeLocalStorageGetItem = nativeLocalStorageGetItem

export function enableTracing(){
    if (tracingEnabled){
        return
    }
    tracingEnabled = true

    document.createElement = function(tagName){

        var el = originalCreateElement.call(this, tagName)
        addElOrigin(el, "openingTagStart", {
            action: "createElement",
            inputValues: [tagName],
            value: el.tagName
        })
        addElOrigin(el, "openingTagEnd", {
            action: "createElement",
            inputValues: [tagName],
            value: el.tagName
        })
        return el;
    }

    Object.defineProperty(Node.prototype, "appendChild", {
        get: function(){
            return function(appendedEl){
                addElOrigin(this, "appendChild",{
                    action: "appendChild",
                    stack: new Error().stack.split("\n"),
                    inputValues: [appendedEl],
                    valueOfEl: appendedEl,
                    child: appendedEl
                })

                return appendChildPropertyDescriptor.value.apply(this, arguments)
            }
        }
    })

    Element.prototype.setAttribute = function(attrName, value){
        addElOrigin(this, "attribute_" + attrName.toString(), {
            action: "setAttribute",
            inputValues: [attrName, value],
            value: "not gotten around to making this work yet"
        })
        return nativeSetAttribute.apply(this, arguments)
    }

    var nativeRemoveAttribute = Element.prototype.removeAttribute;
    Element.prototype.removeAttribute = function(attrName){
        addElOrigin(this, "attribute_" +attrName.toString(), {
            action: "removeAttribute",
            inputValues: [attrName],
            value: "whateverr"
        })
        return nativeRemoveAttribute.apply(this, arguments)
    }

    Object.defineProperty(Element.prototype, "className", {
        set: function(newValue){
            addElOrigin(this, "attribute_class", {
                action: "set className",
                value: " class='" + newValue.toString() + "'",
                inputValues: [newValue]
            })
            return nativeClassNameDescriptor.set.apply(this, arguments)
        },
        get: function(){
            return nativeClassNameDescriptor.get.apply(this, arguments)
        }
    })

    JSON.parse = function(str){
        var parsedVal = nativeJSONParse.apply(this, arguments)
        for (var key in parsedVal) {
            if (typeof parsedVal[key] !== "string") continue
            parsedVal[key] =  makeTraceObject(
                {
                    value: parsedVal[key],
                    origin: new Origin({
                        value: parsedVal[key],
                        inputValues: [str],
                        inputValuesCharacterIndex: [str.toString().indexOf(parsedVal[key])], // not very accurate, but better than nothing/always using char 0
                        action: "JSON.parse",
                        actionDetails: key
                    })
                }
            )
        }

        return parsedVal
    }

    Object.defineProperty(Element.prototype, "innerHTML", {
        set: nativeInnerHTMLDescriptor.set,
        get: function(){
            makeSureInitialHTMLHasBeenProcessed()

            var innerHTML = nativeInnerHTMLDescriptor.get.apply(this, arguments)
            return makeTraceObject({
                value: innerHTML,
                origin: new Origin({
                    value: innerHTML,
                    action: "Read Element innerHTML",
                    inputValues: []
                })
            })
        }
    })

    localStorage.getItem = function(key){
        var val = nativeLocalStorageGetItem.apply(this, arguments)
        if (typeof val === "string"){
            val = makeTraceObject({
                value: val,
                origin: new Origin({
                    action: "localStorage.getItem",
                    actionDetails: key,
                    value: val,
                    inputValues: [key]
                }),
            })
        }
        return val;
    }

    RegExp.prototype.exec = function(){
        var args = unstringTracifyArguments(arguments)
        return nativeExec.apply(this, args)
    }

    window.Function = function(code){
        var args = Array.prototype.slice.apply(arguments)
        var code = args.pop()
        var argsWithoutCode = args.slice()

        var id = _.uniqueId();
        var filename = "DynamicFunction" + id + ".js"
        var res = processJavaScriptCode(stringTraceUseValue(code), {filename: filename})
        args.push(res.code)

        var fnName = "DynamicFunction" + id
        var smFilename = filename + ".map"
        var evalCode = "function " + fnName + "(" + argsWithoutCode.join(",") + "){" + res.code + "}" +
            "\n//# sourceURL=" + filename +
            "\n//# sourceMappingURL=" + smFilename

        // create script tag instead of eval to prevent strict mode from propagating
        // (I'm guessing if you call eval from code that's in strict mode  strict mode will
        // propagate to the eval'd code.)
        var script = document.createElement("script")
        script.innerHTML = evalCode
        document.body.appendChild(script)

        script.remove();

        fromJSDynamicFiles[smFilename] = res.map
        fromJSDynamicFiles[filename] = evalCode
        fromJSDynamicFiles[filename + "?dontprocess=yes"] = code.value
        fromJSDynamicFileOrigins[filename + "?dontprocess=yes"] = new Origin({
            action: "Dynamic Script",
            value: code.value,
            inputValues: [code.origin]
        })

        return function(){
            return window[fnName].apply(this, arguments)
        }
    }

    // try to add this once, but it turned out the .dataset[sth] assignment
    // was in a chrome extension that uses a different HTMLElement object
    window.nativeDataSetDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "dataset")
    // Object.defineProperty(HTMLElement.prototype, "dataset", {
    //     set: function(){
    //         return nativeDataSetDescriptor.set.apply(this, arguments)
    //     },
    //     get: function(){
    //         var nativeRes = nativeDataSetDescriptor.get.apply(this, arguments)
    //
    //         var proxy = new Proxy(nativeRes, {
    //             set: function(target, name, value){
    //                 nativeRes[name] = value
    //             }
    //         })
    //         return proxy;
    //     }
    // })

}

export function disableTracing(){
    if (!tracingEnabled) {
        return;
    }
    window.JSON.parse = window.nativeJSONParse
    document.createElement = window.originalCreateElement
    Object.defineProperty(Node.prototype, "appendChild", window.originalAppendChildPropertyDescriptor);
    Element.prototype.setAttribute = window.nativeSetAttribute
    Object.defineProperty(Element.prototype, "innerHTML", nativeInnerHTMLDescriptor)
    localStorage.getItem = window.nativeLocalStorageGetItem;
    RegExp.prototype.exec = window.nativeExec
    window.Function = nativeFunction
    Object.defineProperty(Element.prototype, "className", window.nativeClassNameDescriptor)
    Object.defineProperty(HTMLElement.prototype, "dataset", window.nativeDataSetDescriptor)

    tracingEnabled = false;
}

window._disableTracing = disableTracing
<|endoftext|>


<commit_before>goog.provide('ol.events.ConditionType');
goog.provide('ol.events.condition');

goog.require('goog.asserts');
goog.require('goog.dom.TagName');
goog.require('goog.functions');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.MapBrowserPointerEvent');

/**
 * A function that takes an {@link ol.MapBrowserEvent} and returns a
 * `{boolean}`. If the condition is met, true should be returned.
 *
 * @typedef {function(ol.MapBrowserEvent): boolean}
 * @todo api
 */
ol.events.ConditionType;

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt key is pressed.
 * @todo api
 */
ol.events.condition.altKeyOnly = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      browserEvent.altKey &&
      !browserEvent.platformModifierKey &&
      !browserEvent.shiftKey);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt and shift keys are pressed.
 * @todo api
 */
ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      browserEvent.altKey &&
      !browserEvent.platformModifierKey &&
      browserEvent.shiftKey);
};

/**
 * Always true.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True.
 * @todo api
 */
ol.events.condition.always = goog.functions.TRUE;

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the browser event is a `mousemove` event.
 * @todo api
 */
ol.events.condition.mouseMove = function(mapBrowserEvent) {
  return mapBrowserEvent.originalEvent.type == 'mousemove';
};

/**
 * Always false.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} False.
 * @todo api
 */
ol.events.condition.never = goog.functions.FALSE;

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a `singleclick` event.
 * @todo api
 */
ol.events.condition.singleClick = function(mapBrowserEvent) {
  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK;
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if there no modifier keys are pressed.
 * @todo api
 */
ol.events.condition.noModifierKeys = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      !browserEvent.altKey &&
      !browserEvent.platformModifierKey &&
      !browserEvent.shiftKey);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the platform modifier key is pressed.
 * @todo api
 */
ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      !browserEvent.altKey &&
      browserEvent.platformModifierKey &&
      !browserEvent.shiftKey);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the shift key is pressed.
 * @todo api
 */
ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      !browserEvent.altKey &&
      !browserEvent.platformModifierKey &&
      browserEvent.shiftKey);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if the target element is not editable.
 * @todo api
 */
ol.events.condition.targetNotEditable = function(mapBrowserEvent) {
  var target = mapBrowserEvent.browserEvent.target;
  goog.asserts.assertInstanceof(target, Element);
  var tagName = target.tagName;
  return (
      tagName !== goog.dom.TagName.INPUT &&
      tagName !== goog.dom.TagName.SELECT &&
      tagName !== goog.dom.TagName.TEXTAREA);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event originates from a mouse device.
 * @todo api
 */
ol.events.condition.mouseOnly = function(mapBrowserEvent) {
  goog.asserts.assertInstanceof(mapBrowserEvent, ol.MapBrowserPointerEvent);
  /* pointerId must be 1 for mouse devices,
   * see: http://www.w3.org/Submission/pointer-events/#pointerevent-interface
   */
  return mapBrowserEvent.pointerEvent.pointerId == 1;
};
<commit_msg>Add ol.events.condition.click<commit_after>goog.provide('ol.events.ConditionType');
goog.provide('ol.events.condition');

goog.require('goog.asserts');
goog.require('goog.dom.TagName');
goog.require('goog.functions');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.MapBrowserPointerEvent');

/**
 * A function that takes an {@link ol.MapBrowserEvent} and returns a
 * `{boolean}`. If the condition is met, true should be returned.
 *
 * @typedef {function(ol.MapBrowserEvent): boolean}
 * @todo api
 */
ol.events.ConditionType;

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt key is pressed.
 * @todo api
 */
ol.events.condition.altKeyOnly = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      browserEvent.altKey &&
      !browserEvent.platformModifierKey &&
      !browserEvent.shiftKey);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt and shift keys are pressed.
 * @todo api
 */
ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      browserEvent.altKey &&
      !browserEvent.platformModifierKey &&
      browserEvent.shiftKey);
};

/**
 * Always true.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True.
 * @todo api
 */
ol.events.condition.always = goog.functions.TRUE;

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `click` event.
 * @todo api
 */
ol.events.condition.click = function(mapBrowserEvent) {
  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK;
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the browser event is a `mousemove` event.
 * @todo api
 */
ol.events.condition.mouseMove = function(mapBrowserEvent) {
  return mapBrowserEvent.originalEvent.type == 'mousemove';
};

/**
 * Always false.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} False.
 * @todo api
 */
ol.events.condition.never = goog.functions.FALSE;

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a `singleclick` event.
 * @todo api
 */
ol.events.condition.singleClick = function(mapBrowserEvent) {
  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK;
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if there no modifier keys are pressed.
 * @todo api
 */
ol.events.condition.noModifierKeys = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      !browserEvent.altKey &&
      !browserEvent.platformModifierKey &&
      !browserEvent.shiftKey);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the platform modifier key is pressed.
 * @todo api
 */
ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      !browserEvent.altKey &&
      browserEvent.platformModifierKey &&
      !browserEvent.shiftKey);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the shift key is pressed.
 * @todo api
 */
ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
  var browserEvent = mapBrowserEvent.browserEvent;
  return (
      !browserEvent.altKey &&
      !browserEvent.platformModifierKey &&
      browserEvent.shiftKey);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if the target element is not editable.
 * @todo api
 */
ol.events.condition.targetNotEditable = function(mapBrowserEvent) {
  var target = mapBrowserEvent.browserEvent.target;
  goog.asserts.assertInstanceof(target, Element);
  var tagName = target.tagName;
  return (
      tagName !== goog.dom.TagName.INPUT &&
      tagName !== goog.dom.TagName.SELECT &&
      tagName !== goog.dom.TagName.TEXTAREA);
};

/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event originates from a mouse device.
 * @todo api
 */
ol.events.condition.mouseOnly = function(mapBrowserEvent) {
  goog.asserts.assertInstanceof(mapBrowserEvent, ol.MapBrowserPointerEvent);
  /* pointerId must be 1 for mouse devices,
   * see: http://www.w3.org/Submission/pointer-events/#pointerevent-interface
   */
  return mapBrowserEvent.pointerEvent.pointerId == 1;
};
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Alistair Stead
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

	var PageController = ['$scope', 'auth', 'api', '$window', '$rootScope', '$sce', '$timeout', function($scope, auth, api, $window, $rootScope, $sce, $timeout) {
		$scope.isAdminUser = $rootScope.user.role == 'ADMIN';
	    $scope.statistics = null;
	    $scope.setLoading(true);
	    $scope.apiVersion = api.contentVersion.get();
	    $scope.emailTypes = null;

	    $scope.lastContentIDSuccessfullyPreviewed = "";

	    $scope.htmlPreview = "";
	    $scope.plainTextPreview = "";
	    $scope.subjectPreview = "";

	    $scope.emailToSend = {
	    	emailType : -1,
	    	contentObjectId : "",
	    	users: {
		    	ADMIN : false,
		    	EVENT_MANAGER : false,
		    	CONTENT_EDITOR : false,
		    	TEACHER : false,
		    	TESTER : false,
		    	STAFF : false,
		    	STUDENT : false,
			}
	    };

	    $scope.queueSize = "?";
	    var checkTimes = 0;

	    var queueSizeChecker = function(){
	    	api.email.getQueueSize({}).$promise.then(function(result){
	    		var d = new Date();
    			$scope.queueSize = "" + result[0] + " at " + d.toLocaleTimeString();
				$timeout(queueSizeChecker, 5000);
    		}).catch(function(e){
				$scope.showToast($scope.toastTypes.Failure, "Email Queue Size", "Could not get email queue size (" + e.status + ") " + e.statusText);
				clearInterval(timerId);
			});
    	}

	    queueSizeChecker();

		var getUserIdArrayFromTextArea = function(){
			var userids = $scope.csvuseridlist.split(/[\s,]+/).map(function(e) {return parseInt(e);});;
			return userids;
		}

	    $scope.getTotalUsers = function(){
	    	if($scope.userSelectionType == 'csvuseridlist') {
				var userIdList = getUserIdArrayFromTextArea();
				return userIdList.length;
	    	}

	    	var total = 0;
			if($scope.emailToSend.users.ADMIN) {
	    		total += parseInt($scope.statistics.role.ADMIN);
	    	}

	    	if($scope.emailToSend.users.EVENT_MANAGER) {
	    		total += parseInt($scope.statistics.role.EVENT_MANAGER);
	    	}

	    	if($scope.emailToSend.users.CONTENT_EDITOR) {
	    		total += parseInt($scope.statistics.role.CONTENT_EDITOR);
	    	}

	    	if($scope.emailToSend.users.TEACHER) {
	    		total += parseInt($scope.statistics.role.TEACHER);
	    	}

	    	if($scope.emailToSend.users.TESTER) {
	    		total += parseInt($scope.statistics.role.TESTER);
	    	}

	    	if($scope.emailToSend.users.STAFF) {
	    		total += parseInt($scope.statistics.role.STAFF);
	    	}

	    	if($scope.emailToSend.users.STUDENT) {
	    		total += parseInt($scope.statistics.role.STUDENT);
	    	}
	    	return total;
	    }

	    api.statisticsEndpoint.get().$promise.then(function(result){
	        $scope.statistics = result;
	        $scope.setLoading(false);
	    }).catch(function(e){
			$scope.showToast($scope.toastTypes.Failure, "Statistics load failed", "With error message (" + e.status + ") " + e.statusText);
	    });

	    api.email.getPreferences().$promise.then(function(result){
	    	$scope.emailTypes = result;
	    	console.log(result);
	    }).catch(function(e){
			$scope.showToast($scope.toastTypes.Failure, "Preferences load failed", "With error message (" + e.status + ") " + e.statusText);
	    });

	    $scope.loadContentTemplate = function(contentId){
	    	$scope.setLoading(true);
	    	api.email.get({"id" : $scope.emailToSend.contentObjectId}).$promise.then(function(content){
	        	$scope.subjectPreview = content.subject;
	        	$scope.htmlPreview = $sce.trustAsResourceUrl("data:text/html," + encodeURIComponent(content.html));
	        	$scope.plainTextPreview = $sce.trustAsResourceUrl("data:text," + content.plainText);
	        	$scope.setLoading(false);
	        	$scope.lastContentIDSuccessfullyPreviewed = $scope.emailToSend.contentObjectId;
	    	}).catch(function(e){
    			$scope.showToast($scope.toastTypes.Failure, "Email template failed", "With error message (" + e.status + ") " + e.statusText);
	        	$scope.setLoading(false);
	        	$scope.lastContentIDSuccessfullyPreviewed = "";
	    	});
	    };

        $scope.emailTypeChanged = function(idOfSelectedEmailType){
			$scope.emailToSend.emailType = idOfSelectedEmailType;
		};

	    $scope.validateAndSendEmails = function(){

	    	if(!$scope.emailToSend.emailType || $scope.emailToSend.emailType < 0){
    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must select a type of email");
				return;
	    	}

	    	if($scope.lastContentIDSuccessfullyPreviewed == "" || $scope.emailToSend.contentObjectId != $scope.lastContentIDSuccessfullyPreviewed){
    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must preview the email before sending");
				return;
	    	}

			if($scope.userSelectionType == 'userfilter') {
		    	var usersSelected = false;
		    	for(var key in $scope.emailToSend.users) {
	    			if($scope.emailToSend.users[key]){
	    				usersSelected = true;
	    			}
		    	}

		    	if(!usersSelected){
					$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must select users to send the email to");
					return;
				}
			}
			else if($scope.userSelectionType == 'csvuseridlist') {
				var letters = /[^,0-9]+/;

				if(letters.test($scope.csvuseridlist)){
					$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "Userids field contains invalid characters");
					return;
				}

				var userids = getUserIdArrayFromTextArea($scope.csvuseridlist);

				// Check for NaNs and invalid characters
				for(var i = 0; i < userids.length; i++){
					if(isNaN(userids[i])){
						$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "One of the userIds given evaluates to NaN");
						return;
					}
				}
			}
			else {
				$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "No user selection type selected");
				return;
			}

        	var confirmation = $window.confirm('You are about to send ' + $scope.getTotalUsers() + ' email(s). Are you sure?');
        	if(confirmation && $scope.userSelectionType == 'userfilter'){
				$scope.setLoading(true);
				api.email.sendEmail({
					contentid : $scope.emailToSend.contentObjectId,
					emailtype: $scope.emailToSend.emailType,
				}, $scope.emailToSend.users).$promise.then(function(){
					$scope.setLoading(false);
	    			$scope.showToast($scope.toastTypes.Success, "Success!", "Email has been sent (and filtered) successfully!");
				}).catch(function(e){
	    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "With error message (" + e.status + ") " + e.statusText);
		        	$scope.setLoading(false);
				});
			}
			else if(confirmation && $scope.userSelectionType == 'csvuseridlist'){
				$scope.setLoading(true);
				api.email.sendEmailWithIds({
					contentid : $scope.emailToSend.contentObjectId,
					emailtype: $scope.emailToSend.emailType
				}, getUserIdArrayFromTextArea($scope.csvuseridlist)).$promise.then(function(){
					$scope.setLoading(false);
	    			$scope.showToast($scope.toastTypes.Success, "Success!", "Email has been sent (and filtered) successfully!");
				}).catch(function(e){
	    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "With error message (" + e.status + ") " + e.statusText);
		        	$scope.setLoading(false);
				});
			}
	    };

    }]
	return {
		PageController: PageController,
	};
})<commit_msg>Fixed plain text preview on admin email sender.<commit_after>/**
 * Copyright 2015 Alistair Stead
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

	var PageController = ['$scope', 'auth', 'api', '$window', '$rootScope', '$sce', '$timeout', function($scope, auth, api, $window, $rootScope, $sce, $timeout) {
		$scope.isAdminUser = $rootScope.user.role == 'ADMIN';
	    $scope.statistics = null;
	    $scope.setLoading(true);
	    $scope.apiVersion = api.contentVersion.get();
	    $scope.emailTypes = null;

	    $scope.lastContentIDSuccessfullyPreviewed = "";

	    $scope.htmlPreview = "";
	    $scope.plainTextPreview = "";
	    $scope.subjectPreview = "";

	    $scope.emailToSend = {
	    	emailType : -1,
	    	contentObjectId : "",
	    	users: {
		    	ADMIN : false,
		    	EVENT_MANAGER : false,
		    	CONTENT_EDITOR : false,
		    	TEACHER : false,
		    	TESTER : false,
		    	STAFF : false,
		    	STUDENT : false,
			}
	    };

	    $scope.queueSize = "?";
	    var checkTimes = 0;

	    var queueSizeChecker = function(){
	    	api.email.getQueueSize({}).$promise.then(function(result){
	    		var d = new Date();
    			$scope.queueSize = "" + result[0] + " at " + d.toLocaleTimeString();
				$timeout(queueSizeChecker, 5000);
    		}).catch(function(e){
				$scope.showToast($scope.toastTypes.Failure, "Email Queue Size", "Could not get email queue size (" + e.status + ") " + e.statusText);
				clearInterval(timerId);
			});
    	}

	    queueSizeChecker();

		var getUserIdArrayFromTextArea = function(){
			var userids = $scope.csvuseridlist.split(/[\s,]+/).map(function(e) {return parseInt(e);});;
			return userids;
		}

	    $scope.getTotalUsers = function(){
	    	if($scope.userSelectionType == 'csvuseridlist') {
				var userIdList = getUserIdArrayFromTextArea();
				return userIdList.length;
	    	}

	    	var total = 0;
			if($scope.emailToSend.users.ADMIN) {
	    		total += parseInt($scope.statistics.role.ADMIN);
	    	}

	    	if($scope.emailToSend.users.EVENT_MANAGER) {
	    		total += parseInt($scope.statistics.role.EVENT_MANAGER);
	    	}

	    	if($scope.emailToSend.users.CONTENT_EDITOR) {
	    		total += parseInt($scope.statistics.role.CONTENT_EDITOR);
	    	}

	    	if($scope.emailToSend.users.TEACHER) {
	    		total += parseInt($scope.statistics.role.TEACHER);
	    	}

	    	if($scope.emailToSend.users.TESTER) {
	    		total += parseInt($scope.statistics.role.TESTER);
	    	}

	    	if($scope.emailToSend.users.STAFF) {
	    		total += parseInt($scope.statistics.role.STAFF);
	    	}

	    	if($scope.emailToSend.users.STUDENT) {
	    		total += parseInt($scope.statistics.role.STUDENT);
	    	}
	    	return total;
	    }

	    api.statisticsEndpoint.get().$promise.then(function(result){
	        $scope.statistics = result;
	        $scope.setLoading(false);
	    }).catch(function(e){
			$scope.showToast($scope.toastTypes.Failure, "Statistics load failed", "With error message (" + e.status + ") " + e.statusText);
	    });

	    api.email.getPreferences().$promise.then(function(result){
	    	$scope.emailTypes = result;
	    	console.log(result);
	    }).catch(function(e){
			$scope.showToast($scope.toastTypes.Failure, "Preferences load failed", "With error message (" + e.status + ") " + e.statusText);
	    });

	    $scope.loadContentTemplate = function(contentId){
	    	$scope.setLoading(true);
	    	api.email.get({"id" : $scope.emailToSend.contentObjectId}).$promise.then(function(content){
	        	$scope.subjectPreview = content.subject;
	        	$scope.htmlPreview = $sce.trustAsResourceUrl("data:text/html," + encodeURIComponent(content.html));
	        	$scope.plainTextPreview = $sce.trustAsResourceUrl("data:/html," + encodeURIComponent(content.plainText));
	        	$scope.setLoading(false);
	        	$scope.lastContentIDSuccessfullyPreviewed = $scope.emailToSend.contentObjectId;
	    	}).catch(function(e){
    			$scope.showToast($scope.toastTypes.Failure, "Email template failed", "With error message (" + e.status + ") " + e.statusText);
	        	$scope.setLoading(false);
	        	$scope.lastContentIDSuccessfullyPreviewed = "";
	    	});
	    };

        $scope.emailTypeChanged = function(idOfSelectedEmailType){
			$scope.emailToSend.emailType = idOfSelectedEmailType;
		};

	    $scope.validateAndSendEmails = function(){

	    	if(!$scope.emailToSend.emailType || $scope.emailToSend.emailType < 0){
    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must select a type of email");
				return;
	    	}

	    	if($scope.lastContentIDSuccessfullyPreviewed == "" || $scope.emailToSend.contentObjectId != $scope.lastContentIDSuccessfullyPreviewed){
    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must preview the email before sending");
				return;
	    	}

			if($scope.userSelectionType == 'userfilter') {
		    	var usersSelected = false;
		    	for(var key in $scope.emailToSend.users) {
	    			if($scope.emailToSend.users[key]){
	    				usersSelected = true;
	    			}
		    	}

		    	if(!usersSelected){
					$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must select users to send the email to");
					return;
				}
			}
			else if($scope.userSelectionType == 'csvuseridlist') {
				var numbersAndCommas = /[^,0-9]+/;

				if(numbersAndCommas.test($scope.csvuseridlist)){
					$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "Userids field contains invalid characters");
					return;
				}

				var userids = getUserIdArrayFromTextArea($scope.csvuseridlist);

				// Check for NaNs and invalid characters
				for(var i = 0; i < userids.length; i++){
					if(isNaN(userids[i])){
						$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "One of the userIds given evaluates to NaN");
						return;
					}
				}
			}
			else {
				$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "No user selection type selected");
				return;
			}

        	var confirmation = $window.confirm('You are about to send ' + $scope.getTotalUsers() + ' email(s). Are you sure?');
        	if(confirmation && $scope.userSelectionType == 'userfilter'){
				$scope.setLoading(true);
				api.email.sendEmail({
					contentid : $scope.emailToSend.contentObjectId,
					emailtype: $scope.emailToSend.emailType,
				}, $scope.emailToSend.users).$promise.then(function(){
					$scope.setLoading(false);
	    			$scope.showToast($scope.toastTypes.Success, "Success!", "Email has been sent (and filtered) successfully!");
				}).catch(function(e){
	    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "With error message (" + e.status + ") " + e.statusText);
		        	$scope.setLoading(false);
				});
			}
			else if(confirmation && $scope.userSelectionType == 'csvuseridlist'){
				$scope.setLoading(true);
				api.email.sendEmailWithIds({
					contentid : $scope.emailToSend.contentObjectId,
					emailtype: $scope.emailToSend.emailType
				}, getUserIdArrayFromTextArea($scope.csvuseridlist)).$promise.then(function(){
					$scope.setLoading(false);
	    			$scope.showToast($scope.toastTypes.Success, "Success!", "Email has been sent (and filtered) successfully!");
				}).catch(function(e){
	    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "With error message (" + e.status + ") " + e.statusText);
		        	$scope.setLoading(false);
				});
			}
	    };

    }]
	return {
		PageController: PageController,
	};
})<|endoftext|>


<commit_before>// ==========================================================================
// Project:   DropDownMenu
// Copyright:
// License:
// ==========================================================================

/** @class

  @extends SC.Button
  @version 1.0
  @author Mohammed Ashik
*/
sc_require('views/button');

SC.DropDownMenu = SC.ButtonView.extend(
/** @scope SC.DropDownView.prototype */ {

  /** Begin new code */

  /**
    An array of items that will form the menu you want to show.
  */
  objects: [],

  /**
    Binding default for an array of objects
  */
  objectsBindingDefault: SC.Binding.multiple(),

  /**
    If you set this to a non-null value, then the name shown for each
    menu item will be pulled from the object using the named property.
    if this is null, the collection objects themselves will be used.
  */
  nameKey: null,

  /**
   If you set this to a non-null value, then the value of this key will
   be used to sort the objects.  If this is not set, then nameKey will
   be used.
  */
  sortKey: null,

  /**
     Set this to a non-null value to use a key from the passed set of objects
     as the value for the options popup.  If you don't set this, then the
     objects themselves will be used as the value.
  */
  valueKey: null,

  /**
    if true, the empty name will be localized.
  */
  localize: false,

  /**
    if true, it means that no sorting will occur, objects will appear
    in the same order as in the array
  */
  disableSort: NO,

  /** End new code */

  /**
    className - sc-popup-button
  */
  classNames: ['drop-down-menu'],

  /**
    Menu item list
    @Array
  */
  itemList: [],

  /**
    Current selected menu item
    @Array
  */
  currentSelItem: null,

  /**
    To set the menu item index

    @property
  */
  itemIdx: null,

  /**
     Current Value of the dropDown

     @property
  */
  value: null,

  /**
    Binds the button's title to the 'value'

    @private
  */
  titleBinding: '*.value',

  /**
    Default selected value of the drop down.
     This will be the first item from the menu item list.

    @private
  */
  defaultSelVal: null,

  /**
    Prefer matrix to position the drop down menu the button such that the
    selected item for the menu item will appear aligned to the
    icon on the button.

    @property
    @default [ 2, 0. 2]

  */
  preferMatrix: [2, 0, 2],

  /**
    Binds the button's selection state to the menu's visibility.
    @private
  */
  isSelectedBinding: '*menu.isVisibleInWindow',

  /**
    override this method to implement your own sorting of the menu. By
    default, menu items are sorted using the value shown or the sortKey

    @param objects the unsorted array of objects to display.
    @returns sorted array of objects
  */
  sortObjects: function(objects) {
    if(!this.get('disableSort')){
      var nameKey = this.get('sortKey') || this.get('nameKey') ;
      objects = objects.sort(function(a,b) {
        if (nameKey) {
          a = a.get ? a.get(nameKey) : a[nameKey] ;
          b = b.get ? b.get(nameKey) : b[nameKey] ;
        }
        return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
      }) ;
    }
    return objects ;
  },

  /**
    render method
    @private
  */
  render: function(context,firstTime) {

    /** New code */
    var objects = this.get('objects') ;
    objects = this.sortObjects(objects) ;

    //Get the namekey, iconKey and valueKey set by the user
    var nameKey = this.get('nameKey') ;
    var iconKey = this.get('iconKey') ;
    var valueKey = this.get('valueKey') ;

    // get the localization flag.
    var shouldLocalize = this.get('localize');

    //itemList array to set the menu items
    var itemList = [];

    //to set the 'checkbox' property of menu items
    var isChecked = YES ;

    //index for finding the first item in the list
    var idx = 0 ;

    objects.forEach(function(object) {
    if (object) {

      //Get the name value. If value key is not specified convert obj
      //to string
      var name = nameKey ? (object.get ?
        object.get(nameKey) : object[nameKey]) : object.toString() ;

      // localize name if specified.
      if(shouldLocalize)
      {
        name = name.loc();
      }

      // get the value using the valueKey or the object if no valueKey.
      // then convert to a string or use _guid if one of available.
      var value = (valueKey) ? (object.get ?
        object.get(valueKey) : object[valueKey]) : object ;

      //Check if the item is currentSelectedItem or not
      if(name === this.value) {

        //set the itemIdx - To change the prefMatrix accordingly.
        this.set('itemIdx', idx) ;
        isChecked = YES ;
      }
      else {
        isChecked = NO ;
      }

      //Get the icon value
      var icon = iconKey ? object[iconKey] : null ;

      //Set the first item from the list as default selected item
      if (idx === 0) {
        this.set('defaultSelVal',name) ;
        this.set('icon', icon) ;
      }

      //Set the items in the itemList array
      itemList.push({
        title: name,
        icon: icon,
        isEnabled: YES,
        checkbox: isChecked,
        action: this.displaySelectedItem
      });
    }
         idx += 1 ;

        this.set('itemList', itemList) ;
     }, this ) ;

    if(firstTime) {
      var selectionValue = this.get('selectionValue') ;
      if(!SC.none(selectionValue)) {
        this.set('value', selectionValue) ;
      }
      else {
        this.set('value', this.get('defaultSelVal')) ;
      }
    }

    //Set the preference matrix for the menu pane
    this.changeDropDownPreferMatrix(this.itemIdx);

    var menu = this.get('menu') ;
    if(firstTime && menu) {
      menu.createLayer() ;
    }
    sc_super() ;
  },

  /**
    Button action handler
    @param {DOMMouseEvent} evt mouseup event that triggered the action
  */
  action: function( evt )
  {
    var width = this.get('layer').offsetWidth ;
    var currSel = this.get('currentSelItem') ;
    var itemList = this.get('itemList') ;
    var menu = SC.MenuPane.create({

      /**
        Class name - drop-down-menu-item

      */
      classNames: ['drop-down-menu-item'],

      /**
        The menu items are set from the itemList property of DropDownMenu

        @property
      */
      items: itemList,

      /**
        This property enables all the items and makes them selectable.

        @property
      */
      isEnabled: YES,

      preferType: SC.PICKER_MENU,
      itemHeightKey: 'height',
      layout: { width: width },
      contentView: SC.View.extend({
      })
    }) ;

    // no menu to toggle... bail...
    if (!menu) return NO ;
    menu.popup(this, this.preferMatrix) ;
    menu.set('currentSelectedMenuItem', currSel) ;
    return YES;
  },

  /**
    @private

    The button theme will be popup
  */
  theme: 'popup',

  /**
    Render method gets triggered when these properties change
  */
  displayProperties: ['icon', 'title'],

  /**
     Action method for the drop down menu items

  */
  displaySelectedItem: function() {

    //Get MenuPane, currentSelectedMenuItem & menuItemView
    var menuView = this.get('parentView') ;
    var currSel = menuView.get('currentSelectedMenuItem') ;
    var itemViews = menuView.menuItemViews ;

    /**
      Fetch the index of the current selected item
    */
    var itemIdx = 0 ;
    if (currSel && itemViews) {
      itemIdx = itemViews.indexOf(currSel) ;
    }

    //Get the drop down View
    var button = this.getPath('parentView.anchor') ;

    //Set the button title and icon
    button.set('title', this.get('value')) ;
    button.set('icon', this.get('icon')) ;

    //Set value, currentSelectedItem & itemIdx
    button.set('value', this.get('value')) ;
    button.set('currentSelItem', currSel) ;
    button.set('itemIdx', itemIdx) ;
  },

  /**
     Set the "top" attribute in the prefer matrix property which will
     position menu such that the selected item in the menu will be
     place aligned to the item on the button when menu is opened.
  */
  changeDropDownPreferMatrix: function() {
    var pxOffset = 0;
    var preferMatrixAttributeTop = 0;
    var itemIdx = this.get('itemIdx') ;

    //Set the preferMatrixAttribute Top
    if(itemIdx)
      preferMatrixAttributeTop = itemIdx * 20;

    if(this.get('preferMatrix'))
      this.get('preferMatrix')[1] = -preferMatrixAttributeTop + pxOffset;
  }
}) ;

<commit_msg>Made action method private and added further documentation<commit_after>// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @class

  Drop Down Menu has a functionality similar to that of SelectField

  Clicking the DropDownMenu button displays a menu pane with a
  list of items. The selected item will be displayed on the button.

  @extends SC.ButtonView
  @version 1.0
  @author Mohammed Ashik
*/
sc_require('views/button');

SC.DropDownMenu = SC.ButtonView.extend(
/** @scope SC.DropDownMenu.prototype */ {

  /**
    An array of items that will form the menu you want to show.

    @property
  */
  objects: [],

  /**
    Binding default for an array of objects

    @property
  */
  objectsBindingDefault: SC.Binding.multiple(),

  /**
    If you set this to a non-null value, then the name shown for each
    menu item will be pulled from the object using the named property.
    if this is null, the collection objects themselves will be used.

    @property
  */
  nameKey: null,

  /**
    If you set this to a non-null value, then the value of this key will
    be used to sort the objects.  If this is not set, then nameKey will
    be used.

    @property
  */
  sortKey: null,

  /**
     Set this to a non-null value to use a key from the passed set of objects
     as the value for the options popup.  If you don't set this, then the
     objects themselves will be used as the value.

     @property
  */
  valueKey: null,

  /**
    if true, the empty name will be localized.

    @property
  */
  localize: false,

  /**
    if true, it means that no sorting will occur, objects will appear
    in the same order as in the array

    @property
  */
  disableSort: NO,

  /**
    className - sc-popup-button

    @property
  */
  classNames: ['drop-down-menu'],

  /**
    Menu item list

    @property
    @type:{SC.Array}
  */
  itemList: [],

  /**
    Current selected menu item

    @property
  */
  currentSelItem: null,

  /**
    To set the menu item index

    @property
  */
  itemIdx: null,

  /**
     Current Value of the dropDown

     @property
  */
  value: null ,

  /**
    Binds the button's title to the 'value'

    @property
  */
  titleBinding: '*.value',

  /**
    Default selected value of the drop down.
     This will be the first item from the menu item list.

    @private
  */
  _defaultSelVal: null,

  /**
    Prefer matrix to position the drop down menu the button such that the
    selected item for the menu item will appear aligned to the
    icon on the button.

    @property
    @default [ 2, 0. 2]

  */
  preferMatrix: [2, 0, 2],

  /**
    Binds the button's selection state to the menu's visibility.
    @private
  */
  isSelectedBinding: '*menu.isVisibleInWindow',

  /**
    override this method to implement your own sorting of the menu. By
    default, menu items are sorted using the value shown or the sortKey

    @param{SC.Array} objects the unsorted array of objects to display.
    @returns sorted array of objects
  */
  sortObjects: function(objects) {
    if(!this.get('disableSort')){
      var nameKey = this.get('sortKey') || this.get('nameKey') ;
      objects = objects.sort(function(a,b) {
        if (nameKey) {
          a = a.get ? a.get(nameKey) : a[nameKey] ;
          b = b.get ? b.get(nameKey) : b[nameKey] ;
        }
        return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
      }) ;
    }
    return objects ;
  },

  /**
    render method

    @private
  */
  render: function(context,firstTime) {
    var objects = this.get('objects') ;
    objects = this.sortObjects(objects) ;

    //Get the namekey, iconKey and valueKey set by the user
    var nameKey = this.get('nameKey') ;
    var iconKey = this.get('iconKey') ;
    var valueKey = this.get('valueKey') ;

    // get the localization flag.
    var shouldLocalize = this.get('localize');

    //itemList array to set the menu items
    var itemList = [];

    //to set the 'checkbox' property of menu items
    var isChecked = YES ;

    //index for finding the first item in the list
    var idx = 0 ;

    objects.forEach(function(object) {
    if (object) {

      //Get the name value. If value key is not specified convert obj
      //to string
      var name = nameKey ? (object.get ?
        object.get(nameKey) : object[nameKey]) : object.toString() ;

      // localize name if specified.
      if(shouldLocalize)
      {
        name = name.loc();
      }

      // get the value using the valueKey or the object if no valueKey.
      // then convert to a string or use _guid if one of available.
      var value = (valueKey) ? (object.get ?
        object.get(valueKey) : object[valueKey]) : object ;

      //Check if the item is currentSelectedItem or not
      if(name === this.value) {

        //set the itemIdx - To change the prefMatrix accordingly.
        this.set('itemIdx', idx) ;
        isChecked = YES ;
      }
      else {
        isChecked = NO ;
      }

      //Get the icon value
      var icon = iconKey ? (object.get ?
        object.get(iconKey) : object[iconKey]) : null ;
      if (SC.none(object[iconKey])) {
         icon = null ;
      }

      //Set the first item from the list as default selected item
      if (idx === 0) {
        this._defaultSelVal = name ;
        this.set('icon', icon) ;
      }

      //Set the items in the itemList array
      itemList.push({
        title: name,
        icon: icon,
        isEnabled: YES,
        checkbox: isChecked,
        action: this.displaySelectedItem
      });
    }
         idx += 1 ;

        this.set('itemList', itemList) ;
     }, this ) ;

    if(firstTime) {
      var selectionValue = this.get('selectionValue') ;
      if(!SC.none(selectionValue)) {
        this.set('value', selectionValue) ;
      }
      else {
        this.set('value', this._defaultSelVal) ;
      }
    }

    //Set the preference matrix for the menu pane
    this.changeDropDownPreferMatrix(this.itemIdx);

    var menu = this.get('menu') ;
    if(firstTime && menu) {
      menu.createLayer() ;
    }
    sc_super() ;
  },

  /**
    Button action handler

    @private
    @param {DOMMouseEvent} evt mouseup event that triggered the action
  */
  _action: function( evt )
  {
    var width = this.get('layer').offsetWidth ;
    var currSel = this.get('currentSelItem') ;
    var itemList = this.get('itemList') ;
    var menu = SC.MenuPane.create({

      /**
        Class name - drop-down-menu-item

      */
      classNames: ['drop-down-menu-item'],

      /**
        The menu items are set from the itemList property of DropDownMenu

        @property
      */
      items: itemList,

      /**
        This property enables all the items and makes them selectable.

        @property
      */
      isEnabled: YES,

      preferType: SC.PICKER_MENU,
      itemHeightKey: 'height',
      layout: { width: width },
      contentView: SC.View.extend({
      })
    }) ;

    // no menu to toggle... bail...
    if (!menu) return NO ;
    menu.popup(this, this.preferMatrix) ;
    menu.set('currentSelectedMenuItem', currSel) ;
    return YES;
  },

  /**
    @private

    The button theme will be popup
  */
  theme: 'popup',

  /**
    Render method gets triggered when these properties change

    @property
    @type{SC.Array}
  */
  displayProperties: ['icon', 'title'],

  /**
     Action method for the drop down menu items

  */
  displaySelectedItem: function() {

    //Get MenuPane, currentSelectedMenuItem & menuItemView
    var menuView = this.get('parentView') ;
    var currSel = menuView.get('currentSelectedMenuItem') ;
    var itemViews = menuView.menuItemViews ;

    /**
      Fetch the index of the current selected item
    */
    var itemIdx = 0 ;
    if (currSel && itemViews) {
      itemIdx = itemViews.indexOf(currSel) ;
    }

    //Get the drop down View
    var button = this.getPath('parentView.anchor') ;

    //Set the button title and icon
    button.set('title', this.get('value')) ;
    button.set('icon', this.get('icon')) ;

    //Set value, currentSelectedItem & itemIdx
    button.set('value', this.get('value')) ;
    button.set('currentSelItem', currSel) ;
    button.set('itemIdx', itemIdx) ;
  },

  /**
     Set the "top" attribute in the prefer matrix property which will
     position menu such that the selected item in the menu will be
     place aligned to the item on the button when menu is opened.
  */
  changeDropDownPreferMatrix: function() {
    var preferMatrixAttributeTop = 0;
    var itemIdx = this.get('itemIdx') ;

    //Set the preferMatrixAttribute Top
    if(itemIdx)
      preferMatrixAttributeTop = itemIdx * 20;

    if(this.get('preferMatrix'))
      this.get('preferMatrix')[1] = -preferMatrixAttributeTop ;
  }
}) ;

<|endoftext|>