// ========================================================================
// SproutCore
// copyright 2006-2007 Sprout Systems, Inc.
// ========================================================================
require('views/field') ;
// SelectFieldView displays browser-native popup menu. To use this view,
// you should either bake into the HTML the preset list of options, or
// you can set the -objects property to an array of items to show. The
// value is current value of the select.
SC.SelectFieldView = SC.FieldView.extend({
emptyElement: '',
// set this property to an array of items that will form the menu
// you want to show.
objects: null,
// if you set this to a non-null value, then the name shown for each
// menu item will be pulled from the object using the named property.
// if this is null, the collection objects themselves will be used.
nameKey: null,
// if you set this to a non-null value, then the value of this key will
// be used to sort the objects. If this is not set, then nameKey will
// be used.
sortKey: null,
// set this to a non-null value to use a key from the passed set of objects
// as the value for the options popup. If you don't set this, then the
// objects themselves will be used as the value.
valueKey: null,
// set this to non-null to place an empty option at the top of the menu.
emptyName: null,
// if true, the empty name will be localized.
localize: false,
// override this to change the enabled/disabled state of menu items as they
// are built. Return false if you want the menu item to be disabled.
validateMenuItem: function(itemValue, itemName) {
return true ;
},
// override this method to implement your own sorting of the menu. By
// default, menu items are sorted using the value shown.
sortObjects: function(objects) {
var nameKey = this.get('sortKey') || this.get('nameKey') ;
objects = objects.sort(function(a,b) {
if (nameKey) {
a = (a.get) ? a.get(nameKey) : a[nameKey] ;
b = (b.get) ? b.get(nameKey) : b[nameKey] ;
}
return (ab) ? 1 : 0) ;
}) ;
return objects ;
},
// call this method to rebuild the menu manually. Normally you should not
// need to do this since the menu will be rebuilt as its data changes.
rebuildMenu: function() {
this._rebuildMenu() ;
},
mouseDown: function(e)
{
e._stopWhenHandled = false;
return false;
},
// .......................................
// PRIVATE
//
// when fetching the raw value, convert back to an object if needed...
getFieldValue: function() {
var value = this.rootElement.value ; // get raw value...
var valueKey = this.get('valueKey') ;
var objects = this.get('objects') ;
// Handle empty selection.
if (value == '***') {
value = null ;
// If no value key was set and there are objects then match back to an
// object.
} else if (value && objects) {
objects = Array.from(objects) ;
var loc = objects.length ;
var found = null ; // matching object goes here.
while(!found && (--loc >= 0)) {
var object = objects[loc] ;
// get value using valueKey if there is one or use object
// map to _guid or toString.
if (valueKey) object = (object.get) ? object.get(valueKey) : object[valueKey] ;
ov = (object) ? ((object._guid) ? object._guid : object.toString()) : null ;
// use this object value if it matches.
if (value == ov) found = object ;
}
}
return value ;
},
// when setting the raw value, convert from object...
setFieldValue: function(nv) {
if (nv) {
nv = (nv._guid) ? nv._guid : nv.toString() ;
} else {
nv = "***" ;
}
if (this.rootElement.value != nv) this.rootElement.value = nv ;
},
// this method is called anytime the objects property or any of its member
// objects change.
_rebuildMenu: function() {
// get list of objects.
var nameKey = this.get('nameKey') ;
var valueKey = this.get('valueKey') ;
var objects = this.get('objects') ;
var fieldValue = this.get('value') ;
// convert fieldValue to guid, if it is an object.
if (!valueKey && fieldValue) fieldValue = fieldValue._guid ;
if ((fieldValue == null) || (fieldValue == '')) fieldValue = '***' ;
if (objects) {
objects = Array.from(objects) ; // make array.
objects = this.sortObjects(objects) ; // sort'em.
var html = [] ;
var emptyName = this.get('emptyName') ;
if (emptyName) {
if (this.get('localize')) emptyName = emptyName.loc() ;
html.push(''.fmt(emptyName)) ;
html.push('') ;
}
// generate option elements.
objects.each(function(object) {
if (object) {
// either get the name from the object or convert object to string.
var name = (nameKey) ? ((object.get) ? object.get(nameKey) : object[nameKey]) : object.toString() ;
// get the value using the valueKey or the object if no valueKey.
// then convert to a string or use _guid if one of available.
var value = (valueKey) ? ((object.get) ? object.get(valueKey) : object[valueKey]) : object ;
if (value) value = (value._guid) ? value._guid : value.toString() ;
// render HTML
var disable = (this.validateMenuItem && this.validateMenuItem(value, name)) ? '' : 'disabled="disabled" ' ;
html.push(''.fmt(disable,value,name)) ;
// null value means separator.
} else {
html.push('') ;
}
}.bind(this) );
// replace the contents of this HTML element.
this.update(html.join(""));
this.rootElement.value = fieldValue ;
} else {
this.set('value',null);
}
},
// object changes to the objects array of objects if possible.
_objectsObserver: function() {
if (!this._boundObserver) {
this._boundObserver = this._objectsItemObserver.bind(this) ;
}
if (this.didChangeFor('_objO','objects','nameKey','valueKey')) {
var loc ;
var objects = Array.from(this.get('objects')) ;
var func = this._boundObserver ;
// stop observing old objects.
if (this._objects) {
loc = this._objects.length ;
while(--loc >= 0) {
var object = this._objects[loc] ;
if (object && object.removeObserver) {
if (this._nameKey && this._valueKey) {
object.removeObserver(this._nameKey, func) ;
object.removeObserver(this._valueKey, func) ;
} else {
object.removeObserver('*', func) ;
} // if (this._nameKey)
} // if (object &&...)
} // while(--loc)
} // if (this._objects)
// start observing new objects.
this._objects = objects ;
this._nameKey = this.get('nameKey') ;
this._valueKey = this.get('valueKey') ;
if (this._objects) {
loc = this._objects.length ;
while(--loc >= 0) {
var object = this._objects[loc] ;
if (object && object.addObserver) {
if (this._nameKey && this._valueKey) {
object.addObserver(this._nameKey, func) ;
object.addObserver(this._valueKey, func) ;
} else {
object.addObserver('*', func) ;
} // if (this._nameKey)
} // if (object &&...)
} // while(--loc)
} // if (this._objects)
this._rebuildMenu() ;
} // if (this.didChangeFor...)
}.observes('objects','nameKey','valueKey'),
// this is invoked anytime an item we are interested in in the menu changes
// rebuild the menu when this happens, but only one time.
_objectsItemObserver: function(item, key, value) {
if (item.didChangeFor(this._guid, key)) {
console.log('rebuildMenu') ;
this._rebuildMenu() ;
}
},
_fieldDidFocus: function()
{
var isFocused = this.get('isFocused');
if (!isFocused) this.set('isFocused', true);
},
_fieldDidBlur: function()
{
var isFocused = this.get('isFocused');
if (isFocused) this.set('isFocused', false);
},
_isFocusedObserver: function()
{
var isFocused = this.get('isFocused');
this.setClassName('focus', isFocused);
}.observes('isFocused'),
init: function()
{
arguments.callee.base.call(this);
this._rebuildMenu();
var changeListener = this.fieldValueDidChange.bind(this,false);
Element.observe(this.rootElement, 'change', changeListener);
var focusListener = this._fieldDidFocus.bindAsEventListener(this);
Element.observe(this.rootElement, 'focus', focusListener);
var blurListener = this._fieldDidBlur.bindAsEventListener(this);
Element.observe(this.rootElement, 'blur', blurListener);
}
});