js_stack 1.8.0 → 1.9.0

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