<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)
// ==========================================================================
sc_require('views/button') ;
sc_require('views/separator') ;

// Constants
SC.BENCHMARK_MENU_ITEM_RENDER = YES ;

/**
  @class SC.MenuItemView
  @extends SC.ButtonView
  @since SproutCore 1.0
*/
SC.MenuItemView = SC.ButtonView.extend( SC.ContentDisplay,
/** @scope SC.MenuItemView.prototype */{

  classNames: ['sc-menu-item'],
  tagName: 'div',

  /**
    This provides the parentPane for the current MenuItemView
  */
  parentPane: null,

  /**
    @private
    @property
    @type {Boolean}
  */
  acceptsFirstResponder: YES,

  // ..........................................................
  // KEY PROPERTIES
  //
  /**
    The content object the menu view will display.

    @type Object
  */
  content: null,

  /**
    This returns true if the child view is a menu list view.
    This property can be over written to have other child views as well.

    @type Boolean
  */
  isSubMenuViewVisible: null,

  /**
    This will return true if the menu item is a separator.

    @type Boolean
  */
  isSeparator: NO,

  /**
    (displayDelegate) The name of the property used for label itself
    If null, then the content object itself will be used.

    @readOnly
    @type String
  */
  contentValueKey: null,

  /**
    (displayDelegate) The name of the property used to determine if the menu
    item is a branch or leaf (i.e. if the branch arow should be displayed to
    the right edge.)
    If this is null, then the branch arrow will be collapsed.

    @readOnly
    @type String
  */
  contentIsBranchKey: null,

  /**
    The name of the property which will set the image for the short cut keys

    @readOnly
    @type String
  */
  shortCutKey: null,

  /**
    The name of the property which will set the icon image for the menu item.

    @readOnly
    @type String
  */
  contentIconKey: null,

  /**
    The name of the property which will set the checkbox image for the menu
    item.

    @readOnly
    @type String
  */
  contentCheckboxKey: null,

  /**
    The name of the property which will set the checkbox image for the menu
    item.

    @readOnly
    @type String
  */
  contentActionKey: null,

  /**
    The name of the property which will set the checkbox state

    @type Boolean
  */
  isCheckboxChecked: NO,

  /**
    Describes the width of the menu item
    Default it to 100

    @type Integer
  */
  itemWidth: 100,

  /**
    Describes the height of the menu item
    Default it to 20

    @type Integer
  */
  itemHeight: 20,

  /**
    Property specifies which menu item the mouseover stops at

    @type Boolean
  */
  isSelected : NO,

  /**
    Sub Menu Items
    If this is null then there is no branching

    @type MenuPane
  */
  subMenu: null,

  /**
    This property specifies whether this menu item is currently in focus

    @type Boolean
  */
  hasMouseExited: NO,

  /**
    Anchor for the Parent Menu of which the Menu Item is part of

    @type ButtonView/MenuItemView
  */
  anchor: null,

  /**
    This will hold the properties that can trigger a change in the diplay
  */
  displayProperties: ['contentValueKey', 'contentIconKey', 'shortCutKey',
                  'contentIsBranchKey','isCheckboxChecked','itemHeight',
                   'subMenu','isEnabled','content'],
  contentDisplayProperties: 'title value icon separator action checkbox shortcut branchItem subMenu'.w(),
  /**
    Fills the passed html-array with strings that can be joined to form the
    innerHTML of the receiver element.  Also populates an array of classNames
    to set on the outer element.

    @param {SC.RenderContext} context
    @param {Boolean} firstTime
    @returns {void}
  */
  render: function(context, firstTime) {
    var bkey ;
    if (SC.BENCHMARK_MENU_ITEM_RENDER) {
      bkey = '%@.render'.fmt(this) ;
      SC.Benchmark.start(bkey) ;
    }
    var content = this.get('content') ;
    var del = this.displayDelegate ;
    var key, val ;
    var ic ;
    var menu = this.parentMenu() ;
    var itemWidth = this.get('itemWidth') || menu.layout.width ;
    var itemHeight = this.get('itemHeight') || 20 ;
    this.set('itemWidth',itemWidth) ;
    this.set('itemHeight',itemHeight) ;

    if(!this.get('isEnabled')) context.addClass('disabled') ;
    //handle separator
    ic = context.begin('a').attr('href', 'javascript: ;') ;
    key = this.getDelegateProperty('isSeparatorKey', del) ;
    val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
    if (val) {
      ic.push("") ;
      context.addClass('disabled') ;
    } else {
      // handle checkbox
      key = this.getDelegateProperty('contentCheckboxKey', del) ;
      if (key) {
        val = content ? (content.get ? content.get(key) : content[key]) : NO ;
        if (val) {
          ic.begin('div').addClass('checkbox').end() ;
        }
      }

      // handle image -- always invoke
      key = this.getDelegateProperty('contentIconKey', del) ;
      val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
      if(val && SC.typeOf(val) !== SC.T_STRING) val = val.toString() ;
      if(val) this.renderImage(ic, val) ;

      // handle label -- always invoke
      key = this.getDelegateProperty('contentValueKey', del) ;
      val = (key && content) ? (content.get ? content.get(key) : content[key]) : content ;
      if (val && SC.typeOf(val) !== SC.T_STRING) val = val.toString() ;
      this.renderLabel(ic, val||'') ;

      // handle branch
      key = this.getDelegateProperty('contentIsBranchKey', del) ;
      val = (key && content) ? (content.get ? content.get(key) : content[key]) : NO ;
      if (val) {
        this.renderBranch(ic, val) ;
        ic.addClass('has-branch') ;
      } else { // handle action

        key = this.getDelegateProperty('action', del) ;
        val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
        if (val && isNaN(val)) this.set('action', val) ;

        key = this.getDelegateProperty('target', del) ;
        val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
        if (val && isNaN(val)) this.set('target', val) ;

        // handle short cut keys
        if (this.getDelegateProperty('shortCutKey', del)) {
          key = this.getDelegateProperty('shortCutKey', del) ;
          val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
          if (val) {
            this.renderShortcut(ic, val) ;
            ic.addClass('shortcutkey') ;
          }
        }
      }
    }
    ic.end() ;
    if (SC.BENCHMARK_MENU_ITEM_RENDER) SC.Benchmark.end(bkey) ;
  },

  /**
   Generates the image used to represent the image icon. override this to
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {String} the source path of the image
   @returns {void}
  */
  renderImage: function(context, image) {
    // get a class name and url to include if relevant

    var url, className ;
    if (image && SC.ImageView.valueIsUrl(image)) {
      url = image ;
      className = '' ;
    } else {
      className = image ;
      url = sc_static('blank.gif') ;
    }
    // generate the img element...
    context.begin('img').addClass('image').addClass(className).attr('src', url).end() ;
  },

  /**
   Generates the label used to represent the menu item. override this to
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {String} menu item name
   @returns {void}
  */

  renderLabel: function(context, label) {
    context.push(""+label+"") ;
  },

  /**
   Generates the string used to represent the branch arrow. override this to
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {Boolean} hasBranch YES if the item has a branch
   @returns {void}
  */

  renderBranch: function(context, hasBranch) {

    var a = '>' ;
    var url = sc_static('blank.gif') ;
    context.push(''+a+'') ;
  },

  /**
   Generates the string used to represent the short cut keys. override this to
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {String} the shortcut key string to be displayed with menu item name
   @returns {void}
  */
  renderShortcut: function(context, shortcut) {
    context.push('' + shortcut + '') ;
  },

  /**
    This method is used to fetch the Menu Item View to which the
    Parent Menu Pane is anchored
    to

    @param {}
    @returns MenuPane
  */
  getAnchor: function() {
    var anchor = this.get('anchor') ;
    if(anchor && anchor.kindOf && anchor.kindOf(SC.MenuItemView)) return anchor ;
    return null ;
  },

  isCurrent: NO,

  /**
    This method checks if the menu item is a separator.

    @param {}
    @returns Boolean
  */
  isSeparator: function() {
    var content = this.get('content') ;
    var del = this.displayDelegate ;
    var key = this.getDelegateProperty('isSeparatorKey', del) ;
    var val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
    if (val) return YES ;
    return NO ;
  },

  /**
    Checks if a menu is a sub menu, during branching.

    @param {}
    @returns MenuPane
  */
  isSubMenuAMenuPane: function() {
    var content = this.get('content') ;
    var subMenu = content.get('subMenu') ;
    if(subMenu && subMenu.kindOf(SC.MenuPane)) return subMenu ;
    return NO ;
  },

  /**
    This method will check whether the current Menu Item is still
    selected and then create a submenu accordignly.

    @param {}
    @returns void
  */
  branching: function() {
    if(this.get('hasMouseExited')) {
      this.set('hasMouseExited',NO) ;
      return ;
    }
      this.createSubMenu() ;
  },

  /**
    This method will remove the focus of the current selected menu item.

    @param {}
  */
  loseFocus: function() {
    if(!this.isSubMenuAMenuPane()) {
      this.set('hasMouseExited',YES) ;
      //this.set('isSelected',NO) ;
      this.$().removeClass('focus') ;
    }
  },

  /**
    This method will create the sub Menu with the current Menu Item as anchor

    @param {}
    @returns void
  */
  createSubMenu: function() {
    var subMenu = this.isSubMenuAMenuPane() ;
    if(subMenu) {
      subMenu.set('anchor', this) ;
      subMenu.popup(this,[0,0,0]) ;
      var context = SC.RenderContext(this) ;
      context = context.begin(subMenu.get('tagName')) ;
      subMenu.prepareContext(context, YES) ;
      context = context.end() ;
      var menuItemViews = subMenu.get('menuItemViews') ;
      if(menuItemViews && menuItemViews.length>0) {
        subMenu.becomeKeyPane();
      }
    }
  },

  parentMenu: function() {
    return this.get('parentPane') ;
  },

  //..........................................
  //Mouse Events Handling
  //..........................................

  mouseUp: function(evt) {
    if (!this.get('isEnabled') || this.get('hasMouseExited')) {
      this.set('hasMouseExited',NO) ;
      return YES ;
    }
    this.set('hasMouseExited',NO) ;
    //this.set('isSelected',NO) ;
    var key = this.get('contentCheckboxKey') ;
    var content = this.get('content') ;
    if (key) {
      if (content && content.get(key)) {
        this.$('.checkbox').setClass('inactive', YES) ;
        content.set(key, NO) ;
      } else if( content.get(key)!== undefined ) {
        this.$('.checkbox').removeClass('inactive') ;
        content.set(key, YES) ;
      }
    }
    this._action(evt) ;
    var anchor = this.getAnchor() ;
    if(anchor) {
      anchor.mouseUp(evt) ;
    }
    else {
      this.resignFirstResponder() ;
    }
    this.closeParent() ;
    return YES ;
  },

  /** @private*/
  mouseDown: function(evt) {
    return YES ;
  },

  /** @private
    This has been over ridden from button view to prevent calling of render
    method (When isActive property is changed).
    Also based on whether the menu item has a sub Branch we create a sub Menu

    @returns Boolean
  */
  mouseEntered: function(evt) {
    var parentMenu = this.parentMenu() ;
    this.set('hasMouseExited', NO) ;
    if(parentMenu) {
      parentMenu.becomeKeyPane() ;
    }
    if (!this.get('isEnabled') && !this.isSeparator()) return YES ;
    //this.set('isSelected',YES) ;
    var key = this.get('contentIsBranchKey') ;
    if(key) {
      var content = this.get('content') ;
      var val = (key && content) ? (content.get ? content.get(key) : content[key]) : NO ;
      if(val) this.invokeLater(this.branching(),100) ;
    }
    this.becomeFirstResponder() ;
	  return YES ;
  },

  /** @private
    Set the focus based on whether the current Menu item is selected or not.

    @returns Boolean
  */
  mouseExited: function(evt) {
    this.loseFocus() ;
    var parentMenu = this.parentMenu() ;
    if(parentMenu) {
      parentMenu.set('previousSelectedMenuItem', this) ;
      this.resignFirstResponder() ;
    }
    return YES ;
  },

  /** @private
    Call the moveUp function on the parent Menu

    @returns Boolean
  */
  moveUp: function(sender,evt) {
    var menu = this.parentMenu() ;
    if(menu) {
      menu.moveUp(this) ;
    }
    return YES ;
  },

  /** @private
    Call the moveDown function on the parent Menu

    @returns Boolean
  */
  moveDown: function(sender,evt) {
    var menu = this.parentMenu() ;
    if(menu) {
      menu.moveDown(this) ;
    }
    return YES ;
  },

  /** @private
    Call the function to create a branch

    @returns Boolean
  */
  moveRight: function(sender,evt) {
    this.createSubMenu() ;
    return YES ;
  },

  /** @private*/
  keyDown: function(evt) {
    return this.interpretKeyEvents(evt) ;
  },

  /** @private*/
  keyUp: function(evt) {
    return YES ;
  },

  /** @private*/
  cancel: function(evt) {
    this.loseFocus() ;
    return YES ;
  },

  /** @private*/
  didBecomeFirstResponder: function(responder) {
    if (responder !== this) return;
    if(!this.isSeparator()) {
      this.$().addClass('focus') ;
    }
    var parentMenu = this.parentMenu() ;
    if(parentMenu) {
      parentMenu.set('currentSelectedMenuItem', this) ;
    }
  },

  /** @private*/
  willLoseFirstResponder: function(responder) {
    if (responder !== this) return;
    this.$().removeClass('focus') ;
    var parentMenu = this.parentMenu() ;
    if(parentMenu) {
      parentMenu.set('currentSelectedMenuItem', null) ;
      parentMenu.set('previousSelectedMenuItem', this) ;
    }
  },

  /** @private*/
  insertNewline: function(sender, evt) {
    this.mouseUp(evt) ;
  },

  /**
    Close the parent Menu and remove the focus of the current Selected
    Menu Item

    @returns void
  */
  closeParent: function() {
    this.$().removeClass('focus') ;
    var menu = this.parentMenu() ;
    if(menu) {
      menu.remove() ;
    }
  },

  /** @private*/
  clickInside: function(frame, evt) {
    return SC.pointInRect({ x: evt.pageX, y: evt.pageY }, frame) ;
  }

}) ;
<commit_msg>Remove isSelected property as it is not required<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)
// ==========================================================================
sc_require('views/button') ;
sc_require('views/separator') ;

