Backbone.stickit handler that allows function as select label
Backbone.Stickit.addHandler({ | |
selector: 'select', | |
events: ['change'], | |
update: function($el, val, model, options) { | |
var optList, | |
selectConfig = options.selectOptions, | |
list = selectConfig && selectConfig.collection || undefined, | |
isMultiple = $el.prop('multiple'); | |
// If there are no `selectOptions` then we assume that the `<select>` | |
// is pre-rendered and that we need to generate the collection. | |
if (!selectConfig) { | |
selectConfig = {}; | |
var getList = function($el) { | |
return $el.map(function() { | |
return {value:this.value, label:this.text}; | |
}).get(); | |
}; | |
if ($el.find('optgroup').length) { | |
list = {opt_labels:[]}; | |
// Search for options without optgroup | |
if ($el.find('> option').length) { | |
list.opt_labels.push(undefined); | |
_.each($el.find('> option'), function(el) { | |
list[undefined] = getList(Backbone.$(el)); | |
}); | |
} | |
_.each($el.find('optgroup'), function(el) { | |
var label = Backbone.$(el).attr('label'); | |
list.opt_labels.push(label); | |
list[label] = getList(Backbone.$(el).find('option')); | |
}); | |
} else { | |
list = getList($el.find('option')); | |
} | |
} | |
// Fill in default label and path values. | |
selectConfig.valuePath = selectConfig.valuePath || 'value'; | |
selectConfig.labelPath = selectConfig.labelPath || 'label'; | |
var addSelectOptions = function(optList, $el, fieldVal) { | |
_.each(optList, function(obj) { | |
var option = Backbone.$('<option/>'), optionVal = obj; | |
var fillOption = function(text, val) { | |
option.text(text); | |
optionVal = val; | |
// Save the option value as data so that we can reference it later. | |
option.data('stickit_bind_val', optionVal); | |
if (!_.isArray(optionVal) && !_.isObject(optionVal)) option.val(optionVal); | |
}; | |
var text, val; | |
if (obj === '__default__') { | |
text = fieldVal.label, | |
val = fieldVal.value; | |
} else { | |
text = _.isFunction(selectConfig.labelPath) ? selectConfig.labelPath.call(this, obj) : evaluatePath(obj, selectConfig.labelPath), | |
val = evaluatePath(obj, selectConfig.valuePath); | |
} | |
fillOption(text, val); | |
// Determine if this option is selected. | |
var isSelected = function() { | |
if (!isMultiple && optionVal != null && fieldVal != null && optionVal === fieldVal) { | |
return true; | |
} else if (_.isObject(fieldVal) && _.isEqual(optionVal, fieldVal)) { | |
return true; | |
} | |
return false; | |
}; | |
if (isSelected()) { | |
option.prop('selected', true); | |
} else if (isMultiple && _.isArray(fieldVal)) { | |
_.each(fieldVal, function(val) { | |
if (_.isObject(val)) val = evaluatePath(val, selectConfig.valuePath); | |
if (val === optionVal || (_.isObject(val) && _.isEqual(optionVal, val))) | |
option.prop('selected', true); | |
}); | |
} | |
$el.append(option); | |
}); | |
}; | |
$el.find('*').remove(); | |
// The `list` configuration is a function that returns the options list or a string | |
// which represents the path to the list relative to `window` or the view/`this`. | |
if (_.isString(list)) { | |
var context = window; | |
if (list.indexOf('this.') === 0) context = this; | |
list = list.replace(/^[a-z]*\.(.+)$/, '$1'); | |
optList = evaluatePath(context, list); | |
} else if (_.isFunction(list)) { | |
optList = applyViewFn.call(this, list, $el, options); | |
} else { | |
optList = list; | |
} | |
// Support Backbone.Collection and deserialize. | |
if (optList instanceof Backbone.Collection) { | |
var collection = optList; | |
var refreshSelectOptions = function() { | |
var currentVal = getAttr(model, options.observe, options); | |
applyViewFn.call(this, options.update, $el, currentVal, model, options); | |
}; | |
// We need to call this function after unstickit and after an update so we don't end up | |
// with multiple listeners doing the same thing | |
var removeCollectionListeners = function() { | |
collection.off('add remove reset sort', refreshSelectOptions); | |
}; | |
var removeAllListeners = function() { | |
removeCollectionListeners(); | |
collection.off('stickit:selectRefresh'); | |
model.off('stickit:selectRefresh'); | |
}; | |
// Remove previously set event listeners by triggering a custom event | |
collection.trigger('stickit:selectRefresh'); | |
collection.once('stickit:selectRefresh', removeCollectionListeners, this); | |
// Listen to the collection and trigger an update of the select options | |
collection.on('add remove reset sort', refreshSelectOptions, this); | |
// Remove the previous model event listener | |
model.trigger('stickit:selectRefresh'); | |
model.once('stickit:selectRefresh', function() { | |
model.off('stickit:unstuck', removeAllListeners); | |
}); | |
// Remove collection event listeners once this binding is unstuck | |
model.once('stickit:unstuck', removeAllListeners, this); | |
optList = optList.toJSON(); | |
} | |
if (selectConfig.defaultOption) { | |
var option = _.isFunction(selectConfig.defaultOption) ? | |
selectConfig.defaultOption.call(this, $el, options) : | |
selectConfig.defaultOption; | |
addSelectOptions(["__default__"], $el, option); | |
} | |
if (_.isArray(optList)) { | |
addSelectOptions(optList, $el, val); | |
} else if (optList.opt_labels) { | |
// To define a select with optgroups, format selectOptions.collection as an object | |
// with an 'opt_labels' property, as in the following: | |
// | |
// { | |
// 'opt_labels': ['Looney Tunes', 'Three Stooges'], | |
// 'Looney Tunes': [{id: 1, name: 'Bugs Bunny'}, {id: 2, name: 'Donald Duck'}], | |
// 'Three Stooges': [{id: 3, name : 'moe'}, {id: 4, name : 'larry'}, {id: 5, name : 'curly'}] | |
// } | |
// | |
_.each(optList.opt_labels, function(label) { | |
var $group = Backbone.$('<optgroup/>').attr('label', label); | |
addSelectOptions(optList[label], $group, val); | |
$el.append($group); | |
}); | |
// With no 'opt_labels' parameter, the object is assumed to be a simple value-label map. | |
// Pass a selectOptions.comparator to override the default order of alphabetical by label. | |
} else { | |
var opts = [], opt; | |
for (var i in optList) { | |
opt = {}; | |
opt[selectConfig.valuePath] = i; | |
opt[selectConfig.labelPath] = optList[i]; | |
opts.push(opt); | |
} | |
opts = _.sortBy(opts, selectConfig.comparator || selectConfig.labelPath); | |
addSelectOptions(opts, $el, val); | |
} | |
}, | |
getVal: function($el) { | |
var selected = $el.find('option:selected'); | |
if ($el.prop('multiple')) { | |
return _.map(selected, function(el) { | |
return Backbone.$(el).data('stickit_bind_val'); | |
}); | |
} else { | |
return selected.data('stickit_bind_val'); | |
} | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment