PageSourceSearch

https://hpgames.jp/shinomas/share/js/lib/backbone.stickit.js

js hpgames.jp collected 2026-09-24 09:33:04 UTC 26,924 bytes, 711 lines download raw bytes

1// Backbone.Stickit v0.9.2, MIT Licensed
2// Copyright (c) 2012-2015 The New York Times, CMS Group, Matthew DeLambo <[email protected]>
3
4(function (factory) {
5
6  // Set up Stickit appropriately for the environment. Start with AMD.
7  if (typeof define === 'function' && define.amd)
8    define(['underscore', 'backbone', 'exports'], factory);
9
10  // Next for Node.js or CommonJS.
11  else if (typeof exports === 'object')
12    factory(require('underscore'), require('backbone'), exports);
13
14  // Finally, as a browser global.
15  else
16    factory(_, Backbone, {});
17
18}(function (_, Backbone, Stickit) {
19
20  // Stickit Namespace
21  // --------------------------
22
23  // Export onto Backbone object
24  Backbone.Stickit = Stickit;
25
26  Stickit._handlers = [];
27
28  Stickit.addHandler = function(handlers) {
29    // Fill-in default values.
30    handlers = _.map(_.flatten([handlers]), function(handler) {
31      return _.defaults({}, handler, {
32        updateModel: true,
33        updateView: true,
34        updateMethod: 'text'
35      });
36    });
37    this._handlers = this._handlers.concat(handlers);
38  };
39
40  // Backbone.View Mixins
41  // --------------------
42
43  Stickit.ViewMixin = {
44
45    // Collection of model event bindings.
46    //   [{model,event,fn,config}, ...]
47    _modelBindings: null,
48
49    // Unbind the model and event bindings from `this._modelBindings` and
50    // `this.$el`. If the optional `model` parameter is defined, then only
51    // delete bindings for the given `model` and its corresponding view events.
52    unstickit: function(model, bindingSelector) {
53
54      // Support passing a bindings hash in place of bindingSelector.
55      if (_.isObject(bindingSelector)) {
56        _.each(bindingSelector, _.bind(function(v, selector) {
57          this.unstickit(model, selector);
58        }, this));
59        return;
60      }
61
62      var models = [], destroyFns = [];
63      this._modelBindings = _.reject(this._modelBindings, function(binding) {
64        if (model && binding.model !== model) return;
65        if (bindingSelector && binding.config.selector != bindingSelector) return;
66
67        binding.model.off(binding.event, binding.fn);
68        destroyFns.push(binding.config._destroy);
69        models.push(binding.model);
70        return true;
71      });
72
73      // Trigger an event for each model that was unbound.
74      _.each(_.uniq(models), _.bind(function (model) {
75        model.trigger('stickit:unstuck', this.cid);
76      }, this));
77
78      // Call `_destroy` on a unique list of the binding callbacks.
79      _.each(_.uniq(destroyFns), _.bind(function(fn) { fn.call(this); }, this));
80
81      this.$el.off('.stickit' + (model ? '.' + model.cid : ''), bindingSelector);
82    },
83
84    // Initilize Stickit bindings for the view. Subsequent binding additions
85    // can either call `stickit` with the new bindings, or add them directly
86    // with `addBinding`. Both arguments to `stickit` are optional.
87    stickit: function(optionalModel, optionalBindingsConfig) {
88      var model = optionalModel || this.model,
89          bindings = optionalBindingsConfig || _.result(this, "bindings") || {};
90
91      this._modelBindings || (this._modelBindings = []);
92
93      // Add bindings in bulk using `addBinding`.
94      this.addBinding(model, bindings);
95
96      // Wrap `view.remove` to unbind stickit model and dom events.
97      var remove = this.remove;
98      if (!remove.stickitWrapped) {
99        this.remove = function() {
100          var ret = this;
101          this.unstickit();
102          if (remove) ret = remove.apply(this, arguments);
103          return ret;
104        };
105      }
106      this.remove.stickitWrapped = true;
107      return this;
108    },
109
110    // Add a single Stickit binding or a hash of bindings to the model. If
111    // `optionalModel` is ommitted, will default to the view's `model` property.
112    addBinding: function(optionalModel, selector, binding) {
113      var model = optionalModel || this.model,
114          namespace = '.stickit.' + model.cid;
115
116      binding = binding || {};
117
118      // Support jQuery-style {key: val} event maps.
119      if (_.isObject(selector)) {
120        var bindings = selector;
121        _.each(bindings, _.bind(function(val, key) {
122          this.addBinding(model, key, val);
123        }, this));
124        return;
125      }
126
127      // Special case the ':el' selector to use the view's this.$el.
128      var $el = selector === ':el' ? this.$el : this.$(selector);
129
130      // Clear any previous matching bindings.
131      this.unstickit(model, selector);
132
133      // Fail fast if the selector didn't match an element.
134      if (!$el.length) return;
135
136      // Allow shorthand setting of model attributes - `'selector':'observe'`.
137      if (_.isString(binding)) binding = {observe: binding};
138
139      // Handle case where `observe` is in the form of a function.
140      if (_.isFunction(binding.observe)) binding.observe = binding.observe.call(this);
141
142      // Find all matching Stickit handlers that could apply to this element
143      // and store in a config object.
144      var config = getConfiguration($el, binding);
145
146      // The attribute we're observing in our config.
147      var modelAttr = config.observe;
148
149      // Store needed properties for later.
150      config.selector = selector;
151      config.view = this;
152
153      // Create the model set options with a unique `bindId` so that we
154      // can avoid double-binding in the `change:attribute` event handler.
155      var bindId = config.bindId = _.uniqueId();
156
157      // Add a reference to the view for handlers of stickitChange events
158      var options = _.extend({stickitChange: config}, config.setOptions);
159
160      // Add a `_destroy` callback to the configuration, in case `destroy`
161      // is a named function and we need a unique function when unsticking.
162      config._destroy = function() {
163        applyViewFn.call(this, config.destroy, $el, model, config);
164      };
165
166      initializeAttributes($el, config, model, modelAttr);
167      initializeVisible($el, config, model, modelAttr);
168      initializeClasses($el, config, model, modelAttr);
169
170      if (modelAttr) {
171        // Setup one-way (input element -> model) bindings.
172        _.each(config.events, _.bind(function(type) {
173          var eventName = type + namespace;
174          var listener = function(event) {
175            var val = applyViewFn.call(this, config.getVal, $el, event, config, slice.call(arguments, 1));
176
177            // Don't update the model if false is returned from the `updateModel` configuration.
178            var currentVal = evaluateBoolean(config.updateModel, val, event, config);
179            if (currentVal) setAttr(model, modelAttr, val, options, config);
180          };
181          var sel = selector === ':el'? '' : selector;
182          this.$el.on(eventName, sel, _.bind(listener, this));
183        }, this));
184
185        // Setup a `change:modelAttr` observer to keep the view element in sync.
186        // `modelAttr` may be an array of attributes or a single string value.
187        _.each(_.flatten([modelAttr]), function(attr) {
188          observeModelEvent(model, 'change:' + attr, config, function(m, val, options) {
189            var changeId = options && options.stickitChange && options.stickitChange.bindId;
190            if (changeId !== bindId) {
191              var currentVal = getAttr(model, modelAttr, config);
192              updateViewBindEl($el, config, currentVal, model);
193            }
194          });
195        });
196
197        var currentVal = getAttr(model, modelAttr, config);
198        updateViewBindEl($el, config, currentVal, model, true);
199      }
200
201      // After each binding is setup, call the `initialize` callback.
202      applyViewFn.call(this, config.initialize, $el, model, config);
203    }
204  };
205
206  _.extend(Backbone.View.prototype, Stickit.ViewMixin);
207
208  // Helpers
209  // -------
210
211  var slice = [].slice;
212
213  // Evaluates the given `path` (in object/dot-notation) relative to the given
214  // `obj`. If the path is null/undefined, then the given `obj` is returned.
215  var evaluatePath = function(obj, path) {
216    var parts = (path || '').split('.');
217    var result = _.reduce(parts, function(memo, i) { return memo[i]; }, obj);
218    return result == null ? obj : result;
219  };
220
221  // If the given `fn` is a string, then view[fn] is called, otherwise it is
222  // a function that should be executed.
223  var applyViewFn = function(fn) {
224    fn = _.isString(fn) ? evaluatePath(this, fn) : fn;
225    if (fn) return (fn).apply(this, slice.call(arguments, 1));
226  };
227
228  // Given a function, string (view function reference), or a boolean
229  // value, returns the truthy result. Any other types evaluate as false.
230  // The first argument must be `reference` and the last must be `config`, but
231  // middle arguments can be variadic.
232  var evaluateBoolean = function(reference, val, config) {
233    if (_.isBoolean(reference)) {
234      return reference;
235    } else if (_.isFunction(reference) || _.isString(reference)) {
236      var view = _.last(arguments).view;
237      return applyViewFn.apply(view, arguments);
238    }
239    return false;
240  };
241
242  // Setup a model event binding with the given function, and track the event
243  // in the view's _modelBindings.
244  var observeModelEvent = function(model, event, config, fn) {
245    var view = config.view;
246    model.on(event, fn, view);
247    view._modelBindings.push({model:model, event:event, fn:fn, config:config});
248  };
249
250  // Prepares the given `val`ue and sets it into the `model`.
251  var setAttr = function(model, attr, val, options, config) {
252    var value = {}, view = config.view;
253    if (config.onSet) {
254      val = applyViewFn.call(view, config.onSet, val, config);
255    }
256
257    if (config.set) {
258      applyViewFn.call(view, config.set, attr, val, options, config);
259    } else {
260      value[attr] = val;
261      // If `observe` is defined as an array and `onSet` returned
262      // an array, then map attributes to their values.
263      if (_.isArray(attr) && _.isArray(val)) {
264        value = _.reduce(attr, function(memo, attribute, index) {
265          memo[attribute] = _.has(val, index) ? val[index] : null;
266          return memo;
267        }, {});
268      }
269      model.set(value, options);
270    }
271  };
272
273  // Returns the given `attr`'s value from the `model`, escaping and
274  // formatting if necessary. If `attr` is an array, then an array of
275  // respective values will be returned.
276  var getAttr = function(model, attr, config) {
277    var view = config.view;
278    var retrieveVal = function(field) {
279      return model[config.escape ? 'escape' : 'get'](field);
280    };
281    var sanitizeVal = function(val) {
282      return val == null ? '' : val;
283    };
284    var val = _.isArray(attr) ? _.map(attr, retrieveVal) : retrieveVal(attr);
285    if (config.onGet) val = applyViewFn.call(view, config.onGet, val, config);
286    return _.isArray(val) ? _.map(val, sanitizeVal) : sanitizeVal(val);
287  };
288
289  // Find handlers in `Backbone.Stickit._handlers` with selectors that match
290  // `$el` and generate a configuration by mixing them in the order that they
291  // were found with the given `binding`.
292  var getConfiguration = Stickit.getConfiguration = function($el, binding) {
293    var handlers = [{
294      updateModel: false,
295      updateMethod: 'text',
296      update: function($el, val, m, opts) { if ($el[opts.updateMethod]) $el[opts.updateMethod](val); },
297      getVal: function($el, e, opts) { return $el[opts.updateMethod](); }
298    }];
299    handlers = handlers.concat(_.filter(Stickit._handlers, function(handler) {
300      return $el.is(handler.selector);
301    }));
302    handlers.push(binding);
303
304    // Merge handlers into a single config object. Last props in wins.
305    var config = _.extend.apply(_, handlers);
306
307    // `updateView` is defaulted to false for configutrations with
308    // `visible`; otherwise, `updateView` is defaulted to true.
309    if (!_.has(config, 'updateView')) config.updateView = !config.visible;
310    return config;
311  };
312
313  // Setup the attributes configuration - a list that maps an attribute or
314  // property `name`, to an `observe`d model attribute, using an optional
315  // `onGet` formatter.
316  //
317  //     attributes: [{
318  //       name: 'attributeOrPropertyName',
319  //       observe: 'modelAttrName'
320  //       onGet: function(modelAttrVal, modelAttrName) { ... }
321  //     }, ...]
322  //
323  var initializeAttributes = function($el, config, model, modelAttr) {
324    var props = ['autofocus', 'autoplay', 'async', 'checked', 'controls',
325      'defer', 'disabled', 'hidden', 'indeterminate', 'loop', 'multiple',
326      'open', 'readonly', 'required', 'scoped', 'selected'];
327
328    var view = config.view;
329
330    _.each(config.attributes || [], function(attrConfig) {
331      attrConfig = _.clone(attrConfig);
332      attrConfig.view = view;
333
334      var lastClass = '';
335      var observed = attrConfig.observe || (attrConfig.observe = modelAttr);
336      var updateAttr = function() {
337        var updateType = _.includes(props, attrConfig.name) ? 'prop' : 'attr',
338            val = getAttr(model, observed, attrConfig);
339
340        // If it is a class then we need to remove the last value and add the new.
341        if (attrConfig.name === 'class') {
342          $el.removeClass(lastClass).addClass(val);
343          lastClass = val;
344        } else {
345          $el[updateType](attrConfig.name, val);
346        }
347      };
348
349      _.each(_.flatten([observed]), function(attr) {
350        observeModelEvent(model, 'change:' + attr, config, updateAttr);
351      });
352
353      // Initialize the matched element's state.
354      updateAttr();
355    });
356  };
357
358  var initializeClasses = function($el, config, model, modelAttr) {
359    _.each(config.classes || [], function(classConfig, name) {
360      if (_.isString(classConfig)) classConfig = {observe: classConfig};
361      classConfig.view = config.view;
362
363      var observed = classConfig.observe;
364      var updateClass = function() {
365        var val = getAttr(model, observed, classConfig);
366        $el.toggleClass(name, !!val);
367      };
368
369      _.each(_.flatten([observed]), function(attr) {
370        observeModelEvent(model, 'change:' + attr, config, updateClass);
371      });
372      updateClass();
373    });
374  };
375
376  // If `visible` is configured, then the view element will be shown/hidden
377  // based on the truthiness of the modelattr's value or the result of the
378  // given callback. If a `visibleFn` is also supplied, then that callback
379  // will be executed to manually handle showing/hiding the view element.
380  //
381  //     observe: 'isRight',
382  //     visible: true, // or function(val, options) {}
383  //     visibleFn: function($el, isVisible, options) {} // optional handler
384  //
385  var initializeVisible = function($el, config, model, modelAttr) {
386    if (config.visible == null) return;
387    var view = config.view;
388
389    var visibleCb = function() {
390      var visible = config.visible,
391          visibleFn = config.visibleFn,
392          val = getAttr(model, modelAttr, config),
393          isVisible = !!val;
394
395      // If `visible` is a function then it should return a boolean result to show/hide.
396      if (_.isFunction(visible) || _.isString(visible)) {
397        isVisible = !!applyViewFn.call(view, visible, val, config);
398      }
399
400      // Either use the custom `visibleFn`, if provided, or execute the standard show/hide.
401      if (visibleFn) {
402        applyViewFn.call(view, visibleFn, $el, isVisible, config);
403      } else {
404        $el.toggle(isVisible);
405      }
406    };
407
408    _.each(_.flatten([modelAttr]), function(attr) {
409      observeModelEvent(model, 'change:' + attr, config, visibleCb);
410    });
411
412    visibleCb();
413  };
414
415  // Update the value of `$el` using the given configuration and trigger the
416  // `afterUpdate` callback. This action may be blocked by `config.updateView`.
417  //
418  //     update: function($el, val, model, options) {},  // handler for updating
419  //     updateView: true, // defaults to true
420  //     afterUpdate: function($el, val, options) {} // optional callback
421  //
422  var updateViewBindEl = function($el, config, val, model, isInitializing) {
423    var view = config.view;
424    if (!evaluateBoolean(config.updateView, val, config)) return;
425    applyViewFn.call(view, config.update, $el, val, model, config);
426    if (!isInitializing) applyViewFn.call(view, config.afterUpdate, $el, val, config);
427  };
428
429  // Default Handlers
430  // ----------------
431
432  Stickit.addHandler([{
433    selector: '[contenteditable]',
434    updateMethod: 'html',
435    events: ['input', 'change']
436  }, {
437    selector: 'input',
438    events: ['propertychange', 'input', 'change'],
439    update: function($el, val) { $el.val(val); },
440    getVal: function($el) {
441      return $el.val();
442    }
443  }, {
444    selector: 'textarea',
445    events: ['propertychange', 'input', 'change'],
446    update: function($el, val) { $el.val(val); },
447    getVal: function($el) { return $el.val(); }
448  }, {
449    selector: 'input[type="radio"]',
450    events: ['change'],
451    update: function($el, val) {
452      // Because we use " in the value descriptor, we need to escape any
453      // quotes in the value itself otherwise jQuery/Sizzle complain that
454      // the selector is invalid.
455      var escapedVal = val.toString().replace(/"/g, '\\"');
456      $el.filter('[value="'+escapedVal+'"]').prop('checked', true);
457    },
458    getVal: function($el) {
459      return $el.filter(':checked').val();
460    }
461  }, {
462    selector: 'input[type="checkbox"]',
463    events: ['change'],
464    update: function($el, val, model, options) {
465      if ($el.length > 1) {
466        // There are multiple checkboxes so we need to go through them and check
467        // any that have value attributes that match what's in the array of `val`s.
468        val || (val = []);
469        $el.each(function(i, el) {
470          var checkbox = Backbone.$(el);
471          var checkboxVal = checkbox.val();
472          var checked = _.some(val, function(item) { return ''+item === checkboxVal; });
473          checkbox.prop('checked', checked);
474        });
475      } else {
476        var checked = _.isBoolean(val) ? val : ''+val === $el.val();
477        $el.prop('checked', checked);
478      }
479    },
480    getVal: function($el) {
481      var val;
482      if ($el.length > 1) {
483        val = _.reduce($el, function(memo, el) {
484          var checkbox = Backbone.$(el);
485          if (checkbox.prop('checked')) memo.push(checkbox.val());
486          return memo;
487        }, []);
488      } else {
489        val = $el.prop('checked');
490        // If the checkbox has a value attribute defined, then
491        // use that value. Most browsers use "on" as a default.
492        var boxval = $el.val();
493        if (boxval !== 'on' && boxval != null) {
494          val = val ? $el.val() : null;
495        }
496      }
497      return val;
498    }
499  }, {
500    selector: 'select',
501    events: ['change'],
502    update: function($el, val, model, options) {
503      var optList,
504        selectConfig = options.selectOptions,
505        list = selectConfig && selectConfig.collection || undefined,
506        isMultiple = $el.prop('multiple');
507
508      // If there are no `selectOptions` then we assume that the `<select>`
509      // is pre-rendered and that we need to generate the collection.
510      if (!selectConfig) {
511        selectConfig = {};
512        var getList = function($el) {
513          return $el.map(function(index, option) {
514            // Retrieve the text and value of the option, preferring "stickit-bind-val"
515            // data attribute over value property.
516            var dataVal = Backbone.$(option).data('stickit-bind-val');
517            return {
518              value: dataVal !== undefined ? dataVal : option.value,
519              label: option.text
520            };
521          }).get();
522        };
523        if ($el.find('optgroup').length) {
524          list = {opt_labels:[]};
525          // Search for options without optgroup
526          if ($el.find('> option').length) {
527            list.opt_labels.push(undefined);
528            _.each($el.find('> option'), function(el) {
529              list[undefined] = getList(Backbone.$(el));
530            });
531          }
532          _.each($el.find('optgroup'), function(el) {
533            var label = Backbone.$(el).attr('label');
534            list.opt_labels.push(label);
535            list[label] = getList(Backbone.$(el).find('option'));
536          });
537        } else {
538          list = getList($el.find('option'));
539        }
540      }
541
542      // Fill in default label and path values.
543      selectConfig.valuePath = selectConfig.valuePath || 'value';
544      selectConfig.labelPath = selectConfig.labelPath || 'label';
545      selectConfig.disabledPath = selectConfig.disabledPath || 'disabled';
546
547      var addSelectOptions = function(optList, $el, fieldVal) {
548        var fragment = document.createDocumentFragment();
549
550        _.each(optList, function(obj) {
551          var option = Backbone.$('<option/>'), optionVal = obj;
552
553          var fillOption = function(text, val, disabled) {
554            option.text(text);
555            optionVal = val;
556            // Save the option value as data so that we can reference it later.
557            option.data('stickit-bind-val', optionVal);
558            if (!_.isArray(optionVal) && !_.isObject(optionVal)) option.val(optionVal);
559
560            if (disabled === true) option.prop('disabled', 'disabled');
561          };
562
563          var text, val, disabled;
564          if (obj === '__default__') {
565            text = fieldVal.label,
566            val = fieldVal.value,
567            disabled = fieldVal.disabled;
568          } else {
569            text = evaluatePath(obj, selectConfig.labelPath),
570            val = evaluatePath(obj, selectConfig.valuePath),
571            disabled = evaluatePath(obj, selectConfig.disabledPath);
572          }
573          fillOption(text, val, disabled);
574
575          // Determine if this option is selected.
576          var isSelected = function() {
577            if (!isMultiple && optionVal != null && fieldVal != null && optionVal === fieldVal) {
578              return true;
579            } else if (_.isObject(fieldVal) && _.isEqual(optionVal, fieldVal)) {
580              return true;
581            }
582            return false;
583          };
584
585          if (isSelected()) {
586            option.prop('selected', true);
587          } else if (isMultiple && _.isArray(fieldVal)) {
588            _.each(fieldVal, function(val) {
589              if (_.isObject(val)) val = evaluatePath(val, selectConfig.valuePath);
590              if (val === optionVal || (_.isObject(val) && _.isEqual(optionVal, val)))
591                option.prop('selected', true);
592            });
593          }
594
595          option.appendTo(fragment);
596        });
597
598        $el.append(fragment);
599      };
600
601      $el.empty();
602
603      // The `list` configuration is a function that returns the options list or a string
604      // which represents the path to the list relative to `window` or the view/`this`.
605      if (_.isString(list)) {
606        var context = window;
607        if (list.indexOf('this.') === 0) context = this;
608        list = list.replace(/^[a-z]*\.(.+)$/, '$1');
609        optList = evaluatePath(context, list);
610      } else if (_.isFunction(list)) {
611        optList = applyViewFn.call(this, list, $el, options);
612      } else {
613        optList = list;
614      }
615
616      // Support Backbone.Collection and deserialize.
617      if (optList instanceof Backbone.Collection) {
618        var collection = optList;
619        var refreshSelectOptions = function() {
620          var currentVal = getAttr(model, options.observe, options);
621          applyViewFn.call(this, options.update, $el, currentVal, model, options);
622        };
623        // We need to call this function after unstickit and after an update so we don't end up
624        // with multiple listeners doing the same thing
625        var removeCollectionListeners = function() {
626          collection.off('add remove reset sort', refreshSelectOptions);
627        };
628        var removeAllListeners = function() {
629          removeCollectionListeners();
630          collection.off('stickit:selectRefresh');
631          collection.off('stickit:selectRefreshForView');
632          model.off('stickit:selectRefresh');
633        };
634        collection.trigger('stickit:selectRefreshForView', this.cid);
635        // Compare the view cid on the event to this.cid to ensure we don't
636        // remove events from the collection that are tied to other views.
637        collection.once('stickit:selectRefreshForView', function(viewCid) {
638          if(this.cid == viewCid){
639            // Remove previously set event listeners by triggering a custom event
640            collection.trigger('stickit:selectRefresh');
641          }
642        }, this);
643        collection.once('stickit:selectRefresh', removeCollectionListeners, this);
644
645        // Listen to the collection and trigger an update of the select options
646        collection.on('add remove reset sort', refreshSelectOptions, this);
647
648        // Remove the previous model event listener
649        model.trigger('stickit:selectRefresh');
650        model.once('stickit:selectRefresh', function() {
651          model.off('stickit:unstuck', removeAllListeners);
652        });
653        // Remove collection event listeners once this binding is unstuck
654        model.once('stickit:unstuck', removeAllListeners, this);
655        optList = optList.toJSON();
656      }
657
658      if (selectConfig.defaultOption) {
659        var option = _.isFunction(selectConfig.defaultOption) ?
660          selectConfig.defaultOption.call(this, $el, options) :
661          selectConfig.defaultOption;
662        addSelectOptions(["__default__"], $el, option);
663      }
664
665      if (_.isArray(optList)) {
666        addSelectOptions(optList, $el, val);
667      } else if (optList.opt_labels) {
668        // To define a select with optgroups, format selectOptions.collection as an object
669        // with an 'opt_labels' property, as in the following:
670        //
671        //     {
672        //       'opt_labels': ['Looney Tunes', 'Three Stooges'],
673        //       'Looney Tunes': [{id: 1, name: 'Bugs Bunny'}, {id: 2, name: 'Donald Duck'}],
674        //       'Three Stooges': [{id: 3, name : 'moe'}, {id: 4, name : 'larry'}, {id: 5, name : 'curly'}]
675        //     }
676        //
677        _.each(optList.opt_labels, function(label) {
678          var $group = Backbone.$('<optgroup/>').attr('label', label);
679          addSelectOptions(optList[label], $group, val);
680          $el.append($group);
681        });
682        // With no 'opt_labels' parameter, the object is assumed to be a simple value-label map.
683        // Pass a selectOptions.comparator to override the default order of alphabetical by label.
684      } else {
685        var opts = [], opt;
686        for (var i in optList) {
687          opt = {};
688          opt[selectConfig.valuePath] = i;
689          opt[selectConfig.labelPath] = optList[i];
690          opts.push(opt);
691        }
692        opts = _.sortBy(opts, selectConfig.comparator || selectConfig.labelPath);
693        addSelectOptions(opts, $el, val);
694      }
695    },
696    getVal: function($el) {
697      var selected = $el.find('option:selected');
698
699      if ($el.prop('multiple')) {
700        return _.map(selected, function(el) {
701          return Backbone.$(el).data('stickit-bind-val');
702        });
703      } else {
704        return selected.data('stickit-bind-val');
705      }
706    }
707  }]);
708
709  return Stickit;
710
711}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.