// Constants
SC.BENCHMARK_MENU_ITEM_RENDER = YES ;

/**
  @class SC.MenuItemView
  @extends SC.ButtonView
  @since SproutCore 1.0
*/
SC.MenuItemView = SC.ButtonView.extend( SC.ContentDisplay,
/** @scope SC.MenuItemView.prototype */{

  classNames: ['sc-menu-item'],
  tagName: 'div',

  /**
    This provides the parentPane for the current MenuItemView
  */
  parentPane: null,

  /**
    @private
    @property
    @type {Boolean}
  */
  acceptsFirstResponder: YES,

  // ..........................................................
  // KEY PROPERTIES
  //
  /**
    The content object the menu view will display.

    @type Object
  */
  content: null,

  /**
    This returns true if the child view is a menu list view.
    This property can be over written to have other child views as well.

    @type Boolean
  */
  isSubMenuViewVisible: null,

  /**
    This will return true if the menu item is a separator.

    @type Boolean
  */
  isSeparator: NO,

  /**
    (displayDelegate) The name of the property used for label itself
    If null, then the content object itself will be used.

    @readOnly
    @type String
  */
  contentValueKey: null,

  /**
    (displayDelegate) The name of the property used to determine if the menu
    item is a branch or leaf (i.e. if the branch arow should be displayed to
    the right edge.)
    If this is null, then the branch arrow will be collapsed.

    @readOnly
    @type String
  */
  contentIsBranchKey: null,

  /**
    The name of the property which will set the image for the short cut keys

    @readOnly
    @type String
  */
  shortCutKey: null,

  /**
    The name of the property which will set the icon image for the menu item.

    @readOnly
    @type String
  */
  contentIconKey: null,

  /**
    The name of the property which will set the checkbox image for the menu
    item.

    @readOnly
    @type String
  */
  contentCheckboxKey: null,

  /**
    The name of the property which will set the checkbox image for the menu
    item.

    @readOnly
    @type String
  */
  contentActionKey: null,

  /**
    The name of the property which will set the checkbox state

    @type Boolean
  */
  isCheckboxChecked: NO,

  /**
    Describes the width of the menu item
    Default it to 100

    @type Integer
  */
  itemWidth: 100,

  /**
    Describes the height of the menu item
    Default it to 20

    @type Integer
  */
  itemHeight: 20,

  /**
    Sub Menu Items
    If this is null then there is no branching

    @type MenuPane
  */
  subMenu: null,

  /**
    This property specifies whether this menu item is currently in focus

    @type Boolean
  */
  hasMouseExited: NO,

  /**
    Anchor for the Parent Menu of which the Menu Item is part of

    @type ButtonView/MenuItemView
  */
  anchor: null,

  /**
    This will hold the properties that can trigger a change in the diplay
  */
  displayProperties: ['contentValueKey', 'contentIconKey', 'shortCutKey',
                  'contentIsBranchKey','isCheckboxChecked','itemHeight',
                   'subMenu','isEnabled','content'],
  contentDisplayProperties: 'title value icon separator action checkbox shortcut branchItem subMenu'.w(),
  /**
    Fills the passed html-array with strings that can be joined to form the
    innerHTML of the receiver element.  Also populates an array of classNames
    to set on the outer element.

    @param {SC.RenderContext} context
    @param {Boolean} firstTime
    @returns {void}
  */
  render: function(context, firstTime) {
    var bkey ;
    if (SC.BENCHMARK_MENU_ITEM_RENDER) {
      bkey = '%@.render'.fmt(this) ;
      SC.Benchmark.start(bkey) ;
    }
    var content = this.get('content') ;
    var del = this.displayDelegate ;
    var key, val ;
    var ic ;
    var menu = this.parentMenu() ;
    var itemWidth = this.get('itemWidth') || menu.layout.width ;
    var itemHeight = this.get('itemHeight') || 20 ;
    this.set('itemWidth',itemWidth) ;
    this.set('itemHeight',itemHeight) ;

    if(!this.get('isEnabled')) context.addClass('disabled') ;
    //handle separator
    ic = context.begin('a').attr('href', 'javascript: ;') ;
    key = this.getDelegateProperty('isSeparatorKey', del) ;
    val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
    if (val) {
      ic.push("") ;
      context.addClass('disabled') ;
    } else {
      // handle checkbox
      key = this.getDelegateProperty('contentCheckboxKey', del) ;
      if (key) {
        val = content ? (content.get ? content.get(key) : content[key]) : NO ;
        if (val) {
          ic.begin('div').addClass('checkbox').end() ;
        }
      }

      // handle image -- always invoke
      key = this.getDelegateProperty('contentIconKey', del) ;
      val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
      if(val && SC.typeOf(val) !== SC.T_STRING) val = val.toString() ;
      if(val) this.renderImage(ic, val) ;

      // handle label -- always invoke
      key = this.getDelegateProperty('contentValueKey', del) ;
      val = (key && content) ? (content.get ? content.get(key) : content[key]) : content ;
      if (val && SC.typeOf(val) !== SC.T_STRING) val = val.toString() ;
      this.renderLabel(ic, val||'') ;

      // handle branch
      key = this.getDelegateProperty('contentIsBranchKey', del) ;
      val = (key && content) ? (content.get ? content.get(key) : content[key]) : NO ;
      if (val) {
        this.renderBranch(ic, val) ;
        ic.addClass('has-branch') ;
      } else { // handle action

        key = this.getDelegateProperty('action', del) ;
        val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
        if (val && isNaN(val)) this.set('action', val) ;

        key = this.getDelegateProperty('target', del) ;
        val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
        if (val && isNaN(val)) this.set('target', val) ;

        // handle short cut keys
        if (this.getDelegateProperty('shortCutKey', del)) {
          key = this.getDelegateProperty('shortCutKey', del) ;
          val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
          if (val) {
            this.renderShortcut(ic, val) ;
            ic.addClass('shortcutkey') ;
          }
        }
      }
    }
    ic.end() ;
    if (SC.BENCHMARK_MENU_ITEM_RENDER) SC.Benchmark.end(bkey) ;
  },

  /**
   Generates the image used to represent the image icon. override this to
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {String} the source path of the image
   @returns {void}
  */
  renderImage: function(context, image) {
    // get a class name and url to include if relevant

    var url, className ;
    if (image && SC.ImageView.valueIsUrl(image)) {
      url = image ;
      className = '' ;
    } else {
      className = image ;
      url = sc_static('blank.gif') ;
    }
    // generate the img element...
    context.begin('img').addClass('image').addClass(className).attr('src', url).end() ;
  },

  /**
   Generates the label used to represent the menu item. override this to
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {String} menu item name
   @returns {void}
  */

  renderLabel: function(context, label) {
    context.push(""+label+"") ;
  },

  /**
   Generates the string used to represent the branch arrow. override this to
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {Boolean} hasBranch YES if the item has a branch
   @returns {void}
  */

  renderBranch: function(context, hasBranch) {

    var a = '>' ;
    var url = sc_static('blank.gif') ;
    context.push(''+a+'') ;
  },

  /**
   Generates the string used to represent the short cut keys. override this to
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {String} the shortcut key string to be displayed with menu item name
   @returns {void}
  */
  renderShortcut: function(context, shortcut) {
    context.push('' + shortcut + '') ;
  },

  /**
    This method is used to fetch the Menu Item View to which the
    Parent Menu Pane is anchored
    to

    @param {}
    @returns MenuPane
  */
  getAnchor: function() {
    var anchor = this.get('anchor') ;
    if(anchor && anchor.kindOf && anchor.kindOf(SC.MenuItemView)) return anchor ;
    return null ;
  },

  isCurrent: NO,

  /**
    This method checks if the menu item is a separator.

    @param {}
    @returns Boolean
  */
  isSeparator: function() {
    var content = this.get('content') ;
    var del = this.displayDelegate ;
    var key = this.getDelegateProperty('isSeparatorKey', del) ;
    var val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
    if (val) return YES ;
    return NO ;
  },

  /**
    Checks if a menu is a sub menu, during branching.

    @param {}
    @returns MenuPane
  */
  isSubMenuAMenuPane: function() {
    var content = this.get('content') ;
    var subMenu = content.get('subMenu') ;
    if(subMenu && subMenu.kindOf(SC.MenuPane)) return subMenu ;
    return NO ;
  },

  /**
    This method will check whether the current Menu Item is still
    selected and then create a submenu accordignly.

    @param {}
    @returns void
  */
  branching: function() {
    if(this.get('hasMouseExited')) {
      this.set('hasMouseExited',NO) ;
      return ;
    }
      this.createSubMenu() ;
  },

  /**
    This method will remove the focus of the current selected menu item.

    @param {}
  */
  loseFocus: function() {
    if(!this.isSubMenuAMenuPane()) {
      this.set('hasMouseExited',YES) ;
      this.$().removeClass('focus') ;
    }
  },

  /**
    This method will create the sub Menu with the current Menu Item as anchor

    @param {}
    @returns void
  */
  createSubMenu: function() {
    var subMenu = this.isSubMenuAMenuPane() ;
    if(subMenu) {
      subMenu.set('anchor', this) ;
      subMenu.popup(this,[0,0,0]) ;
      var context = SC.RenderContext(this) ;
      context = context.begin(subMenu.get('tagName')) ;
      subMenu.prepareContext(context, YES) ;
      context = context.end() ;
      var menuItemViews = subMenu.get('menuItemViews') ;
      if(menuItemViews && menuItemViews.length>0) {
        subMenu.becomeKeyPane();
      }
    }
  },

  parentMenu: function() {
    return this.get('parentPane') ;
  },

  //..........................................
  //Mouse Events Handling
  //..........................................

  mouseUp: function(evt) {
    if (!this.get('isEnabled') || this.get('hasMouseExited')) {
      this.set('hasMouseExited',NO) ;
      return YES ;
    }
    this.set('hasMouseExited',NO) ;
    var key = this.get('contentCheckboxKey') ;
    var content = this.get('content') ;
    if (key) {
      if (content && content.get(key)) {
        this.$('.checkbox').setClass('inactive', YES) ;
        content.set(key, NO) ;
      } else if( content.get(key)!== undefined ) {
        this.$('.checkbox').removeClass('inactive') ;
        content.set(key, YES) ;
      }
    }
    this._action(evt) ;
    var anchor = this.getAnchor() ;
    if(anchor) {
      anchor.mouseUp(evt) ;
    }
    else {
      this.resignFirstResponder() ;
    }
    this.closeParent() ;
    return YES ;
  },

  /** @private*/
  mouseDown: function(evt) {
    return YES ;
  },

  /** @private
    This has been over ridden from button view to prevent calling of render
    method (When isActive property is changed).
    Also based on whether the menu item has a sub Branch we create a sub Menu

    @returns Boolean
  */
  mouseEntered: function(evt) {
    var parentMenu = this.parentMenu() ;
    this.set('hasMouseExited', NO) ;
    if(parentMenu) {
      parentMenu.becomeKeyPane() ;
    }
    if (!this.get('isEnabled') && !this.isSeparator()) return YES ;
    var key = this.get('contentIsBranchKey') ;
    if(key) {
      var content = this.get('content') ;
      var val = (key && content) ? (content.get ? content.get(key) : content[key]) : NO ;
      if(val) this.invokeLater(this.branching(),100) ;
    }
    this.becomeFirstResponder() ;
	  return YES ;
  },

  /** @private
    Set the focus based on whether the current Menu item is selected or not.

    @returns Boolean
  */
  mouseExited: function(evt) {
    this.loseFocus() ;
    var parentMenu = this.parentMenu() ;
    if(parentMenu) {
      parentMenu.set('previousSelectedMenuItem', this) ;
      this.resignFirstResponder() ;
    }
    return YES ;
  },

  /** @private
    Call the moveUp function on the parent Menu

    @returns Boolean
  */
  moveUp: function(sender,evt) {
    var menu = this.parentMenu() ;
    if(menu) {
      menu.moveUp(this) ;
    }
    return YES ;
  },

  /** @private
    Call the moveDown function on the parent Menu

    @returns Boolean
  */
  moveDown: function(sender,evt) {
    var menu = this.parentMenu() ;
    if(menu) {
      menu.moveDown(this) ;
    }
    return YES ;
  },

  /** @private
    Call the function to create a branch

    @returns Boolean
  */
  moveRight: function(sender,evt) {
    this.createSubMenu() ;
    return YES ;
  },

  /** @private*/
  keyDown: function(evt) {
    return this.interpretKeyEvents(evt) ;
  },

  /** @private*/
  keyUp: function(evt) {
    return YES ;
  },

  /** @private*/
  cancel: function(evt) {
    this.loseFocus() ;
    return YES ;
  },

  /** @private*/
  didBecomeFirstResponder: function(responder) {
    if (responder !== this) return;
    if(!this.isSeparator()) {
      this.$().addClass('focus') ;
    }
    var parentMenu = this.parentMenu() ;
    if(parentMenu) {
      parentMenu.set('currentSelectedMenuItem', this) ;
    }
  },

  /** @private*/
  willLoseFirstResponder: function(responder) {
    if (responder !== this) return;
    this.$().removeClass('focus') ;
    var parentMenu = this.parentMenu() ;
    if(parentMenu) {
      parentMenu.set('currentSelectedMenuItem', null) ;
      parentMenu.set('previousSelectedMenuItem', this) ;
    }
  },

  /** @private*/
  insertNewline: function(sender, evt) {
    this.mouseUp(evt) ;
  },

  /**
    Close the parent Menu and remove the focus of the current Selected
    Menu Item

    @returns void
  */
  closeParent: function() {
    this.$().removeClass('focus') ;
    var menu = this.parentMenu() ;
    if(menu) {
      menu.remove() ;
    }
  },

  /** @private*/
  clickInside: function(frame, evt) {
    return SC.pointInRect({ x: evt.pageX, y: evt.pageY }, frame) ;
  }

}) ;
<|endoftext|>


