Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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