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.