<commit_before>/**
 * QrCodeController
 *
 * @description :: Server-side logic for managing users
 * @help        :: See http://sailsjs.org/#!/documentation/concepts/Controllers
 */

module.exports = {
    generate: function(req,res){
        User.findOne(1).exec(function (err, user){
          if (!err) {
            var qr = {owner:user};
            QrCode.create(qr).exec(function(err, qrCode) {
                if(!err){
                    return res.json(qr);
                }else{
                    console.log(err);
                    res.status(404);
                    return res.json({error:"Could not generate QR Code."});
                }
            });
          }else{
            console.log(err);
            res.status(404);
            return res.json({error:"Could not find user."});
          }
        });
    },
    validate: function(req,res){
        var hash = req.params.hash;
        QrCode.findOne({hash:hash}).exec(function(err, qrCode) {
            if(!err && qrCode){
                var followerId = 2;
                User.findOne(followerId).exec(function(err, follower) {
                    if(!err){
                        follower.unlock(qrCode.owner);
                        QrCode.destroy(qrCode).exec(function(){
                            User.findOne(qrCode.owner).exec(function(err, owner) {
                                if(!err){
                                    owner.addFollower(followerId);
                                    return res.json({message:"User unlocked "+owner.name});
                                 }else{
                                    console.log(err);
                                    res.status(404);
                                    return res.json({error:"Could not find owner User."});
                                }
                            });
                        });
                     }else{
                        console.log(err);
                        res.status(404);
                        return res.json({error:"Could not find follower User."});
                    }
                });
            }else{
                if(err){
                    console.log(err);
                }
                res.status(404);
                return res.json({error:"Could not find QR Code."});
            }
        });
    }
};
<commit_msg>change: render the qr code generated<commit_after>/**
 * QrCodeController
 *
 * @description :: Server-side logic for managing users
 * @help        :: See http://sailsjs.org/#!/documentation/concepts/Controllers
 */

module.exports = {
    generate: function(req,res){
        User.findOne(1).exec(function (err, user){
          if (!err) {
            var qr = {owner:user};
            QrCode.create(qr).exec(function(err, qrCode) {
                if(!err){
                    return res.json(qrCode);
                }else{
                    console.log(err);
                    res.status(404);
                    return res.json({error:"Could not generate QR Code."});
                }
            });
          }else{
            console.log(err);
            res.status(404);
            return res.json({error:"Could not find user."});
          }
        });
    },
    validate: function(req,res){
        var hash = req.params.hash;
        QrCode.findOne({hash:hash}).exec(function(err, qrCode) {
            if(!err && qrCode){
                var followerId = 2;
                User.findOne(followerId).exec(function(err, follower) {
                    if(!err){
                        follower.unlock(qrCode.owner);
                        QrCode.destroy(qrCode).exec(function(){
                            User.findOne(qrCode.owner).exec(function(err, owner) {
                                if(!err){
                                    owner.addFollower(followerId);
                                    return res.json({message:"User unlocked "+owner.name});
                                 }else{
                                    console.log(err);
                                    res.status(404);
                                    return res.json({error:"Could not find owner User."});
                                }
                            });
                        });
                     }else{
                        console.log(err);
                        res.status(404);
                        return res.json({error:"Could not find follower User."});
                    }
                });
            }else{
                if(err){
                    console.log(err);
                }
                res.status(404);
                return res.json({error:"Could not find QR Code."});
            }
        });
    }
};
<|endoftext|>


<commit_before>var common = {};

common.INPUT_TYPES = new Set(["INPUT", "TEXTAREA"]);

common._request =
function _request(method, url, callback)
{
    var request = new XMLHttpRequest();
    request.onreadystatechange = function()
    {
        if (request.readyState == 4)
        {
            if (callback != null)
            {
                var response = {
                    "data": JSON.parse(request.responseText),
                    "meta": {}
                };
                response["meta"]["request_url"] = url;
                response["meta"]["status"] = request.status;
                callback(response);
            }
        }
    };
    var asynchronous = true;
    request.open(method, url, asynchronous);
    return request;
}

common.get =
function get(url, callback)
{
    request = common._request("GET", url, callback);
    request.send();
}

common.post =
function post(url, data, callback)
{
    request = common._request("POST", url, callback);
    request.send(data);
}

common.bind_box_to_button =
function bind_box_to_button(box, button, ctrl_enter)
{
    // Thanks Yaroslav Yakovlev
    // http://stackoverflow.com/a/9343095
    var bound_box_hook = function(event)
    {
        if (event.key !== "Enter")
            {return;}

        ctrl_success = !ctrl_enter || (event.ctrlKey)

        if (! ctrl_success)
            {return;}

        button.click();
    }
    box.addEventListener("keyup", bound_box_hook);
}

common.create_message_bubble =
function create_message_bubble(message_area, message_positivity, message_text, lifespan)
{
    if (lifespan === undefined)
    {
        lifespan = 8000;
    }
    var message = document.createElement("div");
    message.className = "message_bubble " + message_positivity;
    var span = document.createElement("span");
    span.innerHTML = message_text;
    message.appendChild(span);
    message_area.appendChild(message);
    setTimeout(function(){message_area.removeChild(message);}, lifespan);
}

common.delete_all_children =
function delete_all_children(element)
{
    while (element.firstChild)
    {
        element.removeChild(element.firstChild);
    }
}

common.entry_with_history_hook =
function entry_with_history_hook(event)
{
    //console.log(event);
    var box = event.target;

    if (box.entry_history === undefined)
        {box.entry_history = [];}

    if (box.entry_history_pos === undefined)
        {box.entry_history_pos = -1;}

    if (event.key === "Enter")
    {
        box.entry_history.push(box.value);
    }
    else if (event.key === "ArrowUp")
    {
        if (box.entry_history.length == 0)
            {return}

        if (box.entry_history_pos == -1)
            {box.entry_history_pos = box.entry_history.length - 1;}
        else if (box.entry_history_pos > 0)
            {box.entry_history_pos -= 1;}

        box.value = box.entry_history[box.entry_history_pos];
        setTimeout(function(){box.selectionStart = box.value.length;}, 0);
    }
    else if (event.key === "Escape")
    {
        box.value = "";
    }
    else
    {
        box.entry_history_pos = -1;
    }
}

