Source: Selection.js

/**
 * @constructor
 * @param {Object} options The options : {value:null}
 */
hui.ui.Selection = function(options) {
  this.options = hui.override({value:null},options);
  this.element = hui.get(options.element);
  this.name = options.name;
  this.items = [];
  this.subItems = [];
  this.busy = 0;
  this.selection = null;
  if (options.items && options.items.length>0) {
    for (var i=0; i < options.items.length; i++) {
      var item = options.items[i];
      this.items.push(item);
      var element = hui.get(item.id);
      item.element = element;
      this.addItemBehavior(element,item);
    }
    this.selection = this._getSelectionWithValue(this.options.value);
    this._updateUI();
  } else if (this.options.value !== null) {
    this.selection = {value:this.options.value};
  }
  hui.ui.extend(this);
};

/**
 * Creates a new selection widget
 * @param {Object} options The options : {width:0}
 */
hui.ui.Selection.create = function(options) {
  options = hui.override({width:0},options);
  var e = options.element = hui.build('div', {'class':'hui_selection'});
  if (options.width>0) {
    e.style.width = options.width+'px';
  }
  return new hui.ui.Selection(options);
};

hui.ui.Selection.prototype = {
  /** Get the selected item
   * @returns {Object} The selected item, null if no selection */
  getValue : function() {
    return this.selection;
  },
  valueForProperty : function(p) {
    if (p==='value') {
      return this.selection ? this.selection.value : null;
    } else if (p==='kind') {
      return this.selection ? this.selection.kind : null;
    }
    return undefined;
  },
  /** Set the selected item
   * @param {Object} value The selected item */
  setValue : function(value) {
    var item = this._getSelectionWithValue(value);
    if (item===null) {
      this.selection = null;
    } else {
      this.selection = item;
      this.kind=item.kind;
    }
    this._updateUI();
    this.fireChange();
  },
  _getSelectionWithValue : function(value) {
    var i;
    for (i=0; i < this.items.length; i++) {
      if (this.items[i].value==value) {
        return this.items[i];
      }
    }
    for (i=0; i < this.subItems.length; i++) {
      var items = this.subItems[i].items;
      for (var j=0; j < items.length; j++) {
        if (items[j].value==value) {
          return items[j];
        }
      }
    }
    return null;
  },
  /** Changes selection to the first item */
  selectFirst : function() {
    var i;
    for (i=0; i < this.items.length; i++) {
      this.changeSelection(this.items[i]);
      return;
    }
    for (i=0; i < this.subItems.length; i++) {
      var items = this.subItems[i].items;
      for (var j=0; j < items.length; j++) {
        this.changeSelection(items[j]);
        return;
      }
    }
  },
  /** Set the value to null */
  reset : function() {
    this.setValue(null);
  },

  addItems : function(options) {
    options.element = hui.build('div',{parent:this.element});
    var items = new hui.ui.Selection.Items(options);
    items.parent = this;
    this.subItems.push(items);
  },

  _updateUI : function() {
    var i;
    for (i=0; i < this.items.length; i++) {
      hui.cls.set(this.items[i].element,'hui_selected',this.isSelection(this.items[i]));
    }
    for (i=0; i < this.subItems.length; i++) {
      this.subItems[i]._updateUI();
    }
  },
  /** @private */
  changeSelection : function(item) {
    for (var i=0; i < this.subItems.length; i++) {
      this.subItems[i].selectionChanged(this.selection,item);
    }
    this.selection = item;
    this._updateUI();
    this.fireChange();
  },
  /** @private */
  fireChange : function() {
    this.fire('select',this.selection);
    this.fireProperty('value',this.selection ? this.selection.value : null);
    this.fireProperty('kind',this.selection ? this.selection.kind : null);
    for (var i=0; i < this.subItems.length; i++) {
      this.subItems[i].parentValueChanged();
    }
  },
  /** @private */
  registerItems : function(items) {
    items.parent = this;
    this.subItems.push(items);
  },
  /** @private */
  addItemBehavior : function(node,item) {
    hui.listen(node,'click',function() {
      this.itemWasClicked(item);
    }.bind(this));
    hui.listen(node,'dblclick',function(e) {
      hui.stop(e);
      hui.selection.clear();
      this._onDoubleClick(item);
    }.bind(this));
    node.dragDropInfo = item;
  },
  /** Untested!! */
  setObjects : function(items) {
    this.items = [];
    hui.each(items,function(item) {
      this.items.push(item);
      var node = hui.build('div',{'class':'hui_selection_item'});
      item.element = node;
      this.element.appendChild(node);
      var inner = hui.build('span',{'class':'hui_selection_label',text:item.title || item.text || ''});
      if (item.icon) {
        node.appendChild(hui.ui.createIcon(item.icon,16));
      }
      node.appendChild(inner);
      hui.listen(node,'click',function() {
        this.itemWasClicked(item);
      }.bind(this));
      hui.listen(node,'dblclick',function(e) {
        hui.stop(e);
        this._onDoubleClick(item);
      }.bind(this));
    }.bind(this));
    this.fireSizeChange();
  },
  /** @private */
  isSelection : function(item) {
    if (this.selection===null) {
      return false;
    }
    var selected = item.value==this.selection.value;
    if (this.selection.kind) {
      selected = selected && item.kind==this.selection.kind;
    }
    return selected;
  },

  /** @private */
  itemWasClicked : function(item) {
    if (this.busy>0) {return;}
    this.changeSelection(item);
  },
  _onDoubleClick : function(item) {
    if (this.busy>0) {return;}
    this.fire('open',item);
  },
  _setBusy : function(busy) {
    this.busy+= busy ? 1 : -1;
    window.clearTimeout(this.busytimer);
    if (this.busy>0) {
      var e = this.element;
      this.busytimer = window.setTimeout(function() {
        hui.cls.add(e,'hui_selection_busy');
      },300);
    } else {
      hui.cls.remove(this.element,'hui_selection_busy');
      this.fire('loaded');
    }
  },
  _checkValue : function() {
    if (!this.selection) {return;}
    var item = this._getSelectionWithValue(this.selection.value);
    if (!item) {
      hui.log('Value not found: '+this.selection.value);
      if (!this.busy) {
        this.selectFirst();
      } else {
        hui.log('Will not select first since im still busy');
      }
    }
  },
  show : function() {
    this.element.style.display='';
  },
  hide : function() {
    this.element.style.display='none';
  }
};

