js_stack 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +2 -2
- data/lib/js_stack/version.rb +1 -1
- data/vendor/assets/javascripts/js_stack/base/underscore/1.8.2.js +1546 -0
- data/vendor/assets/javascripts/js_stack/base/underscore.js +1 -1
- data/vendor/assets/javascripts/js_stack/plugins/backbone/stickit/0.9.0.js +692 -0
- data/vendor/assets/javascripts/js_stack/plugins/backbone.stickit.js +1 -1
- metadata +5 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
//= require js_stack/base/underscore/1.
|
|
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.
|
|
1
|
+
//= require js_stack/plugins/backbone/stickit/0.9.0
|