common.html_to_element =
function html_to_element(html)
{
    var template = document.createElement("template");
    template.innerHTML = html;
    return template.content.firstChild;
}

common.init_button_with_confirm =
function init_button_with_confirm()
{
    /*
    To create a button that requires a second confirmation step, assign it the
    class "button_with_confirm".

    Required:
        data-onclick: String that would normally be the button's onclick.

    Optional:
        data-prompt: Text that appears next to the confirm button. Default is
            "Are you sure?".
        data-prompt-class

        data-confirm: Text inside the confirm button. Default is to inherit the
            original button's text.
        data-confirm-class

        data-cancel: Text inside the cancel button. Default is "Cancel".
        data-cancel-class
    */
    var buttons = document.getElementsByClassName("button_with_confirm");
    for (var index = 0; index < buttons.length; index += 1)
    {
        var button = buttons[index];
        button.classList.remove("button_with_confirm");

        var holder = document.createElement("span");
        holder.classList.add("confirm_holder");
        holder.classList.add(button.dataset.holderClass || "confirm_holder");
        button.parentElement.insertBefore(holder, button);
        button.parentElement.removeChild(button);

        var holder_stage1 = document.createElement("span");
        holder_stage1.classList.add("confirm_holder_stage1");
        holder_stage1.appendChild(button);
        holder.appendChild(holder_stage1);

        var holder_stage2 = document.createElement("span");
        holder_stage2.classList.add("confirm_holder_stage2");
        holder_stage2.classList.add("hidden");
        holder.appendChild(holder_stage2);

        var prompt;
        var input_source;
        if (button.dataset.isInput)
        {
            prompt = document.createElement("input");
            prompt.placeholder = button.dataset.prompt || "";
            input_source = prompt;
        }
        else
        {
            prompt = document.createElement("span");
            prompt.innerText = (button.dataset.prompt || "Are you sure?") + " ";
            input_source = undefined;
        }
        prompt.className = button.dataset.promptClass || "";
        holder_stage2.appendChild(prompt)
        delete button.dataset.prompt;
        delete button.dataset.promptClass;

        var button_confirm = document.createElement("button");
        button_confirm.innerText = (button.dataset.confirm || button.innerText).trim();
        button_confirm.className = button.dataset.confirmClass || "";
        button_confirm.input_source = input_source;
        holder_stage2.appendChild(button_confirm);
        holder_stage2.appendChild(document.createTextNode(" "));
        if (button.dataset.isInput)
        {
            common.bind_box_to_button(prompt, button_confirm);
        }
        delete button.dataset.confirm;
        delete button.dataset.confirmClass;
        delete button.dataset.isInput;

        var button_cancel = document.createElement("button");
        button_cancel.innerText = button.dataset.cancel || "Cancel";
        button_cancel.className = button.dataset.cancelClass || "";
        holder_stage2.appendChild(button_cancel);
        delete button.dataset.cancel;
        delete button.dataset.cancelClass;

        // If this is stupid, let me know.
        var confirm_onclick = button.dataset.onclick + `
            ;
            var holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
        `
        button_confirm.onclick = Function(confirm_onclick);
        button.removeAttribute("onclick");
        button.onclick = function(event)
        {
            var holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.add("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.remove("hidden");
            var input = holder.getElementsByTagName("input")[0];
            if (input)
            {
                input.focus();
            }
        }

        button_cancel.onclick = function(event)
        {
            var holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
        }
        delete button.dataset.onclick;
    }
}

common.normalize_tagname =
function normalize_tagname(tagname)
{
    tagname = tagname.trim();
    tagname = tagname.toLocaleLowerCase();
    tagname = tagname.split(".");
    tagname = tagname[tagname.length-1];
    tagname = tagname.split("+")[0];
    tagname = tagname.replace(new RegExp(" ", 'g'), "_");
    tagname = tagname.replace(new RegExp("-", 'g'), "_");
    return tagname;
}

common.refresh =
function refresh()
{
    window.location.reload();
}

common.on_pageload =
function on_pageload()
{
    common.init_button_with_confirm();
}
document.addEventListener("DOMContentLoaded", common.on_pageload);
<commit_msg>Use separate array of buttons to prevent index skipping.<commit_after>var common = {};

common.INPUT_TYPES = new Set(["INPUT", "TEXTAREA"]);

common._request =
function _request(method, url, callback)
{
    var request = new XMLHttpRequest();
    request.onreadystatechange = function()
    {
        if (request.readyState == 4)
        {
            if (callback != null)
            {
                var response = {
                    "data": JSON.parse(request.responseText),
                    "meta": {}
                };
                response["meta"]["request_url"] = url;
                response["meta"]["status"] = request.status;
                callback(response);
            }
        }
    };
    var asynchronous = true;
    request.open(method, url, asynchronous);
    return request;
}

common.get =
function get(url, callback)
{
    request = common._request("GET", url, callback);
    request.send();
}

common.post =
function post(url, data, callback)
{
    request = common._request("POST", url, callback);
    request.send(data);
}

common.bind_box_to_button =
function bind_box_to_button(box, button, ctrl_enter)
{
    // Thanks Yaroslav Yakovlev
    // http://stackoverflow.com/a/9343095
    var bound_box_hook = function(event)
    {
        if (event.key !== "Enter")
            {return;}

        ctrl_success = !ctrl_enter || (event.ctrlKey)

        if (! ctrl_success)
            {return;}

        button.click();
    }
    box.addEventListener("keyup", bound_box_hook);
}

common.create_message_bubble =
function create_message_bubble(message_area, message_positivity, message_text, lifespan)
{
    if (lifespan === undefined)
    {
        lifespan = 8000;
    }
    var message = document.createElement("div");
    message.className = "message_bubble " + message_positivity;
    var span = document.createElement("span");
    span.innerHTML = message_text;
    message.appendChild(span);
    message_area.appendChild(message);
    setTimeout(function(){message_area.removeChild(message);}, lifespan);
}

common.delete_all_children =
function delete_all_children(element)
{
    while (element.firstChild)
    {
        element.removeChild(element.firstChild);
    }
}

common.entry_with_history_hook =
function entry_with_history_hook(event)
{
    //console.log(event);
    var box = event.target;

    if (box.entry_history === undefined)
        {box.entry_history = [];}

    if (box.entry_history_pos === undefined)
        {box.entry_history_pos = -1;}

    if (event.key === "Enter")
    {
        box.entry_history.push(box.value);
    }
    else if (event.key === "ArrowUp")
    {
        if (box.entry_history.length == 0)
            {return}

        if (box.entry_history_pos == -1)
            {box.entry_history_pos = box.entry_history.length - 1;}
        else if (box.entry_history_pos > 0)
            {box.entry_history_pos -= 1;}

        box.value = box.entry_history[box.entry_history_pos];
        setTimeout(function(){box.selectionStart = box.value.length;}, 0);
    }
    else if (event.key === "Escape")
    {
        box.value = "";
    }
    else
    {
        box.entry_history_pos = -1;
    }
}

common.html_to_element =
function html_to_element(html)
{
    var template = document.createElement("template");
    template.innerHTML = html;
    return template.content.firstChild;
}

common.init_button_with_confirm =
function init_button_with_confirm()
{
    /*
    To create a button that requires a second confirmation step, assign it the
    class "button_with_confirm".

    Required:
        data-onclick: String that would normally be the button's onclick.

    Optional:
        data-prompt: Text that appears next to the confirm button. Default is
            "Are you sure?".
        data-prompt-class

        data-confirm: Text inside the confirm button. Default is to inherit the
            original button's text.
        data-confirm-class

        data-cancel: Text inside the cancel button. Default is "Cancel".
        data-cancel-class
    */
    var buttons = Array.from(document.getElementsByClassName("button_with_confirm"));
    for (var index = 0; index < buttons.length; index += 1)
    {
        var button = buttons[index];
        button.classList.remove("button_with_confirm");

        var holder = document.createElement("span");
        holder.classList.add("confirm_holder");
        holder.classList.add(button.dataset.holderClass || "confirm_holder");
        button.parentElement.insertBefore(holder, button);
        button.parentElement.removeChild(button);

        var holder_stage1 = document.createElement("span");
        holder_stage1.classList.add("confirm_holder_stage1");
        holder_stage1.appendChild(button);
        holder.appendChild(holder_stage1);

        var holder_stage2 = document.createElement("span");
        holder_stage2.classList.add("confirm_holder_stage2");
        holder_stage2.classList.add("hidden");
        holder.appendChild(holder_stage2);

        var prompt;
        var input_source;
        if (button.dataset.isInput)
        {
            prompt = document.createElement("input");
            prompt.placeholder = button.dataset.prompt || "";
            input_source = prompt;
        }
        else
        {
            prompt = document.createElement("span");
            prompt.innerText = (button.dataset.prompt || "Are you sure?") + " ";
            input_source = undefined;
        }
        prompt.className = button.dataset.promptClass || "";
        holder_stage2.appendChild(prompt)
        delete button.dataset.prompt;
        delete button.dataset.promptClass;

        var button_confirm = document.createElement("button");
        button_confirm.innerText = (button.dataset.confirm || button.innerText).trim();
        button_confirm.className = button.dataset.confirmClass || "";
        button_confirm.input_source = input_source;
        holder_stage2.appendChild(button_confirm);
        holder_stage2.appendChild(document.createTextNode(" "));
        if (button.dataset.isInput)
        {
            common.bind_box_to_button(prompt, button_confirm);
        }
        delete button.dataset.confirm;
        delete button.dataset.confirmClass;
        delete button.dataset.isInput;

        var button_cancel = document.createElement("button");
        button_cancel.innerText = button.dataset.cancel || "Cancel";
        button_cancel.className = button.dataset.cancelClass || "";
        holder_stage2.appendChild(button_cancel);
        delete button.dataset.cancel;
        delete button.dataset.cancelClass;

        // If this is stupid, let me know.
        var confirm_onclick = button.dataset.onclick + `
            ;
            var holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
        `
        button_confirm.onclick = Function(confirm_onclick);
        button.removeAttribute("onclick");
        button.onclick = function(event)
        {
            var holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.add("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.remove("hidden");
            var input = holder.getElementsByTagName("input")[0];
            if (input)
            {
                input.focus();
            }
        }

        button_cancel.onclick = function(event)
        {
            var holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
        }
        delete button.dataset.onclick;
    }
}

common.normalize_tagname =
function normalize_tagname(tagname)
{
    tagname = tagname.trim();
    tagname = tagname.toLocaleLowerCase();
    tagname = tagname.split(".");
    tagname = tagname[tagname.length-1];
    tagname = tagname.split("+")[0];
    tagname = tagname.replace(new RegExp(" ", 'g'), "_");
    tagname = tagname.replace(new RegExp("-", 'g'), "_");
    return tagname;
}

common.refresh =
function refresh()
{
    window.location.reload();
}

common.on_pageload =
function on_pageload()
{
    common.init_button_with_confirm();
}
document.addEventListener("DOMContentLoaded", common.on_pageload);
<|endoftext|>


<commit_before>import { includes, isEmpty, trim } from 'lodash'

export default function validatePostData (userId, data) {
  if (!trim(data.name)) {
    throw new Error('title can\'t be blank')
  }

  const allowedTypes = [Post.Type.REQUEST, Post.Type.OFFER, Post.Type.DISCUSSION, Post.Type.PROJECT, Post.Type.EVENT, Post.Type.RESOURCE]
  if (data.type && !includes(allowedTypes, data.type)) {
    throw new Error('not a valid type')
  }

  if (isEmpty(data.community_ids)) {
    throw new Error('no communities specified')
  }

  if (data.topicNames && data.topicNames.length > 3) {
    throw new Error('too many topics in post, maximum 3')
  }

  if (data.type === Post.Type.RESOURCE && !data.location_id) {
    throw new Error('resources must have a location')
  }

  return Group.allHaveMember(data.community_ids, userId, Community)
  .then(ok => ok ? Promise.resolve() : Promise.reject(new Error('unable to post to all those communities')))
}
<commit_msg>remove location requirement for resource type posts<commit_after>import { includes, isEmpty, trim } from 'lodash'

export default function validatePostData (userId, data) {
  if (!trim(data.name)) {
    throw new Error('title can\'t be blank')
  }

  const allowedTypes = [Post.Type.REQUEST, Post.Type.OFFER, Post.Type.DISCUSSION, Post.Type.PROJECT, Post.Type.EVENT, Post.Type.RESOURCE]
  if (data.type && !includes(allowedTypes, data.type)) {
    throw new Error('not a valid type')
  }

  if (isEmpty(data.community_ids)) {
    throw new Error('no communities specified')
  }

  if (data.topicNames && data.topicNames.length > 3) {
    throw new Error('too many topics in post, maximum 3')
  }

  return Group.allHaveMember(data.community_ids, userId, Community)
  .then(ok => ok ? Promise.resolve() : Promise.reject(new Error('unable to post to all those communities')))
}
<|endoftext|>


<commit_before>/*global web3:true */
import moment from 'moment';
import EthereumBlockies from 'ethereum-blockies';
import BigNumber from 'bignumber.js';
import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Avatar from 'material-ui/Avatar';
import Chip from 'material-ui/Chip';
import RaisedButton from 'material-ui/RaisedButton';
import * as MColors from 'material-ui/styles/colors';
import LinkIcon from 'material-ui/svg-icons/content/link';

import {
  Table,
  TableBody,
  TableHeader,
  TableHeaderColumn,
  TableRow,
  TableRowColumn,
} from 'material-ui/Table';

import {formatEth} from 'utils/ethUtils';
import {
  Step,
  Stepper,
  StepLabel,
} from 'material-ui/Stepper';

import {betState, stepperState} from 'utils/betStates';
import Arbiters from 'components/Arbiters';

class BetController extends Component {
  constructor(props) {
    super(props);
    this.state = {
      selectedTeam: null,
      amountToBet: new BigNumber(0),
      isArbiter: false,
      betList: [],
      allBetEvents: null
    }
  }

  componentWillMount() {
    if (this.props.isDetailed) {
      var allBetEvents = this.props.betContractInstance.allEvents({
      fromBlock: 0,
      toBlock: 'latest'});
      this.setState({allBetEvents: allBetEvents});
      //this.setState({myBetsFilter: filter});
      allBetEvents.watch((error, result) => {
        if (result.event === 'NewBet') {
          web3.eth.getBlock(result.blockNumber, (err, block) => {
            this.setState(previousState => {
              const newBet = {
                from: result.args.from,
                amount: result.args.amount,
                forTeam: result.args.forTeam,
                timestamp: block.timestamp
              };
              previousState.betList.unshift(newBet);
            });
          });
        }
      });
    }
  }
  componentWillUnmount() {
    if (this.state.allBetEvents)
      this.state.allBetEvents.stopWatching();
  }

  setTeam = (event, index, value) => {
    this.setState({selectedTeam: value});
  };

  setBetValue = (event, newValue) => {
    if (newValue !== '')
      this.setState({amountToBet : new BigNumber(newValue)});
  };

  Steps = () => {
    var BetDecision = () => {
      if (this.props.currentBetState <= betState.shouldCallArbiter ||
        this.props.currentBetState === betState.calledArbiter)
        return 'Result';
      else if (this.props.currentBetState === betState.team0Won)
        return `${this.props.team0Name} won`;
      else if (this.props.currentBetState === betState.arbiterUndecided)
        return 'Undecided';
      else if (this.props.currentBetState === betState.team1Won)
        return `${this.props.team1Name} won`;
      else if (this.props.currentBetState === betState.draw)
        return 'Draw';
      return null;
    };
    var CallArbiterName = () => {
      if (this.props.currentBetState === betState.calledArbiter)
        return 'Called Arbiter';
      else
        return 'Call Arbiter';
    };

    // TODO: I don't think BetDecision() works
    // TODO: Change Call Arbiter to Arbiter Called when the arbiter is called
    return (
        <Stepper activeStep={this.props.stepperState}>
          <Step>
            <StepLabel>Place your bet!</StepLabel>
          </Step>
          <Step>
            <StepLabel>Match running</StepLabel>
          </Step>
          <Step>
            <StepLabel>{CallArbiterName()}</StepLabel>
          </Step>
          <Step>
            <StepLabel>{BetDecision()}</StepLabel>
          </Step>
          <Step>
            <StepLabel>Payout</StepLabel>
          </Step>
        </Stepper>
      );
  }

  DynamicBetButton = () => {
    if (this.props.currentBetState === betState.shouldCallArbiter ||
        this.props.currentBetState === betState.calledArbiter) {
      return (
      <RaisedButton
        disabled={this.props.currentBetState === betState.calledArbiter}
        secondary={true}
        className="betBtn"
        onTouchTap={() => this.props.callArbiterFunction()}
      >Call Arbiter
      </RaisedButton>
      )
    }
    else if ((this.props.currentBetState >= betState.team0Won &&
              this.props.currentBetState <= betState.draw) ||
              this.props.stepperState === stepperState.payout) {
      var gain = this.FinalGain();
      return (
      <RaisedButton
        className="betBtn"
        primary={true}
        onTouchTap={() => this.props.withdrawFunction()}
        disabled={(this.props.stepperState !== stepperState.payout)}
      >Withdraw Ξ{gain.toString()}
      </RaisedButton>
      )
    }
    return (
      <RaisedButton
        disabled={(this.props.currentBetState !== betState.matchOpen)}
        primary={true}
        label='Bet'
        onTouchTap={() => this.props.betOnTeamFunction(
          (this.props.hasBetOnTeam.team !== null) ? this.props.hasBetOnTeam.team
          : this.state.selectedTeam ,
          new BigNumber(this.state.amountToBet).times(new BigNumber('1000000000000000000')))}
      />
      )
  }

  ArbiterExpandedMatch = () => {
    return (

          Who won the match?

        <SelectField style={{ width: 160 }} className='test'
          floatingLabelText='Team'
          value={this.state.selectedTeam}
          onChange={this.setTeam}
          disabled={this.props.currentBetState !== betState.calledArbiter}
        >
          <MenuItem value={1} primaryText={this.props.team0Name} />
          <MenuItem value={2} primaryText={this.props.team1Name} />
          <MenuItem value={3} primaryText='Draw' />
        </SelectField>
        <RaisedButton
          disabled={this.props.currentBetState !== betState.calledArbiter}
          secondary={true}
          className="betBtn"
          onTouchTap={() => this.props.callVoteFunction(this.state.selectedTeam)}
        >Vote
        </RaisedButton>

    );
  }

  ComputeGain = (amount, winnerPool, loserPool, tax) => {
    var profit = amount.dividedBy(winnerPool).times(loserPool);
    if (profit.gt(0))
      profit = profit.minus(profit.times(new BigNumber(tax)))
    return amount.plus(profit);
  }

  ExpectedGain = () => {
    var expectedIncome;
    var amount;
    var winnerPool;
    var loserPool;
    var tax = new BigNumber(this.props.tax);
    tax = tax.dividedBy(100);

    if (this.state.selectedTeam === null || this.props.currentBetState !== betState.matchOpen)
      return null;

    if (this.state.amountToBet.lessThan(new BigNumber(0)))
      return null;

    amount = this.state.amountToBet.times(new BigNumber('1000000000000000000'));
    if (this.state.selectedTeam === false) {
      loserPool = this.props.team1BetSum;
      winnerPool = this.props.team0BetSum;
    }
    else {
      loserPool = this.props.team0BetSum;
      winnerPool = this.props.team1BetSum;
    }
    winnerPool = winnerPool.plus(amount);
    expectedIncome = this.ComputeGain(amount, winnerPool, loserPool, tax);

    if (amount.isZero(0) || isNaN(amount))
      return null;
    else
      return (
      <Chip backgroundColor={MColors.yellow500}>
        <Avatar size={32} backgroundColor={MColors.yellow800}>Ξ</Avatar>
        Win {formatEth(expectedIncome)}
      </Chip>
      );
  }

  FinalGain = () => {
    var amount;
    var winnerPool;
    var loserPool;
    var tax = new BigNumber(this.props.tax);
    tax = tax.dividedBy(100);

    if (this.props.currentBetState < betState.team0Won || this.props.currentBetState > betState.draw)
      return new BigNumber(0);

    if (this.props.hasBetOnTeam === null)
      return new BigNumber(0);

    amount = this.props.hasBetOnTeam.amount;
    if (this.props.currentBetState === betState.draw)
      return amount;

    var hasBetTeam0 = !this.props.hasBetOnTeam.team;
    var hasBetTeam1 = this.props.hasBetOnTeam.team;
    var team0BetSum = this.props.team0BetSum;
    var team1BetSum = this.props.team1BetSum;

    if ((this.props.currentBetState === betState.team0Won && hasBetTeam1) ||
        (this.props.currentBetState === betState.team1Won && hasBetTeam0))
      return new BigNumber(0);

    if (this.props.currentBetState === betState.team0Won) {
      winnerPool = team0BetSum;
      loserPool = team1BetSum;
    }
    else if (this.props.currentBetState === betState.team1Won) {
      winnerPool = team1BetSum;
      loserPool = team0BetSum;
    }
    return this.ComputeGain(amount, winnerPool, loserPool, tax);
  }

  DynamicList = () => {
    if (!this.props.isDetailed)
      return null;
    return (

Bets history

          <TableHeader adjustForCheckbox={false} displaySelectAll={false}>
            <TableRow>
              <TableHeaderColumn colSpan='2'>Address</TableHeaderColumn>
              <TableHeaderColumn>Amount</TableHeaderColumn>
              <TableHeaderColumn>For Team</TableHeaderColumn>
              <TableHeaderColumn>At</TableHeaderColumn>
            </TableRow>
          </TableHeader>
          <TableBody displayRowCheckbox={false}>
            {this.state.betList.map((bet, index) => {
              const imgURL = EthereumBlockies.create({
                seed: bet.from.toLowerCase(),
                spotcolor: -1,
                size: 8,
                scale: 4,
              }).toDataURL();

              return (
                <TableRow key={index}>
                  <TableRowColumn colSpan='2' >{bet.from}</TableRowColumn>
                  <TableRowColumn>{formatEth(bet.amount)}</TableRowColumn>
                  <TableRowColumn>{(bet.forTeam) ? this.props.team1Name : this.props.team0Name}</TableRowColumn>
                  <TableRowColumn>{moment(bet.timestamp*1e3).format('LLL')}</TableRowColumn>
                </TableRow>
              );
            })}
          </TableBody>

    );
  }

  render() {
    if (this.props.isExpanded) {
      /*
       * Arbiters can vote on the match's outcome
       */
      if (this.props.isArbiter) {
        return <this.ArbiterExpandedMatch />
      }
      const ArbiterInfo =  Arbiters.getArbiterInfo(this.props.arbiterInfo);
      return (

          <SelectField style={{ width: 160,
                                verticalAlign: 'bottom',
                                marginRight: 10,
                                marginLeft: 70 }}
            floatingLabelText='Team'
            value={(this.props.hasBetOnTeam.team !== null) ? this.props.hasBetOnTeam.team : this.state.selectedTeam}
            onChange={this.setTeam}
            disabled={this.props.hasBetOnTeam.team !== null || this.props.currentBetState >= betState.matchRunning}
          >
            <MenuItem value={false} primaryText={this.props.team0Name} />
            <MenuItem value={true} primaryText={this.props.team1Name} />
          </SelectField>
          <TextField
            disabled={(this.props.currentBetState >= betState.matchRunning)}
            style={{ width: 80 }}
            id='betAmount'
            type='number'
            onChange={this.setBetValue}
            />
          <this.DynamicBetButton />
          <ArbiterInfo />
          {(!this.props.isDetailed) ? <RaisedButton
            style={{marginLeft: 14}}
            href={`#bet/${this.props.address}`}
            label='Permalink'
            primary={true}
            icon={<LinkIcon />}
          /> : null}
          <this.ExpectedGain />

          <this.Steps />
          <this.DynamicList />

      )
    }
    return null;
  }
}

export default BetController;
<commit_msg>sorting events<commit_after>/*global web3:true */
import moment from 'moment';
import EthereumBlockies from 'ethereum-blockies';
import BigNumber from 'bignumber.js';
import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Avatar from 'material-ui/Avatar';
import Chip from 'material-ui/Chip';
import RaisedButton from 'material-ui/RaisedButton';
import * as MColors from 'material-ui/styles/colors';
import LinkIcon from 'material-ui/svg-icons/content/link';

import {
  Table,
  TableBody,
  TableHeader,
  TableHeaderColumn,
  TableRow,
  TableRowColumn,
} from 'material-ui/Table';

import {formatEth} from 'utils/ethUtils';
import {
  Step,
  Stepper,
  StepLabel,
} from 'material-ui/Stepper';

import {betState, stepperState} from 'utils/betStates';
import Arbiters from 'components/Arbiters';

class BetController extends Component {
  constructor(props) {
    super(props);
    this.state = {
      selectedTeam: null,
      amountToBet: new BigNumber(0),
      isArbiter: false,
      betList: [],
      allBetEvents: null
    }
  }

  componentWillMount() {
    if (this.props.isDetailed) {
      var allBetEvents = this.props.betContractInstance.allEvents({
      fromBlock: 0,
      toBlock: 'latest'});
      this.setState({allBetEvents: allBetEvents});
      //this.setState({myBetsFilter: filter});
      allBetEvents.watch((error, result) => {
        if (result.event === 'NewBet') {
          web3.eth.getBlock(result.blockNumber, (err, block) => {
            this.setState(previousState => {
              const newBet = {
                from: result.args.from,
                amount: result.args.amount,
                forTeam: result.args.forTeam,
                timestamp: block.timestamp
              };
              previousState.betList.push(newBet);
              previousState.betList.sort((A, B) => A.timestamp  {
    this.setState({selectedTeam: value});
  };

  setBetValue = (event, newValue) => {
    if (newValue !== '')
      this.setState({amountToBet : new BigNumber(newValue)});
  };

  Steps = () => {
    var BetDecision = () => {
      if (this.props.currentBetState <= betState.shouldCallArbiter ||
        this.props.currentBetState === betState.calledArbiter)
        return 'Result';
      else if (this.props.currentBetState === betState.team0Won)
        return `${this.props.team0Name} won`;
      else if (this.props.currentBetState === betState.arbiterUndecided)
        return 'Undecided';
      else if (this.props.currentBetState === betState.team1Won)
        return `${this.props.team1Name} won`;
      else if (this.props.currentBetState === betState.draw)
        return 'Draw';
      return null;
    };
    var CallArbiterName = () => {
      if (this.props.currentBetState === betState.calledArbiter)
        return 'Called Arbiter';
      else
        return 'Call Arbiter';
    };

    // TODO: I don't think BetDecision() works
    // TODO: Change Call Arbiter to Arbiter Called when the arbiter is called
    return (
        <Stepper activeStep={this.props.stepperState}>
          <Step>
            <StepLabel>Place your bet!</StepLabel>
          </Step>
          <Step>
            <StepLabel>Match running</StepLabel>
          </Step>
          <Step>
            <StepLabel>{CallArbiterName()}</StepLabel>
          </Step>
          <Step>
            <StepLabel>{BetDecision()}</StepLabel>
          </Step>
          <Step>
            <StepLabel>Payout</StepLabel>
          </Step>
        </Stepper>
      );
  }

  DynamicBetButton = () => {
    if (this.props.currentBetState === betState.shouldCallArbiter ||
        this.props.currentBetState === betState.calledArbiter) {
      return (
      <RaisedButton
        disabled={this.props.currentBetState === betState.calledArbiter}
        secondary={true}
        className="betBtn"
        onTouchTap={() => this.props.callArbiterFunction()}
      >Call Arbiter
      </RaisedButton>
      )
    }
    else if ((this.props.currentBetState >= betState.team0Won &&
              this.props.currentBetState <= betState.draw) ||
              this.props.stepperState === stepperState.payout) {
      var gain = this.FinalGain();
      return (
      <RaisedButton
        className="betBtn"
        primary={true}
        onTouchTap={() => this.props.withdrawFunction()}
        disabled={(this.props.stepperState !== stepperState.payout)}
      >Withdraw Ξ{gain.toString()}
      </RaisedButton>
      )
    }
    return (
      <RaisedButton
        disabled={(this.props.currentBetState !== betState.matchOpen)}
        primary={true}
        label='Bet'
        onTouchTap={() => this.props.betOnTeamFunction(
          (this.props.hasBetOnTeam.team !== null) ? this.props.hasBetOnTeam.team
          : this.state.selectedTeam ,
          new BigNumber(this.state.amountToBet).times(new BigNumber('1000000000000000000')))}
      />
      )
  }

  ArbiterExpandedMatch = () => {
    return (

          Who won the match?

        <SelectField style={{ width: 160 }} className='test'
          floatingLabelText='Team'
          value={this.state.selectedTeam}
          onChange={this.setTeam}
          disabled={this.props.currentBetState !== betState.calledArbiter}
        >
          <MenuItem value={1} primaryText={this.props.team0Name} />
          <MenuItem value={2} primaryText={this.props.team1Name} />
          <MenuItem value={3} primaryText='Draw' />
        </SelectField>
        <RaisedButton
          disabled={this.props.currentBetState !== betState.calledArbiter}
          secondary={true}
          className="betBtn"
          onTouchTap={() => this.props.callVoteFunction(this.state.selectedTeam)}
        >Vote
        </RaisedButton>

    );
  }

  ComputeGain = (amount, winnerPool, loserPool, tax) => {
    var profit = amount.dividedBy(winnerPool).times(loserPool);
    if (profit.gt(0))
      profit = profit.minus(profit.times(new BigNumber(tax)))
    return amount.plus(profit);
  }

  ExpectedGain = () => {
    var expectedIncome;
    var amount;
    var winnerPool;
    var loserPool;
    var tax = new BigNumber(this.props.tax);
    tax = tax.dividedBy(100);

    if (this.state.selectedTeam === null || this.props.currentBetState !== betState.matchOpen)
      return null;

    if (this.state.amountToBet.lessThan(new BigNumber(0)))
      return null;

    amount = this.state.amountToBet.times(new BigNumber('1000000000000000000'));
    if (this.state.selectedTeam === false) {
      loserPool = this.props.team1BetSum;
      winnerPool = this.props.team0BetSum;
    }
    else {
      loserPool = this.props.team0BetSum;
      winnerPool = this.props.team1BetSum;
    }
    winnerPool = winnerPool.plus(amount);
    expectedIncome = this.ComputeGain(amount, winnerPool, loserPool, tax);

    if (amount.isZero(0) || isNaN(amount))
      return null;
    else
      return (
      <Chip backgroundColor={MColors.yellow500}>
        <Avatar size={32} backgroundColor={MColors.yellow800}>Ξ</Avatar>
        Win {formatEth(expectedIncome)}
      </Chip>
      );
  }

  FinalGain = () => {
    var amount;
    var winnerPool;
    var loserPool;
    var tax = new BigNumber(this.props.tax);
    tax = tax.dividedBy(100);

    if (this.props.currentBetState < betState.team0Won || this.props.currentBetState > betState.draw)
      return new BigNumber(0);

    if (this.props.hasBetOnTeam === null)
      return new BigNumber(0);

    amount = this.props.hasBetOnTeam.amount;
    if (this.props.currentBetState === betState.draw)
      return amount;

    var hasBetTeam0 = !this.props.hasBetOnTeam.team;
    var hasBetTeam1 = this.props.hasBetOnTeam.team;
    var team0BetSum = this.props.team0BetSum;
    var team1BetSum = this.props.team1BetSum;

    if ((this.props.currentBetState === betState.team0Won && hasBetTeam1) ||
        (this.props.currentBetState === betState.team1Won && hasBetTeam0))
      return new BigNumber(0);

    if (this.props.currentBetState === betState.team0Won) {
      winnerPool = team0BetSum;
      loserPool = team1BetSum;
    }
    else if (this.props.currentBetState === betState.team1Won) {
      winnerPool = team1BetSum;
      loserPool = team0BetSum;
    }
    return this.ComputeGain(amount, winnerPool, loserPool, tax);
  }

  DynamicList = () => {
    if (!this.props.isDetailed)
      return null;
    return (

Bets history

          <TableHeader adjustForCheckbox={false} displaySelectAll={false}>
            <TableRow>
              <TableHeaderColumn colSpan='2'>Address</TableHeaderColumn>
              <TableHeaderColumn>Amount</TableHeaderColumn>
              <TableHeaderColumn>For Team</TableHeaderColumn>
              <TableHeaderColumn>At</TableHeaderColumn>
            </TableRow>
          </TableHeader>
          <TableBody displayRowCheckbox={false}>
            {this.state.betList.map((bet, index) => {
              const imgURL = EthereumBlockies.create({
                seed: bet.from.toLowerCase(),
                spotcolor: -1,
                size: 8,
                scale: 4,
              }).toDataURL();

              return (
                <TableRow key={index}>
                  <TableRowColumn colSpan='2' >{bet.from}</TableRowColumn>
                  <TableRowColumn>{formatEth(bet.amount)}</TableRowColumn>
                  <TableRowColumn>{(bet.forTeam) ? this.props.team1Name : this.props.team0Name}</TableRowColumn>
                  <TableRowColumn>{moment(bet.timestamp*1e3).format('LLL')}</TableRowColumn>
                </TableRow>
              );
            })}
          </TableBody>

    );
  }

  render() {
    if (this.props.isExpanded) {
      /*
       * Arbiters can vote on the match's outcome
       */
      if (this.props.isArbiter) {
        return <this.ArbiterExpandedMatch />
      }
      const ArbiterInfo =  Arbiters.getArbiterInfo(this.props.arbiterInfo);
      return (

          <SelectField style={{ width: 160,
                                verticalAlign: 'bottom',
                                marginRight: 10,
                                marginLeft: 70 }}
            floatingLabelText='Team'
            value={(this.props.hasBetOnTeam.team !== null) ? this.props.hasBetOnTeam.team : this.state.selectedTeam}
            onChange={this.setTeam}
            disabled={this.props.hasBetOnTeam.team !== null || this.props.currentBetState >= betState.matchRunning}
          >
            <MenuItem value={false} primaryText={this.props.team0Name} />
            <MenuItem value={true} primaryText={this.props.team1Name} />
          </SelectField>
          <TextField
            disabled={(this.props.currentBetState >= betState.matchRunning)}
            style={{ width: 80 }}
            id='betAmount'
            type='number'
            onChange={this.setBetValue}
            />
          <this.DynamicBetButton />
          <ArbiterInfo />
          {(!this.props.isDetailed) ? <RaisedButton
            style={{marginLeft: 14}}
            href={`#bet/${this.props.address}`}
            label='Permalink'
            primary={true}
            icon={<LinkIcon />}
          /> : null}
          <this.ExpectedGain />

          <this.Steps />
          <this.DynamicList />

      )
    }
    return null;
  }
}

export default BetController;
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2016-2021 Camptocamp SA
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import angular from 'angular';
import gmfRasterRasterService from 'gmf/raster/RasterService.js';

import ngeoMiscDebounce from 'ngeo/misc/debounce.js';

import {listen, unlistenByKey} from 'ol/events.js';
import MapBrowserEvent from 'ol/MapBrowserEvent.js';

import 'bootstrap/js/src/dropdown.js';

/**
 * @type {angular.IModule}
 * @hidden
 */
const myModule = angular.module('gmfRasterComponent', [gmfRasterRasterService.name, ngeoMiscDebounce.name]);

myModule.run(
  /**
   * @ngInject
   * @param {angular.ITemplateCacheService} $templateCache
   */
  ($templateCache) => {
    // @ts-ignore: webpack
    $templateCache.put('gmf/raster/widgetComponent', require('./widgetComponent.html'));
  }
);

myModule.value(
  'gmfElevationwidgetTemplateUrl',
  /**
   * @param {angular.IAttributes} $attrs Attributes.
   * @return {string} The template url.
   */
  ($attrs) => {
    const templateUrl = $attrs.gmfElevationwidgetTemplateUrl;
    return templateUrl !== undefined ? templateUrl : 'gmf/raster/widgetComponent';
  }
);

/**
 * @hidden
 * @param {angular.IAttributes} $attrs Attributes.
 * @param {function(angular.IAttributes): string} gmfElevationwidgetTemplateUrl Template function.
 * @return {string} Template URL.
 * @ngInject
 * @private
 * @hidden
 */
function gmfElevationwidgetTemplateUrl($attrs, gmfElevationwidgetTemplateUrl) {
  return gmfElevationwidgetTemplateUrl($attrs);
}

/**
 * Provide a directive that set a value each 500ms with the elevation under the
 * mouse cursor position on the map. The value must come from the elevation
 * service of a c2cgeoportal server. The server's URL must be defined as
 * config value of the application.
 *
 * Example:
 *
 *      <span gmf-elevation
 *            gmf-elevation-active="elevationActive"
 *            gmf-elevation-elevation="elevationValue"
 *            gmf-elevation-layer="mainCtrl.elevationLayer"
 *            gmf-elevation-map="::mainCtrl.map">
 *            {{elevationValue}}
 *      </span>
 *
 *  For value in meter `elevationLayersConfig` can be an empty object, complex example:
 *
 *      elevationLayersConfig = {
 *          '<layer>': {
 *              'filter': 'ngeoUnitPrefix',
 *              'args': ['m²', 'square'],
 *              'postfix': '<notice>',
 *              'separator': ''
 *          }
 *      };
 *
 *
 * @htmlAttribute {boolean} gmf-elevation-active A boolean to set active or deactivate the component.
 * @htmlAttribute {number} gmf-elevation-elevation The value to set with the elevation value.
 * @htmlAttribute {string} gmf-elevation-layer Elevation layer to use.
 * @htmlAttribute {import("ol/Map.js").default} gmf-elevation-map The map.
 * @return {angular.IDirective} Directive Definition Object.
 * @ngdoc directive
 * @ngname gmfElevation
 */
function rasterComponent() {
  return {
    restrict: 'A',
    controller: 'GmfElevationController as ctrl',
    bindToController: true,
    scope: {
      'active': '<gmfElevationActive',
      'elevation': '=gmfElevationElevation',
      'loading': '=?gmfElevationLoading',
      'layer': '<gmfElevationLayer',
      'map': '=gmfElevationMap',
    },
    link: (scope, element, attr) => {
      // @ts-ignore: scope ......
      const ctrl = scope.ctrl;

      // Watch active or not.
      scope.$watch(
        () => ctrl.active,
        (active) => {
          ctrl.toggleActive_(active);
        }
      );

      // Watch current layer.
      scope.$watch(
        () => ctrl.layer,
        (layer) => {
          ctrl.layer = layer;
          ctrl.elevation = null;
        }
      );
    },
  };
}

myModule.directive('gmfElevation', rasterComponent);

/**
 * @hidden
 * @param {angular.IScope} $scope Scope.
 * @param {angular.IFilterService} $filter Angular filter.
 * @param {import("ngeo/misc/debounce.js").miscDebounce<function(Event|import('ol/events/Event.js').default): void>} ngeoDebounce Ngeo debounce factory
 * @param {import("gmf/raster/RasterService.js").RasterService} gmfRaster Gmf Raster service
 * @param {angular.gettext.gettextCatalog} gettextCatalog Gettext catalog.
 * @param {import('gmf/options.js').gmfElevationOptions} gmfElevationOptions The options
 * @class
 * @hidden
 * @ngInject
 * @ngdoc controller
 * @ngname gmfElevationController
 */
export function Controller($scope, $filter, ngeoDebounce, gmfRaster, gettextCatalog, gmfElevationOptions) {
  /**
   * @type {import('gmf/options.js').gmfElevationOptions}
   */
  this.options = gmfElevationOptions;

  this.filter_ = $filter;
  this.ngeoDebounce_ = ngeoDebounce;
  this.gmfRaster_ = gmfRaster;
  this.gettextCatalog = gettextCatalog;

  /**
   * @type {boolean}
   */
  this.active = false;

  /**
   * @type {?string}
   */
  this.elevation = null;

  /**
   * @type {string}
   */
  this.layer = '';

  /**
   * @type {?import("ol/Map.js").default}
   */
  this.map = null;

  /**
   * @type {import("ol/events.js").EventsKey[]}
   */
  this.listenerKeys_ = [];

  /**
   * @type {angular.IScope}
   */
  this.scope_ = $scope;

  /**
   * @type {boolean}
   */
  this.inViewport_ = false;

  /**
   * @type {boolean}
   */
  this.loading = false;
}

/**
 * Activate or deactivate the request of the raster each 500 ms on pointermove.
 * @param {boolean} active true to make requests.
 * @private
 */
Controller.prototype.toggleActive_ = function (active) {
  this.elevation = null;
  if (active) {
    if (this.listenerKeys_.length) {
      throw new Error('Unexpexted listenerKeys');
    }
    if (!this.map) {
      throw new Error('Missing map');
    }

    // Moving the mouse clears previously displayed elevation
    /** @type {import("ol/events.js").ListenerFunction} */
    const listener = (event) => {
      this.scope_.$apply(() => {
        this.inViewport_ = true;
        this.elevation = null;
        this.loading = false;
      });
    };
    this.listenerKeys_.push(listen(this.map, 'pointermove', listener));

    // Launch the elevation service request when the user stops moving the
    // mouse for less short delay
    this.listenerKeys_.push(
      listen(
        this.map,
        'pointermove',
        this.ngeoDebounce_(
          /** @type {import("ol/events.js").ListenerFunction} */
          (e) => {
            if (this.inViewport_ && e instanceof MapBrowserEvent) {
              this.loading = true;
              const params = {
                'layers': this.layer,
              };
              this.gmfRaster_
                .getRaster(e.coordinate, params)
                .then(this.getRasterSuccess_.bind(this), this.getRasterError_.bind(this));
            }
          },
          500,
          true
        )
      )
    );

    this.listenerKeys_.push(
      listen(
        this.map.getViewport(),
        'mouseout',
        /** @type {import("ol/events.js").ListenerFunction} */
        (evt) => {
          this.scope_.$apply(() => {
            this.elevation = null;
            this.inViewport_ = false;
            this.loading = false;
          });
        }
      )
    );
  } else {
    this.elevation = null;
    this.listenerKeys_.forEach(unlistenByKey);
    this.listenerKeys_.length = 0;
  }
};

/**
 * @param {Object<string, number>} resp Response of the get Raster service.
 */
Controller.prototype.getRasterSuccess_ = function (resp) {
  if (this.layer === undefined) {
    throw new Error('Missing layer');
  }
  const value = resp[this.layer];
  if (value !== undefined && value !== null) {
    const options = (this.options.layersConfig || {})[this.layer] || {};
    const filter = options.filter || 'number';
    const custom_args = options.args || [];
    /** @type {string} */
    const postfix = options.hasOwnProperty('postfix') ? options.postfix : 'm';
    /** @type {string} */
    const separator =
      postfix.length > 0 ? (options.hasOwnProperty('separator') ? options.separator : '\u00a0') : '';
    const args = Array.prototype.concat([value], custom_args);
    const elevation = /** @type {string} */ (this.filter_(filter)(...args));
    if (typeof elevation != 'string') {
      throw new Error('Wrong elevation type');
    }
    this.elevation = elevation + separator + postfix;
  } else {
    const gettextCatalog = this.gettextCatalog;
    this.elevation = gettextCatalog.getString('No value');
  }
  this.loading = false;
};

Controller.prototype.getRasterError_ = function () {
  console.error('Error on getting the raster.');
  this.elevation = null;
  this.loading = false;
};

myModule.controller('GmfElevationController', Controller);

/**
 * Provides a component which encapsulates the elevation component (see above)
 * in a button with dropdown menu to be included in a application directly.
 *
 * Example:
 *  <gmf-elevationwidget
 *      gmf-elevationwidget-map="::mainCtrl.map"
 *      gmf-elevationwidget-active="mainCtrl.showInfobar">
 *  </gmf-elevationwidget>
 *
 * @htmlAttribute {import("ol/Map.js").default} gmf-elevationwidget-map The map.
 * @htmlAttribute {boolean} gmf-elevationwidget-active Whether to activate the elevation component.
 *
 * @ngdoc component
 * @ngname gmfElevationwidget
 */
const rasterWidgetComponent = {
  controller: 'gmfElevationwidgetController as ctrl',
  bindings: {
    'map': '<gmfElevationwidgetMap',
    'active': '<gmfElevationwidgetActive',
  },
  templateUrl: gmfElevationwidgetTemplateUrl,
};
myModule.component('gmfElevationwidget', rasterWidgetComponent);

/**
 * @class
 * @hidden
 * @nginject
 * @ngdoc controller
 * @param {import('gmf/options.js').gmfElevationOptions} gmfElevationOptions The options.
 */
export function WidgetController(gmfElevationOptions) {
  this.options = gmfElevationOptions;
  /**
   * @type {?import("ol/Map.js").default}
   */
  this.map = null;

  /**
   * @type {boolean}
   */
  this.active = false;

  /**
   * @type {boolean}
   */
  this.show = this.options.layers && this.options.layers.length > 0;

  /**
   * @type {string}
   */
  this.selectedElevationLayer = this.show ? this.options.layers[0] : '';
}

myModule.controller('gmfElevationwidgetController', WidgetController);

export default myModule;
<commit_msg>More little function in rasterComponent<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2016-2021 Camptocamp SA
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import angular from 'angular';
import gmfRasterRasterService from 'gmf/raster/RasterService.js';

import ngeoMiscDebounce from 'ngeo/misc/debounce.js';

import {listen, unlistenByKey} from 'ol/events.js';
import MapBrowserEvent from 'ol/MapBrowserEvent.js';

import 'bootstrap/js/src/dropdown.js';

/**
 * @type {angular.IModule}
 * @hidden
 */
const myModule = angular.module('gmfRasterComponent', [gmfRasterRasterService.name, ngeoMiscDebounce.name]);

myModule.run(
  /**
   * @ngInject
   * @param {angular.ITemplateCacheService} $templateCache
   */
  ($templateCache) => {
    // @ts-ignore: webpack
    $templateCache.put('gmf/raster/widgetComponent', require('./widgetComponent.html'));
  }
);

myModule.value(
  'gmfElevationwidgetTemplateUrl',
  /**
   * @param {angular.IAttributes} $attrs Attributes.
   * @return {string} The template url.
   */
  ($attrs) => {
    const templateUrl = $attrs.gmfElevationwidgetTemplateUrl;
    return templateUrl !== undefined ? templateUrl : 'gmf/raster/widgetComponent';
  }
);

/**
 * @hidden
 * @param {angular.IAttributes} $attrs Attributes.
 * @param {function(angular.IAttributes): string} gmfElevationwidgetTemplateUrl Template function.
 * @return {string} Template URL.
 * @ngInject
 * @private
 * @hidden
 */
function gmfElevationwidgetTemplateUrl($attrs, gmfElevationwidgetTemplateUrl) {
  return gmfElevationwidgetTemplateUrl($attrs);
}

/**
 * Provide a directive that set a value each 500ms with the elevation under the
 * mouse cursor position on the map. The value must come from the elevation
 * service of a c2cgeoportal server. The server's URL must be defined as
 * config value of the application.
 *
 * Example:
 *
 *      <span gmf-elevation
 *            gmf-elevation-active="elevationActive"
 *            gmf-elevation-elevation="elevationValue"
 *            gmf-elevation-layer="mainCtrl.elevationLayer"
 *            gmf-elevation-map="::mainCtrl.map">
 *            {{elevationValue}}
 *      </span>
 *
 *  For value in meter `elevationLayersConfig` can be an empty object, complex example:
 *
 *      elevationLayersConfig = {
 *          '<layer>': {
 *              'filter': 'ngeoUnitPrefix',
 *              'args': ['m²', 'square'],
 *              'postfix': '<notice>',
 *              'separator': ''
 *          }
 *      };
 *
 *
 * @htmlAttribute {boolean} gmf-elevation-active A boolean to set active or deactivate the component.
 * @htmlAttribute {number} gmf-elevation-elevation The value to set with the elevation value.
 * @htmlAttribute {string} gmf-elevation-layer Elevation layer to use.
 * @htmlAttribute {import("ol/Map.js").default} gmf-elevation-map The map.
 * @return {angular.IDirective} Directive Definition Object.
 * @ngdoc directive
 * @ngname gmfElevation
 */
function rasterComponent() {
  return {
    restrict: 'A',
    controller: 'GmfElevationController as ctrl',
    bindToController: true,
    scope: {
      'active': '<gmfElevationActive',
      'elevation': '=gmfElevationElevation',
      'loading': '=?gmfElevationLoading',
      'layer': '<gmfElevationLayer',
      'map': '=gmfElevationMap',
    },
    link: (scope, element, attr) => {
      // @ts-ignore: scope ......
      const ctrl = scope.ctrl;

      // Watch active or not.
      scope.$watch(
        () => ctrl.active,
        (active) => {
          ctrl.toggleActive_(active);
        }
      );

      // Watch current layer.
      scope.$watch(
        () => ctrl.layer,
        (layer) => {
          ctrl.layer = layer;
          ctrl.elevation = null;
        }
      );
    },
  };
}

myModule.directive('gmfElevation', rasterComponent);

/**
 * @hidden
 * @param {angular.IScope} $scope Scope.
 * @param {angular.IFilterService} $filter Angular filter.
 * @param {import("ngeo/misc/debounce.js").miscDebounce<function(Event|import('ol/events/Event.js').default): void>} ngeoDebounce Ngeo debounce factory
 * @param {import("gmf/raster/RasterService.js").RasterService} gmfRaster Gmf Raster service
 * @param {angular.gettext.gettextCatalog} gettextCatalog Gettext catalog.
 * @param {import('gmf/options.js').gmfElevationOptions} gmfElevationOptions The options
 * @class
 * @hidden
 * @ngInject
 * @ngdoc controller
 * @ngname gmfElevationController
 */
export function Controller($scope, $filter, ngeoDebounce, gmfRaster, gettextCatalog, gmfElevationOptions) {
  /**
   * @type {import('gmf/options.js').gmfElevationOptions}
   */
  this.options = gmfElevationOptions;

  this.filter_ = $filter;
  this.ngeoDebounce_ = ngeoDebounce;
  this.gmfRaster_ = gmfRaster;
  this.gettextCatalog = gettextCatalog;

  /**
   * @type {boolean}
   */
  this.active = false;

  /**
   * @type {?string}
   */
  this.elevation = null;

  /**
   * @type {string}
   */
  this.layer = '';

  /**
   * @type {?import("ol/Map.js").default}
   */
  this.map = null;

  /**
   * @type {import("ol/events.js").EventsKey[]}
   */
  this.listenerKeys_ = [];

  /**
   * @type {angular.IScope}
   */
  this.scope_ = $scope;

  /**
   * @type {boolean}
   */
  this.inViewport_ = false;

  /**
   * @type {boolean}
   */
  this.loading = false;
}

/**
 * Activate or deactivate the listeners of the component.
 * @param {boolean} active true to make requests.
 * @private
 */
Controller.prototype.toggleActive_ = function (active) {
  this.clear_();
  active ? this.activate_() : this.deactivate_();
};

/**
 * Activate the listeners of the component.
 */
Controller.prototype.activate_ = function () {
  if (this.listenerKeys_.length) {
    throw new Error('Unexpexted listenerKeys');
  }
  if (!this.map) {
    throw new Error('Missing map');
  }

  // Moving the mouse clears previously displayed elevation
  this.listenerKeys_.push(listen(this.map, 'pointermove', this.onPointerMove_.bind(this)));

  // Launch the raster service request when the user stops moving the mouse for less short delay.
  this.listenerKeys_.push(
    listen(this.map, 'pointermove', this.ngeoDebounce_(this.afterPointerMove_.bind(this), 500, true))
  );

  // Clear the elevation and set inViewport_ to false.
  this.listenerKeys_.push(listen(this.map.getViewport(), 'mouseout', this.onMouseout_.bind(this)));
};

/**
 * Deactivate the listeners of the component
 */
Controller.prototype.deactivate_ = function () {
  this.listenerKeys_.forEach(unlistenByKey);
  this.listenerKeys_.length = 0;
};

/**
 * Clears previously displayed raster info and set inViewport_ to true.
 */
Controller.prototype.onPointerMove_ = function () {
  this.scope_.$apply(() => {
    this.inViewport_ = true;
    this.clear_();
  });
};

/**
 * Set the state of the component and query the raster service with the coordinate.
 * @param {Event|import('ol/events/Event.js').default} evt Event.
 */
Controller.prototype.afterPointerMove_ = function (evt) {
  if (this.inViewport_ && evt instanceof MapBrowserEvent) {
    this.loading = true;
    const params = {
      'layers': this.layer,
    };
    this.gmfRaster_
      .getRaster(evt.coordinate, params)
      .then(this.getRasterSuccess_.bind(this), this.getRasterError_.bind(this));
  }
};

/**
 * Clears previously displayed raster info and set inViewport_ to false.
 */
Controller.prototype.onMouseout_ = function () {
  this.scope_.$apply(() => {
    this.inViewport_ = false;
    this.clear_();
  });
};

/**
 * @param {Object<string, number>} resp Response of the get Raster service.
 */
Controller.prototype.getRasterSuccess_ = function (resp) {
  if (this.layer === undefined) {
    throw new Error('Missing layer');
  }
  const value = resp[this.layer];
  if (value !== undefined && value !== null) {
    const options = (this.options.layersConfig || {})[this.layer] || {};
    const filter = options.filter || 'number';
    const custom_args = options.args || [];
    /** @type {string} */
    const postfix = options.hasOwnProperty('postfix') ? options.postfix : 'm';
    /** @type {string} */
    const separator =
      postfix.length > 0 ? (options.hasOwnProperty('separator') ? options.separator : '\u00a0') : '';
    const args = Array.prototype.concat([value], custom_args);
    const elevation = /** @type {string} */ (this.filter_(filter)(...args));
    if (typeof elevation != 'string') {
      throw new Error('Wrong elevation type');
    }
    this.elevation = elevation + separator + postfix;
  } else {
    const gettextCatalog = this.gettextCatalog;
    this.elevation = gettextCatalog.getString('No value');
  }
  this.loading = false;
};

Controller.prototype.getRasterError_ = function () {
  console.error('Error on getting the raster.');
  this.clear_();
};

/**
 * Clear the state.
 */
Controller.prototype.clear_ = function () {
  this.elevation = null;
  this.loading = false;
};

myModule.controller('GmfElevationController', Controller);

/**
 * Provides a component which encapsulates the elevation component (see above)
 * in a button with dropdown menu to be included in a application directly.
 *
 * Example:
 *  <gmf-elevationwidget
 *      gmf-elevationwidget-map="::mainCtrl.map"
 *      gmf-elevationwidget-active="mainCtrl.showInfobar">
 *  </gmf-elevationwidget>
 *
 * @htmlAttribute {import("ol/Map.js").default} gmf-elevationwidget-map The map.
 * @htmlAttribute {boolean} gmf-elevationwidget-active Whether to activate the elevation component.
 *
 * @ngdoc component
 * @ngname gmfElevationwidget
 */
const rasterWidgetComponent = {
  controller: 'gmfElevationwidgetController as ctrl',
  bindings: {
    'map': '<gmfElevationwidgetMap',
    'active': '<gmfElevationwidgetActive',
  },
  templateUrl: gmfElevationwidgetTemplateUrl,
};
myModule.component('gmfElevationwidget', rasterWidgetComponent);

/**
 * @class
 * @hidden
 * @nginject
 * @ngdoc controller
 * @param {import('gmf/options.js').gmfElevationOptions} gmfElevationOptions The options.
 */
export function WidgetController(gmfElevationOptions) {
  this.options = gmfElevationOptions;
  /**
   * @type {?import("ol/Map.js").default}
   */
  this.map = null;

  /**
   * @type {boolean}
   */
  this.active = false;

  /**
   * @type {boolean}
   */
  this.show = this.options.layers && this.options.layers.length > 0;

  /**
   * @type {string}
   */
  this.selectedElevationLayer = this.show ? this.options.layers[0] : '';
}

myModule.controller('gmfElevationwidgetController', WidgetController);

export default myModule;
<|endoftext|>