/////////////////////////// Items ///////////////////////////

/**
 * A group of items loaded from a source
 * @constructor
 * @param {Object} options The options : {element,name,source}
 */
hui.ui.Selection.Items = function(options) {
  this.options = hui.override({source:null},options);
  this.element = hui.get(options.element);
  this.title = hui.get(this.element.id+'_title');
  this.name = options.name;
  this.disclosed = {};
  this.parent = null;
  this.items = [];
  hui.ui.extend(this);
  if (this.options.source) {
    this.options.source.listen(this);
  }
};

hui.ui.Selection.Items.prototype = {
  /**
   * Refresh the underlying source
   */
  refresh : function() {
    if (this.options.source) {
      this.options.source.refresh();
    }
  },
  setOptions : function(options) {
    this.$optionsLoaded(options);
  },
  /** @private */
  $objectsLoaded : function(objects) {
    this.$optionsLoaded(objects);
  },
  /** @private */
  $optionsLoaded : function(items) {
    this.items = [];
    this.element.innerHTML='';
    this.buildLevel(this.element,items,0,true);
    if (this.title) {
      this.title.style.display=this.items.length>0 ? 'block' : 'none';
    }
    this.parent._updateUI();
    this.parent._checkValue();
    this.fireSizeChange();
  },
  $sourceIsBusy : function() {
    this.parent._setBusy(true);
  },
  /** @private */
  $sourceIsNotBusy : function() {
    this.parent._setBusy(false);
  },
  /** @private */
  $sourceShouldRefresh : function() {
    return hui.dom.isVisible(this.element);
  },
  /** @private */
  $visibilityChanged : function() {
    if (hui.dom.isVisible(this.element)) {
      if (this.options.source) {
        // If there is a source, make sure it is initially
        this.options.source.refreshFirst();
      }
    }
  },
  /** @private */
  buildLevel : function(parent,items,inc,open) {
    if (!items) return;
    var hierarchical = this.isHierarchy(items);
    var level = hui.build('div',{'class':'hui_selection_level',style:(open ? 'display:block' : 'display:none'),parent:parent});
    hui.each(items,function(item) {
      var text = item.text || item.title || '';
      if (item.type=='title') {
        hui.build('div',{'class':'hui_selection_title',html:'<span>'+text+'</span>',parent:level});
        return;
      }
      var hasChildren = item.children && item.children.length>0;
      var left = inc*16+6;
      if (!hierarchical && inc>0 || hierarchical && !hasChildren) {
        left+=13;
      }
      var node = hui.build('div',{'class':'hui_selection_item'});
      node.style.paddingLeft = left+'px';
      if (item.badge) {
        node.appendChild(hui.build('strong',{'class':'hui_selection_badge',text:item.badge}));
      }
      var subOpen = false;
      if (hierarchical && hasChildren) {
        var self = this;
        subOpen = this.disclosed[item.value];
        var cls = this.disclosed[item.value] ? 'hui_disclosure hui_disclosure_open' : 'hui_disclosure';
        var disc = hui.build('span', {'class': cls, parent: node});
        hui.listen(disc,'click',function(e) {
          hui.stop(e);
          self.toggle(disc,item);
        });
      }
      var rendition = hui.ui.callDelegates(this.parent, 'render', item);
      if (rendition) {
        node.appendChild(rendition);
      } else {
        var inner = hui.build('span', {'class':'hui_selection_label', text: text});
        if (item.icon) {
          node.appendChild(hui.ui.createIcon(item.icon, 16));
        }
        node.appendChild(inner);        
      }
      hui.listen(node,'click',function(e) {
        this.parent.itemWasClicked(item);
      }.bind(this));
      hui.listen(node,'dblclick',function(e) {
        hui.stop(e);
        hui.selection.clear();
        this.parent._onDoubleClick(item);
      }.bind(this));
      level.appendChild(node);
      var info = {title:text,icon:item.icon,badge:item.badge,kind:item.kind,element:node,value:item.value};
      node.dragDropInfo = info;
      this.items.push(info);
      this.buildLevel(level,item.children,inc+1,subOpen);
    }.bind(this));
  },
  /** @private */
  toggle : function(node,item) {
    if (hui.cls.has(node,'hui_disclosure_open')) {
      this.disclosed[item.value] = false;
      hui.get.next(node.parentNode).style.display='none';
      hui.cls.remove(node,'hui_disclosure_open');
    } else {
      this.disclosed[item.value] = true;
      hui.get.next(node.parentNode).style.display='block';
      hui.cls.add(node,'hui_disclosure_open');
    }
    this.parent.fireSizeChange();
  },
  /** @private */
  isHierarchy : function(items) {
    if (!items) {return false;}
    for (var i=0; i < items.length; i++) {
      if (items[i]!==null && items[i].children && items[i].children.length>0) {
        return true;
      }
    }
    return false;
  },
  /** Get the selection of this items group
   * @returns {Object} The selected item or null */
  getValue : function() {
    if (this.parent.selection === null) {
      return null;
    }
    for (var i=0; i < this.items.length; i++) {
      if (this.items[i].value == this.parent.selection.value) {
        return this.items[i];
      }
    }
    return null;
  },
  _updateUI : function() {
    for (var i=0; i < this.items.length; i++) {
      hui.cls.set(this.items[i].element,'hui_selected',this.parent.isSelection(this.items[i]));
    }
  },
  /** @private */
  selectionChanged : function(oldSelection,newSelection) {
    for (var i=0; i < this.items.length; i++) {
      var value = this.items[i].value;
      if (value == newSelection.value) {
        this.fireProperty('value',newSelection.value);
        return;
      }
    }
    this.fireProperty('value',null);
  },
  /**
   * Called when the parent changes value, must fire its new value
   * @private
   */
  parentValueChanged : function() {
    for (var i=0; i < this.items.length; i++) {
      if (this.parent.isSelection(this.items[i])) {
        this.fireProperty('value',this.items[i].value);
        return;
      }
    }
    this.fireProperty('value',null);
  }
};