knockoutjs-rails 2.2.1 → 2.3.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.
- data/README.md +1 -1
- data/lib/knockoutjs-rails/version.rb +1 -1
- data/vendor/assets/javascripts/knockout.js +347 -254
- metadata +3 -3
data/README.md
CHANGED
@@ -25,7 +25,7 @@ application.js):
|
|
25
25
|
|
26
26
|
## Versioning
|
27
27
|
|
28
|
-
knockoutjs-rails 2.
|
28
|
+
knockoutjs-rails 2.3.0 == Knockout.js 2.3.0
|
29
29
|
|
30
30
|
Every attempt is made to mirror the currently shipping Knockout.js version number wherever possible.
|
31
31
|
The major and minor version numbers will always represent the Knockout.js version, but the patch level
|
@@ -1,11 +1,18 @@
|
|
1
|
-
// Knockout JavaScript library v2.
|
1
|
+
// Knockout JavaScript library v2.3.0
|
2
2
|
// (c) Steven Sanderson - http://knockoutjs.com/
|
3
3
|
// License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
4
4
|
|
5
5
|
(function(){
|
6
6
|
var DEBUG=true;
|
7
|
-
(function(
|
8
|
-
|
7
|
+
(function(undefined){
|
8
|
+
// (0, eval)('this') is a robust way of getting a reference to the global object
|
9
|
+
// For details, see http://stackoverflow.com/questions/14119988/return-this-0-evalthis/14120023#14120023
|
10
|
+
var window = this || (0, eval)('this'),
|
11
|
+
document = window['document'],
|
12
|
+
navigator = window['navigator'],
|
13
|
+
jQuery = window["jQuery"],
|
14
|
+
JSON = window["JSON"];
|
15
|
+
(function(factory) {
|
9
16
|
// Support three module loading scenarios
|
10
17
|
if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
|
11
18
|
// [1] CommonJS/Node.js
|
@@ -37,38 +44,43 @@ ko.exportSymbol = function(koPath, object) {
|
|
37
44
|
ko.exportProperty = function(owner, publicName, object) {
|
38
45
|
owner[publicName] = object;
|
39
46
|
};
|
40
|
-
ko.version = "2.
|
47
|
+
ko.version = "2.3.0";
|
41
48
|
|
42
49
|
ko.exportSymbol('version', ko.version);
|
43
|
-
ko.utils =
|
44
|
-
var
|
50
|
+
ko.utils = (function () {
|
51
|
+
var objectForEach = function(obj, action) {
|
52
|
+
for (var prop in obj) {
|
53
|
+
if (obj.hasOwnProperty(prop)) {
|
54
|
+
action(prop, obj[prop]);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
};
|
45
58
|
|
46
59
|
// Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup)
|
47
60
|
var knownEvents = {}, knownEventTypesByEventName = {};
|
48
|
-
var keyEventTypeName = /Firefox\/2/i.test(navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents';
|
61
|
+
var keyEventTypeName = (navigator && /Firefox\/2/i.test(navigator.userAgent)) ? 'KeyboardEvent' : 'UIEvents';
|
49
62
|
knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
|
50
63
|
knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
|
51
|
-
|
52
|
-
var knownEventsForType = knownEvents[eventType];
|
64
|
+
objectForEach(knownEvents, function(eventType, knownEventsForType) {
|
53
65
|
if (knownEventsForType.length) {
|
54
66
|
for (var i = 0, j = knownEventsForType.length; i < j; i++)
|
55
67
|
knownEventTypesByEventName[knownEventsForType[i]] = eventType;
|
56
68
|
}
|
57
|
-
}
|
69
|
+
});
|
58
70
|
var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
|
59
71
|
|
60
72
|
// Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
|
61
73
|
// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
|
62
74
|
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
|
63
75
|
// If there is a future need to detect specific versions of IE10+, we will amend this.
|
64
|
-
var ieVersion = (function() {
|
76
|
+
var ieVersion = document && (function() {
|
65
77
|
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
|
66
78
|
|
67
79
|
// Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
|
68
80
|
while (
|
69
81
|
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
|
70
82
|
iElems[0]
|
71
|
-
)
|
83
|
+
) {}
|
72
84
|
return version > 4 ? version : undefined;
|
73
85
|
}());
|
74
86
|
var isIe6 = ieVersion === 6,
|
@@ -147,6 +159,17 @@ ko.utils = new (function () {
|
|
147
159
|
return array;
|
148
160
|
},
|
149
161
|
|
162
|
+
addOrRemoveItem: function(array, value, included) {
|
163
|
+
var existingEntryIndex = array.indexOf ? array.indexOf(value) : ko.utils.arrayIndexOf(array, value);
|
164
|
+
if (existingEntryIndex < 0) {
|
165
|
+
if (included)
|
166
|
+
array.push(value);
|
167
|
+
} else {
|
168
|
+
if (!included)
|
169
|
+
array.splice(existingEntryIndex, 1);
|
170
|
+
}
|
171
|
+
},
|
172
|
+
|
150
173
|
extend: function (target, source) {
|
151
174
|
if (source) {
|
152
175
|
for(var prop in source) {
|
@@ -158,6 +181,8 @@ ko.utils = new (function () {
|
|
158
181
|
return target;
|
159
182
|
},
|
160
183
|
|
184
|
+
objectForEach: objectForEach,
|
185
|
+
|
161
186
|
emptyDomNode: function (domNode) {
|
162
187
|
while (domNode.firstChild) {
|
163
188
|
ko.removeNode(domNode.firstChild);
|
@@ -214,7 +239,10 @@ ko.utils = new (function () {
|
|
214
239
|
},
|
215
240
|
|
216
241
|
stringTrim: function (string) {
|
217
|
-
return
|
242
|
+
return string === null || string === undefined ? '' :
|
243
|
+
string.trim ?
|
244
|
+
string.trim() :
|
245
|
+
string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
|
218
246
|
},
|
219
247
|
|
220
248
|
stringTokenize: function (string, delimiter) {
|
@@ -250,6 +278,10 @@ ko.utils = new (function () {
|
|
250
278
|
return ko.utils.domNodeIsContainedBy(node, node.ownerDocument);
|
251
279
|
},
|
252
280
|
|
281
|
+
anyDomNodeIsAttachedToDocument: function(nodes) {
|
282
|
+
return !!ko.utils.arrayFirst(nodes, ko.utils.domNodeIsAttachedToDocument);
|
283
|
+
},
|
284
|
+
|
253
285
|
tagNameLower: function(element) {
|
254
286
|
// For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case.
|
255
287
|
// Possible future optimization: If we know it's an element from an XHTML document (not HTML),
|
@@ -277,11 +309,17 @@ ko.utils = new (function () {
|
|
277
309
|
jQuery(element)['bind'](eventType, handler);
|
278
310
|
} else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
|
279
311
|
element.addEventListener(eventType, handler, false);
|
280
|
-
else if (typeof element.attachEvent != "undefined")
|
281
|
-
|
282
|
-
|
312
|
+
else if (typeof element.attachEvent != "undefined") {
|
313
|
+
var attachEventHandler = function (event) { handler.call(element, event); },
|
314
|
+
attachEventName = "on" + eventType;
|
315
|
+
element.attachEvent(attachEventName, attachEventHandler);
|
316
|
+
|
317
|
+
// IE does not dispose attachEvent handlers automatically (unlike with addEventListener)
|
318
|
+
// so to avoid leaks, we have to remove them manually. See bug #856
|
319
|
+
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
|
320
|
+
element.detachEvent(attachEventName, attachEventHandler);
|
283
321
|
});
|
284
|
-
else
|
322
|
+
} else
|
285
323
|
throw new Error("Browser doesn't support addEventListener or attachEvent");
|
286
324
|
},
|
287
325
|
|
@@ -326,17 +364,10 @@ ko.utils = new (function () {
|
|
326
364
|
|
327
365
|
toggleDomNodeCssClass: function (node, classNames, shouldHaveClass) {
|
328
366
|
if (classNames) {
|
329
|
-
var cssClassNameRegex =
|
367
|
+
var cssClassNameRegex = /\S+/g,
|
330
368
|
currentClassNames = node.className.match(cssClassNameRegex) || [];
|
331
369
|
ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
|
332
|
-
|
333
|
-
if (indexOfClass >= 0) {
|
334
|
-
if (!shouldHaveClass)
|
335
|
-
currentClassNames.splice(indexOfClass, 1);
|
336
|
-
} else {
|
337
|
-
if (shouldHaveClass)
|
338
|
-
currentClassNames.push(className);
|
339
|
-
}
|
370
|
+
ko.utils.addOrRemoveItem(currentClassNames, className, shouldHaveClass);
|
340
371
|
});
|
341
372
|
node.className = currentClassNames.join(" ");
|
342
373
|
}
|
@@ -347,21 +378,17 @@ ko.utils = new (function () {
|
|
347
378
|
if ((value === null) || (value === undefined))
|
348
379
|
value = "";
|
349
380
|
|
350
|
-
|
351
|
-
|
381
|
+
// We need there to be exactly one child: a text node.
|
382
|
+
// If there are no children, more than one, or if it's not a text node,
|
383
|
+
// we'll clear everything and create a single text node.
|
384
|
+
var innerTextNode = ko.virtualElements.firstChild(element);
|
385
|
+
if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) {
|
386
|
+
ko.virtualElements.setDomNodeChildren(element, [document.createTextNode(value)]);
|
352
387
|
} else {
|
353
|
-
|
354
|
-
// If there are no children, more than one, or if it's not a text node,
|
355
|
-
// we'll clear everything and create a single text node.
|
356
|
-
var innerTextNode = ko.virtualElements.firstChild(element);
|
357
|
-
if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) {
|
358
|
-
ko.virtualElements.setDomNodeChildren(element, [document.createTextNode(value)]);
|
359
|
-
} else {
|
360
|
-
innerTextNode.data = value;
|
361
|
-
}
|
362
|
-
|
363
|
-
ko.utils.forceRefresh(element);
|
388
|
+
innerTextNode.data = value;
|
364
389
|
}
|
390
|
+
|
391
|
+
ko.utils.forceRefresh(element);
|
365
392
|
},
|
366
393
|
|
367
394
|
setElementName: function(element, name) {
|
@@ -391,7 +418,8 @@ ko.utils = new (function () {
|
|
391
418
|
ensureSelectElementIsRenderedCorrectly: function(selectElement) {
|
392
419
|
// Workaround for IE9 rendering bug - it doesn't reliably display all the text in dynamically-added select boxes unless you force it to re-render by updating the width.
|
393
420
|
// (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
|
394
|
-
if (
|
421
|
+
// Also fixes IE7 and IE8 bug that causes selects to be zero width if enclosed by 'if' or 'with'. (See issue #839)
|
422
|
+
if (ieVersion) {
|
395
423
|
var originalWidth = selectElement.style.width;
|
396
424
|
selectElement.style.width = 0;
|
397
425
|
selectElement.style.width = originalWidth;
|
@@ -436,8 +464,8 @@ ko.utils = new (function () {
|
|
436
464
|
if (typeof jsonString == "string") {
|
437
465
|
jsonString = ko.utils.stringTrim(jsonString);
|
438
466
|
if (jsonString) {
|
439
|
-
if (
|
440
|
-
return
|
467
|
+
if (JSON && JSON.parse) // Use native parsing where available
|
468
|
+
return JSON.parse(jsonString);
|
441
469
|
return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
|
442
470
|
}
|
443
471
|
}
|
@@ -445,7 +473,7 @@ ko.utils = new (function () {
|
|
445
473
|
},
|
446
474
|
|
447
475
|
stringifyJson: function (data, replacer, space) { // replacer and space are optional
|
448
|
-
if (
|
476
|
+
if (!JSON || !JSON.stringify)
|
449
477
|
throw new Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
|
450
478
|
return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
|
451
479
|
},
|
@@ -473,23 +501,24 @@ ko.utils = new (function () {
|
|
473
501
|
form.action = url;
|
474
502
|
form.method = "post";
|
475
503
|
for (var key in data) {
|
504
|
+
// Since 'data' this is a model object, we include all properties including those inherited from its prototype
|
476
505
|
var input = document.createElement("input");
|
477
506
|
input.name = key;
|
478
507
|
input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
|
479
508
|
form.appendChild(input);
|
480
509
|
}
|
481
|
-
|
510
|
+
objectForEach(params, function(key, value) {
|
482
511
|
var input = document.createElement("input");
|
483
512
|
input.name = key;
|
484
|
-
input.value =
|
513
|
+
input.value = value;
|
485
514
|
form.appendChild(input);
|
486
|
-
}
|
515
|
+
});
|
487
516
|
document.body.appendChild(form);
|
488
517
|
options['submitter'] ? options['submitter'](form) : form.submit();
|
489
518
|
setTimeout(function () { form.parentNode.removeChild(form); }, 0);
|
490
519
|
}
|
491
520
|
}
|
492
|
-
}
|
521
|
+
}());
|
493
522
|
|
494
523
|
ko.exportSymbol('utils', ko.utils);
|
495
524
|
ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach);
|
@@ -512,6 +541,9 @@ ko.exportSymbol('utils.range', ko.utils.range);
|
|
512
541
|
ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);
|
513
542
|
ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
|
514
543
|
ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
|
544
|
+
ko.exportSymbol('utils.objectForEach', ko.utils.objectForEach);
|
545
|
+
ko.exportSymbol('utils.addOrRemoveItem', ko.utils.addOrRemoveItem);
|
546
|
+
ko.exportSymbol('unwrap', ko.utils.unwrapObservable); // Convenient shorthand, because this is used so commonly
|
515
547
|
|
516
548
|
if (!Function.prototype['bind']) {
|
517
549
|
// Function.prototype.bind is a standard part of ECMAScript 5th Edition (December 2009, http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf)
|
@@ -705,7 +737,7 @@ ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeD
|
|
705
737
|
function jQueryHtmlParse(html) {
|
706
738
|
// jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API.
|
707
739
|
if (jQuery['parseHTML']) {
|
708
|
-
return jQuery['parseHTML'](html);
|
740
|
+
return jQuery['parseHTML'](html) || []; // Ensure we always return an array and never null
|
709
741
|
} else {
|
710
742
|
// For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function.
|
711
743
|
var elems = jQuery['clean']([html]);
|
@@ -862,12 +894,12 @@ ko.extenders = {
|
|
862
894
|
function applyExtenders(requestedExtenders) {
|
863
895
|
var target = this;
|
864
896
|
if (requestedExtenders) {
|
865
|
-
|
897
|
+
ko.utils.objectForEach(requestedExtenders, function(key, value) {
|
866
898
|
var extenderHandler = ko.extenders[key];
|
867
899
|
if (typeof extenderHandler == 'function') {
|
868
|
-
target = extenderHandler(target,
|
900
|
+
target = extenderHandler(target, value);
|
869
901
|
}
|
870
|
-
}
|
902
|
+
});
|
871
903
|
}
|
872
904
|
return target;
|
873
905
|
}
|
@@ -927,10 +959,9 @@ ko.subscribable['fn'] = {
|
|
927
959
|
|
928
960
|
getSubscriptionsCount: function () {
|
929
961
|
var total = 0;
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
}
|
962
|
+
ko.utils.objectForEach(this._subscriptions, function(eventName, subscriptions) {
|
963
|
+
total += subscriptions.length;
|
964
|
+
});
|
934
965
|
return total;
|
935
966
|
},
|
936
967
|
|
@@ -939,7 +970,7 @@ ko.subscribable['fn'] = {
|
|
939
970
|
|
940
971
|
|
941
972
|
ko.isSubscribable = function (instance) {
|
942
|
-
return typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
|
973
|
+
return instance != null && typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
|
943
974
|
};
|
944
975
|
|
945
976
|
ko.exportSymbol('subscribable', ko.subscribable);
|
@@ -1052,17 +1083,15 @@ ko.exportSymbol('observable', ko.observable);
|
|
1052
1083
|
ko.exportSymbol('isObservable', ko.isObservable);
|
1053
1084
|
ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
|
1054
1085
|
ko.observableArray = function (initialValues) {
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
}
|
1059
|
-
if ((initialValues !== null) && (initialValues !== undefined) && !('length' in initialValues))
|
1086
|
+
initialValues = initialValues || [];
|
1087
|
+
|
1088
|
+
if (typeof initialValues != 'object' || !('length' in initialValues))
|
1060
1089
|
throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
|
1061
1090
|
|
1062
1091
|
var result = ko.observable(initialValues);
|
1063
1092
|
ko.utils.extend(result, ko.observableArray['fn']);
|
1064
1093
|
return result;
|
1065
|
-
}
|
1094
|
+
};
|
1066
1095
|
|
1067
1096
|
ko.observableArray['fn'] = {
|
1068
1097
|
'remove': function (valueOrPredicate) {
|
@@ -1142,7 +1171,7 @@ ko.observableArray['fn'] = {
|
|
1142
1171
|
this.valueHasMutated();
|
1143
1172
|
}
|
1144
1173
|
}
|
1145
|
-
}
|
1174
|
+
};
|
1146
1175
|
|
1147
1176
|
// Populate ko.observableArray.fn with read/write functions from native arrays
|
1148
1177
|
// Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
|
@@ -1248,14 +1277,16 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
|
|
1248
1277
|
_hasBeenEvaluated = true;
|
1249
1278
|
|
1250
1279
|
dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
|
1280
|
+
|
1251
1281
|
_latestValue = newValue;
|
1252
1282
|
if (DEBUG) dependentObservable._latestValue = _latestValue;
|
1283
|
+
dependentObservable["notifySubscribers"](_latestValue);
|
1284
|
+
|
1253
1285
|
} finally {
|
1254
1286
|
ko.dependencyDetection.end();
|
1287
|
+
_isBeingEvaluated = false;
|
1255
1288
|
}
|
1256
1289
|
|
1257
|
-
dependentObservable["notifySubscribers"](_latestValue);
|
1258
|
-
_isBeingEvaluated = false;
|
1259
1290
|
if (!_subscriptionsToDependencies.length)
|
1260
1291
|
dispose();
|
1261
1292
|
}
|
@@ -1323,7 +1354,7 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
|
|
1323
1354
|
// plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
|
1324
1355
|
if (disposeWhenNodeIsRemoved && isActive()) {
|
1325
1356
|
dispose = function() {
|
1326
|
-
ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved,
|
1357
|
+
ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, dispose);
|
1327
1358
|
disposeAllSubscriptionsToDependencies();
|
1328
1359
|
};
|
1329
1360
|
ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
|
@@ -1375,7 +1406,7 @@ ko.exportSymbol('isComputed', ko.isComputed);
|
|
1375
1406
|
visitedObjects = visitedObjects || new objectLookup();
|
1376
1407
|
|
1377
1408
|
rootObject = mapInputCallback(rootObject);
|
1378
|
-
var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date));
|
1409
|
+
var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date)) && (!(rootObject instanceof String)) && (!(rootObject instanceof Number)) && (!(rootObject instanceof Boolean));
|
1379
1410
|
if (!canHaveProperties)
|
1380
1411
|
return rootObject;
|
1381
1412
|
|
@@ -1414,27 +1445,32 @@ ko.exportSymbol('isComputed', ko.isComputed);
|
|
1414
1445
|
if (typeof rootObject['toJSON'] == 'function')
|
1415
1446
|
visitorCallback('toJSON');
|
1416
1447
|
} else {
|
1417
|
-
for (var propertyName in rootObject)
|
1448
|
+
for (var propertyName in rootObject) {
|
1418
1449
|
visitorCallback(propertyName);
|
1450
|
+
}
|
1419
1451
|
}
|
1420
1452
|
};
|
1421
1453
|
|
1422
1454
|
function objectLookup() {
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1455
|
+
this.keys = [];
|
1456
|
+
this.values = [];
|
1457
|
+
};
|
1458
|
+
|
1459
|
+
objectLookup.prototype = {
|
1460
|
+
constructor: objectLookup,
|
1461
|
+
save: function(key, value) {
|
1462
|
+
var existingIndex = ko.utils.arrayIndexOf(this.keys, key);
|
1427
1463
|
if (existingIndex >= 0)
|
1428
|
-
values[existingIndex] = value;
|
1464
|
+
this.values[existingIndex] = value;
|
1429
1465
|
else {
|
1430
|
-
keys.push(key);
|
1431
|
-
values.push(value);
|
1466
|
+
this.keys.push(key);
|
1467
|
+
this.values.push(value);
|
1432
1468
|
}
|
1433
|
-
}
|
1434
|
-
|
1435
|
-
var existingIndex = ko.utils.arrayIndexOf(keys, key);
|
1436
|
-
return (existingIndex >= 0) ? values[existingIndex] : undefined;
|
1437
|
-
}
|
1469
|
+
},
|
1470
|
+
get: function(key) {
|
1471
|
+
var existingIndex = ko.utils.arrayIndexOf(this.keys, key);
|
1472
|
+
return (existingIndex >= 0) ? this.values[existingIndex] : undefined;
|
1473
|
+
}
|
1438
1474
|
};
|
1439
1475
|
})();
|
1440
1476
|
|
@@ -1453,7 +1489,7 @@ ko.exportSymbol('toJSON', ko.toJSON);
|
|
1453
1489
|
if (element[hasDomDataExpandoProperty] === true)
|
1454
1490
|
return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
|
1455
1491
|
return ko.utils.ieVersion <= 7
|
1456
|
-
? (element.getAttributeNode('value').specified ? element.value : element.text)
|
1492
|
+
? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text)
|
1457
1493
|
: element.value;
|
1458
1494
|
case 'select':
|
1459
1495
|
return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
|
@@ -1484,12 +1520,20 @@ ko.exportSymbol('toJSON', ko.toJSON);
|
|
1484
1520
|
}
|
1485
1521
|
break;
|
1486
1522
|
case 'select':
|
1523
|
+
if (value === "")
|
1524
|
+
value = undefined;
|
1525
|
+
if (value === null || value === undefined)
|
1526
|
+
element.selectedIndex = -1;
|
1487
1527
|
for (var i = element.options.length - 1; i >= 0; i--) {
|
1488
1528
|
if (ko.selectExtensions.readValue(element.options[i]) == value) {
|
1489
1529
|
element.selectedIndex = i;
|
1490
1530
|
break;
|
1491
1531
|
}
|
1492
1532
|
}
|
1533
|
+
// for drop-down select, ensure first is selected
|
1534
|
+
if (!(element.size > 1) && element.selectedIndex === -1) {
|
1535
|
+
element.selectedIndex = 0;
|
1536
|
+
}
|
1493
1537
|
break;
|
1494
1538
|
default:
|
1495
1539
|
if ((value === null) || (value === undefined))
|
@@ -1506,7 +1550,7 @@ ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
|
|
1506
1550
|
ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
|
1507
1551
|
ko.expressionRewriting = (function () {
|
1508
1552
|
var restoreCapturedTokensRegex = /\@ko_token_(\d+)\@/g;
|
1509
|
-
var javaScriptReservedWords = ["true", "false"];
|
1553
|
+
var javaScriptReservedWords = ["true", "false", "null", "undefined"];
|
1510
1554
|
|
1511
1555
|
// Matches something that can be assigned to--either an isolated identifier or something ending with a property accessor
|
1512
1556
|
// This is designed to be simple and avoid false negatives, but could produce false positives (e.g., a+b.c).
|
@@ -1683,11 +1727,11 @@ ko.expressionRewriting = (function () {
|
|
1683
1727
|
// checkIfDifferent: If true, and if the property being written is a writable observable, the value will only be written if
|
1684
1728
|
// it is !== existing value on that writable observable
|
1685
1729
|
writeValueToProperty: function(property, allBindingsAccessor, key, value, checkIfDifferent) {
|
1686
|
-
if (!property || !ko.
|
1730
|
+
if (!property || !ko.isObservable(property)) {
|
1687
1731
|
var propWriters = allBindingsAccessor()['_ko_property_writers'];
|
1688
1732
|
if (propWriters && propWriters[key])
|
1689
1733
|
propWriters[key](value);
|
1690
|
-
} else if (!checkIfDifferent || property.peek() !== value) {
|
1734
|
+
} else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
|
1691
1735
|
property(value);
|
1692
1736
|
}
|
1693
1737
|
}
|
@@ -1714,7 +1758,7 @@ ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.ex
|
|
1714
1758
|
// IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
|
1715
1759
|
// but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
|
1716
1760
|
// So, use node.text where available, and node.nodeValue elsewhere
|
1717
|
-
var commentNodesHaveTextProperty = document.createComment("test").text === "<!--test-->";
|
1761
|
+
var commentNodesHaveTextProperty = document && document.createComment("test").text === "<!--test-->";
|
1718
1762
|
|
1719
1763
|
var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*-->$/ : /^\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*$/;
|
1720
1764
|
var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
|
@@ -1933,7 +1977,8 @@ ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomN
|
|
1933
1977
|
var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache);
|
1934
1978
|
return bindingFunction(bindingContext, node);
|
1935
1979
|
} catch (ex) {
|
1936
|
-
|
1980
|
+
ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message;
|
1981
|
+
throw ex;
|
1937
1982
|
}
|
1938
1983
|
}
|
1939
1984
|
});
|
@@ -2029,6 +2074,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2029
2074
|
}
|
2030
2075
|
}
|
2031
2076
|
|
2077
|
+
var boundElementDomDataKey = '__ko_boundElement';
|
2032
2078
|
function applyBindingsToNodeInternal (node, bindings, viewModelOrBindingContext, bindingContextMayDifferFromDomParentElement) {
|
2033
2079
|
// Need to be sure that inits are only run once, and updates never run until all the inits have been run
|
2034
2080
|
var initPhase = 0; // 0 = before all inits, 1 = during inits, 2 = after all inits
|
@@ -2048,6 +2094,16 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2048
2094
|
}
|
2049
2095
|
|
2050
2096
|
var bindingHandlerThatControlsDescendantBindings;
|
2097
|
+
|
2098
|
+
// Prevent multiple applyBindings calls for the same node, except when a binding value is specified
|
2099
|
+
var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
|
2100
|
+
if (!bindings) {
|
2101
|
+
if (alreadyBound) {
|
2102
|
+
throw Error("You cannot apply bindings multiple times to the same element.");
|
2103
|
+
}
|
2104
|
+
ko.utils.domData.set(node, boundElementDomDataKey, true);
|
2105
|
+
}
|
2106
|
+
|
2051
2107
|
ko.dependentObservable(
|
2052
2108
|
function () {
|
2053
2109
|
// Ensure we have a nonnull binding context to work with
|
@@ -2059,7 +2115,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2059
2115
|
// Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
|
2060
2116
|
// we can easily recover it just by scanning up the node's ancestors in the DOM
|
2061
2117
|
// (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
|
2062
|
-
if (bindingContextMayDifferFromDomParentElement)
|
2118
|
+
if (!alreadyBound && bindingContextMayDifferFromDomParentElement)
|
2063
2119
|
ko.storedBindingContextForNode(node, bindingContextInstance);
|
2064
2120
|
|
2065
2121
|
// Use evaluatedBindings if given, otherwise fall back on asking the bindings provider to give us some bindings
|
@@ -2070,7 +2126,7 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2070
2126
|
// First run all the inits, so bindings can register for notification on changes
|
2071
2127
|
if (initPhase === 0) {
|
2072
2128
|
initPhase = 1;
|
2073
|
-
|
2129
|
+
ko.utils.objectForEach(parsedBindings, function(bindingKey) {
|
2074
2130
|
var binding = ko.bindingHandlers[bindingKey];
|
2075
2131
|
if (binding && node.nodeType === 8)
|
2076
2132
|
validateThatBindingIsAllowedForVirtualElements(bindingKey);
|
@@ -2086,19 +2142,19 @@ ko.exportSymbol('bindingProvider', ko.bindingProvider);
|
|
2086
2142
|
bindingHandlerThatControlsDescendantBindings = bindingKey;
|
2087
2143
|
}
|
2088
2144
|
}
|
2089
|
-
}
|
2145
|
+
});
|
2090
2146
|
initPhase = 2;
|
2091
2147
|
}
|
2092
2148
|
|
2093
2149
|
// ... then run all the updates, which might trigger changes even on the first evaluation
|
2094
2150
|
if (initPhase === 2) {
|
2095
|
-
|
2151
|
+
ko.utils.objectForEach(parsedBindings, function(bindingKey) {
|
2096
2152
|
var binding = ko.bindingHandlers[bindingKey];
|
2097
2153
|
if (binding && typeof binding["update"] == "function") {
|
2098
2154
|
var handlerUpdateFn = binding["update"];
|
2099
2155
|
handlerUpdateFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
|
2100
2156
|
}
|
2101
|
-
}
|
2157
|
+
});
|
2102
2158
|
}
|
2103
2159
|
}
|
2104
2160
|
},
|
@@ -2167,40 +2223,38 @@ var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
|
|
2167
2223
|
ko.bindingHandlers['attr'] = {
|
2168
2224
|
'update': function(element, valueAccessor, allBindingsAccessor) {
|
2169
2225
|
var value = ko.utils.unwrapObservable(valueAccessor()) || {};
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2177
|
-
|
2226
|
+
ko.utils.objectForEach(value, function(attrName, attrValue) {
|
2227
|
+
attrValue = ko.utils.unwrapObservable(attrValue);
|
2228
|
+
|
2229
|
+
// To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
|
2230
|
+
// when someProp is a "no value"-like value (strictly null, false, or undefined)
|
2231
|
+
// (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
|
2232
|
+
var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
|
2233
|
+
if (toRemove)
|
2234
|
+
element.removeAttribute(attrName);
|
2235
|
+
|
2236
|
+
// In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
|
2237
|
+
// HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
|
2238
|
+
// but instead of figuring out the mode, we'll just set the attribute through the Javascript
|
2239
|
+
// property for IE <= 8.
|
2240
|
+
if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
|
2241
|
+
attrName = attrHtmlToJavascriptMap[attrName];
|
2178
2242
|
if (toRemove)
|
2179
2243
|
element.removeAttribute(attrName);
|
2244
|
+
else
|
2245
|
+
element[attrName] = attrValue;
|
2246
|
+
} else if (!toRemove) {
|
2247
|
+
element.setAttribute(attrName, attrValue.toString());
|
2248
|
+
}
|
2180
2249
|
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2187
|
-
if (toRemove)
|
2188
|
-
element.removeAttribute(attrName);
|
2189
|
-
else
|
2190
|
-
element[attrName] = attrValue;
|
2191
|
-
} else if (!toRemove) {
|
2192
|
-
element.setAttribute(attrName, attrValue.toString());
|
2193
|
-
}
|
2194
|
-
|
2195
|
-
// Treat "name" specially - although you can think of it as an attribute, it also needs
|
2196
|
-
// special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
|
2197
|
-
// Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
|
2198
|
-
// entirely, and there's no strong reason to allow for such casing in HTML.
|
2199
|
-
if (attrName === "name") {
|
2200
|
-
ko.utils.setElementName(element, toRemove ? "" : attrValue.toString());
|
2201
|
-
}
|
2250
|
+
// Treat "name" specially - although you can think of it as an attribute, it also needs
|
2251
|
+
// special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
|
2252
|
+
// Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
|
2253
|
+
// entirely, and there's no strong reason to allow for such casing in HTML.
|
2254
|
+
if (attrName === "name") {
|
2255
|
+
ko.utils.setElementName(element, toRemove ? "" : attrValue.toString());
|
2202
2256
|
}
|
2203
|
-
}
|
2257
|
+
});
|
2204
2258
|
}
|
2205
2259
|
};
|
2206
2260
|
ko.bindingHandlers['checked'] = {
|
@@ -2219,11 +2273,7 @@ ko.bindingHandlers['checked'] = {
|
|
2219
2273
|
if ((element.type == "checkbox") && (unwrappedValue instanceof Array)) {
|
2220
2274
|
// For checkboxes bound to an array, we add/remove the checkbox value to that array
|
2221
2275
|
// This works for both observable and non-observable arrays
|
2222
|
-
|
2223
|
-
if (element.checked && (existingEntryIndex < 0))
|
2224
|
-
modelValue.push(element.value);
|
2225
|
-
else if ((!element.checked) && (existingEntryIndex >= 0))
|
2226
|
-
modelValue.splice(existingEntryIndex, 1);
|
2276
|
+
ko.utils.addOrRemoveItem(modelValue, element.value, element.checked);
|
2227
2277
|
} else {
|
2228
2278
|
ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
|
2229
2279
|
}
|
@@ -2242,7 +2292,7 @@ ko.bindingHandlers['checked'] = {
|
|
2242
2292
|
// When bound to an array, the checkbox being checked represents its value being present in that array
|
2243
2293
|
element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0;
|
2244
2294
|
} else {
|
2245
|
-
// When bound to
|
2295
|
+
// When bound to any other value (not an array), the checkbox being checked represents the value being trueish
|
2246
2296
|
element.checked = value;
|
2247
2297
|
}
|
2248
2298
|
} else if (element.type == "radio") {
|
@@ -2255,10 +2305,10 @@ ko.bindingHandlers['css'] = {
|
|
2255
2305
|
'update': function (element, valueAccessor) {
|
2256
2306
|
var value = ko.utils.unwrapObservable(valueAccessor());
|
2257
2307
|
if (typeof value == "object") {
|
2258
|
-
|
2259
|
-
|
2308
|
+
ko.utils.objectForEach(value, function(className, shouldHaveClass) {
|
2309
|
+
shouldHaveClass = ko.utils.unwrapObservable(shouldHaveClass);
|
2260
2310
|
ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
|
2261
|
-
}
|
2311
|
+
});
|
2262
2312
|
} else {
|
2263
2313
|
value = String(value || ''); // Make sure we don't try to store or set a non-string value
|
2264
2314
|
ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
|
@@ -2300,41 +2350,38 @@ function makeEventHandlerShortcut(eventName) {
|
|
2300
2350
|
ko.bindingHandlers['event'] = {
|
2301
2351
|
'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) {
|
2302
2352
|
var eventsToHandle = valueAccessor() || {};
|
2303
|
-
|
2304
|
-
(
|
2305
|
-
|
2306
|
-
|
2307
|
-
|
2308
|
-
|
2309
|
-
|
2310
|
-
|
2311
|
-
|
2312
|
-
|
2313
|
-
|
2314
|
-
|
2315
|
-
|
2316
|
-
|
2317
|
-
|
2318
|
-
|
2319
|
-
|
2320
|
-
|
2321
|
-
|
2322
|
-
|
2323
|
-
else
|
2324
|
-
event.returnValue = false;
|
2325
|
-
}
|
2353
|
+
ko.utils.objectForEach(eventsToHandle, function(eventName) {
|
2354
|
+
if (typeof eventName == "string") {
|
2355
|
+
ko.utils.registerEventHandler(element, eventName, function (event) {
|
2356
|
+
var handlerReturnValue;
|
2357
|
+
var handlerFunction = valueAccessor()[eventName];
|
2358
|
+
if (!handlerFunction)
|
2359
|
+
return;
|
2360
|
+
var allBindings = allBindingsAccessor();
|
2361
|
+
|
2362
|
+
try {
|
2363
|
+
// Take all the event args, and prefix with the viewmodel
|
2364
|
+
var argsForHandler = ko.utils.makeArray(arguments);
|
2365
|
+
argsForHandler.unshift(viewModel);
|
2366
|
+
handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);
|
2367
|
+
} finally {
|
2368
|
+
if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
|
2369
|
+
if (event.preventDefault)
|
2370
|
+
event.preventDefault();
|
2371
|
+
else
|
2372
|
+
event.returnValue = false;
|
2326
2373
|
}
|
2374
|
+
}
|
2327
2375
|
|
2328
|
-
|
2329
|
-
|
2330
|
-
|
2331
|
-
|
2332
|
-
|
2333
|
-
|
2334
|
-
|
2335
|
-
|
2336
|
-
|
2337
|
-
}
|
2376
|
+
var bubble = allBindings[eventName + 'Bubble'] !== false;
|
2377
|
+
if (!bubble) {
|
2378
|
+
event.cancelBubble = true;
|
2379
|
+
if (event.stopPropagation)
|
2380
|
+
event.stopPropagation();
|
2381
|
+
}
|
2382
|
+
});
|
2383
|
+
}
|
2384
|
+
});
|
2338
2385
|
}
|
2339
2386
|
};
|
2340
2387
|
// "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
|
@@ -2376,6 +2423,7 @@ ko.bindingHandlers['foreach'] = {
|
|
2376
2423
|
ko.expressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
|
2377
2424
|
ko.virtualElements.allowedBindings['foreach'] = true;
|
2378
2425
|
var hasfocusUpdatingProperty = '__ko_hasfocusUpdating';
|
2426
|
+
var hasfocusLastValue = '__ko_hasfocusLastValue';
|
2379
2427
|
ko.bindingHandlers['hasfocus'] = {
|
2380
2428
|
'init': function(element, valueAccessor, allBindingsAccessor) {
|
2381
2429
|
var handleElementFocusChange = function(isFocused) {
|
@@ -2388,10 +2436,20 @@ ko.bindingHandlers['hasfocus'] = {
|
|
2388
2436
|
element[hasfocusUpdatingProperty] = true;
|
2389
2437
|
var ownerDoc = element.ownerDocument;
|
2390
2438
|
if ("activeElement" in ownerDoc) {
|
2391
|
-
|
2439
|
+
var active;
|
2440
|
+
try {
|
2441
|
+
active = ownerDoc.activeElement;
|
2442
|
+
} catch(e) {
|
2443
|
+
// IE9 throws if you access activeElement during page load (see issue #703)
|
2444
|
+
active = ownerDoc.body;
|
2445
|
+
}
|
2446
|
+
isFocused = (active === element);
|
2392
2447
|
}
|
2393
2448
|
var modelValue = valueAccessor();
|
2394
2449
|
ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'hasfocus', isFocused, true);
|
2450
|
+
|
2451
|
+
//cache the latest value, so we can avoid unnecessarily calling focus/blur in the update function
|
2452
|
+
element[hasfocusLastValue] = isFocused;
|
2395
2453
|
element[hasfocusUpdatingProperty] = false;
|
2396
2454
|
};
|
2397
2455
|
var handleElementFocusIn = handleElementFocusChange.bind(null, true);
|
@@ -2403,13 +2461,15 @@ ko.bindingHandlers['hasfocus'] = {
|
|
2403
2461
|
ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
|
2404
2462
|
},
|
2405
2463
|
'update': function(element, valueAccessor) {
|
2406
|
-
var value = ko.utils.unwrapObservable(valueAccessor());
|
2407
|
-
if (!element[hasfocusUpdatingProperty]) {
|
2464
|
+
var value = !!ko.utils.unwrapObservable(valueAccessor()); //force boolean to compare with last value
|
2465
|
+
if (!element[hasfocusUpdatingProperty] && element[hasfocusLastValue] !== value) {
|
2408
2466
|
value ? element.focus() : element.blur();
|
2409
2467
|
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
|
2410
2468
|
}
|
2411
2469
|
}
|
2412
2470
|
};
|
2471
|
+
|
2472
|
+
ko.bindingHandlers['hasFocus'] = ko.bindingHandlers['hasfocus']; // Make "hasFocus" an alias
|
2413
2473
|
ko.bindingHandlers['html'] = {
|
2414
2474
|
'init': function() {
|
2415
2475
|
// Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
|
@@ -2479,59 +2539,83 @@ function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue,
|
|
2479
2539
|
};
|
2480
2540
|
|
2481
2541
|
ko.bindingHandlers['options'] = {
|
2482
|
-
'
|
2542
|
+
'init': function(element) {
|
2483
2543
|
if (ko.utils.tagNameLower(element) !== "select")
|
2484
2544
|
throw new Error("options binding applies only to SELECT elements");
|
2485
2545
|
|
2486
|
-
var selectWasPreviouslyEmpty = element.length == 0;
|
2487
|
-
var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
|
2488
|
-
return node.tagName && (ko.utils.tagNameLower(node) === "option") && node.selected;
|
2489
|
-
}), function (node) {
|
2490
|
-
return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
|
2491
|
-
});
|
2492
|
-
var previousScrollTop = element.scrollTop;
|
2493
|
-
|
2494
|
-
var value = ko.utils.unwrapObservable(valueAccessor());
|
2495
|
-
var selectedValue = element.value;
|
2496
|
-
|
2497
2546
|
// Remove all existing <option>s.
|
2498
|
-
// Need to use .remove() rather than .removeChild() for <option>s otherwise IE behaves oddly (https://github.com/SteveSanderson/knockout/issues/134)
|
2499
2547
|
while (element.length > 0) {
|
2500
|
-
ko.cleanNode(element.options[0]);
|
2501
2548
|
element.remove(0);
|
2502
2549
|
}
|
2503
2550
|
|
2504
|
-
|
2505
|
-
|
2506
|
-
|
2507
|
-
|
2508
|
-
|
2509
|
-
|
2510
|
-
|
2511
|
-
|
2512
|
-
|
2513
|
-
|
2514
|
-
|
2515
|
-
|
2551
|
+
// Ensures that the binding processor doesn't try to bind the options
|
2552
|
+
return { 'controlsDescendantBindings': true };
|
2553
|
+
},
|
2554
|
+
'update': function (element, valueAccessor, allBindingsAccessor) {
|
2555
|
+
var selectWasPreviouslyEmpty = element.length == 0;
|
2556
|
+
var previousScrollTop = (!selectWasPreviouslyEmpty && element.multiple) ? element.scrollTop : null;
|
2557
|
+
|
2558
|
+
var unwrappedArray = ko.utils.unwrapObservable(valueAccessor());
|
2559
|
+
var allBindings = allBindingsAccessor();
|
2560
|
+
var includeDestroyed = allBindings['optionsIncludeDestroyed'];
|
2561
|
+
var captionPlaceholder = {};
|
2562
|
+
var captionValue;
|
2563
|
+
var previousSelectedValues;
|
2564
|
+
if (element.multiple) {
|
2565
|
+
previousSelectedValues = ko.utils.arrayMap(element.selectedOptions || ko.utils.arrayFilter(element.childNodes, function (node) {
|
2566
|
+
return node.tagName && (ko.utils.tagNameLower(node) === "option") && node.selected;
|
2567
|
+
}), function (node) {
|
2568
|
+
return ko.selectExtensions.readValue(node);
|
2569
|
+
});
|
2570
|
+
} else if (element.selectedIndex >= 0) {
|
2571
|
+
previousSelectedValues = [ ko.selectExtensions.readValue(element.options[element.selectedIndex]) ];
|
2572
|
+
}
|
2516
2573
|
|
2517
|
-
|
2518
|
-
|
2519
|
-
|
2520
|
-
if (arrayEntry && arrayEntry['_destroy'] && !includeDestroyed)
|
2521
|
-
continue;
|
2574
|
+
if (unwrappedArray) {
|
2575
|
+
if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
|
2576
|
+
unwrappedArray = [unwrappedArray];
|
2522
2577
|
|
2523
|
-
|
2578
|
+
// Filter out any entries marked as destroyed
|
2579
|
+
var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
|
2580
|
+
return includeDestroyed || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
|
2581
|
+
});
|
2524
2582
|
|
2525
|
-
|
2526
|
-
|
2527
|
-
|
2528
|
-
|
2529
|
-
|
2530
|
-
|
2531
|
-
else // Given no optionsText arg; use the data value itself
|
2532
|
-
return defaultValue;
|
2583
|
+
// If caption is included, add it to the array
|
2584
|
+
if ('optionsCaption' in allBindings) {
|
2585
|
+
captionValue = ko.utils.unwrapObservable(allBindings['optionsCaption']);
|
2586
|
+
// If caption value is null or undefined, don't show a caption
|
2587
|
+
if (captionValue !== null && captionValue !== undefined) {
|
2588
|
+
filteredArray.unshift(captionPlaceholder);
|
2533
2589
|
}
|
2534
|
-
|
2590
|
+
}
|
2591
|
+
} else {
|
2592
|
+
// If a falsy value is provided (e.g. null), we'll simply empty the select element
|
2593
|
+
unwrappedArray = [];
|
2594
|
+
}
|
2595
|
+
|
2596
|
+
function applyToObject(object, predicate, defaultValue) {
|
2597
|
+
var predicateType = typeof predicate;
|
2598
|
+
if (predicateType == "function") // Given a function; run it against the data value
|
2599
|
+
return predicate(object);
|
2600
|
+
else if (predicateType == "string") // Given a string; treat it as a property name on the data value
|
2601
|
+
return object[predicate];
|
2602
|
+
else // Given no optionsText arg; use the data value itself
|
2603
|
+
return defaultValue;
|
2604
|
+
}
|
2605
|
+
|
2606
|
+
// The following functions can run at two different times:
|
2607
|
+
// The first is when the whole array is being updated directly from this binding handler.
|
2608
|
+
// The second is when an observable value for a specific array entry is updated.
|
2609
|
+
// oldOptions will be empty in the first case, but will be filled with the previously generated option in the second.
|
2610
|
+
function optionForArrayItem(arrayEntry, index, oldOptions) {
|
2611
|
+
if (oldOptions.length) {
|
2612
|
+
previousSelectedValues = oldOptions[0].selected && [ ko.selectExtensions.readValue(oldOptions[0]) ];
|
2613
|
+
}
|
2614
|
+
var option = document.createElement("option");
|
2615
|
+
if (arrayEntry === captionPlaceholder) {
|
2616
|
+
ko.utils.setHtml(option, captionValue);
|
2617
|
+
ko.selectExtensions.writeValue(option, undefined);
|
2618
|
+
} else {
|
2535
2619
|
// Apply a value to the option element
|
2536
2620
|
var optionValue = applyToObject(arrayEntry, allBindings['optionsValue'], arrayEntry);
|
2537
2621
|
ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue));
|
@@ -2539,33 +2623,44 @@ ko.bindingHandlers['options'] = {
|
|
2539
2623
|
// Apply some text to the option element
|
2540
2624
|
var optionText = applyToObject(arrayEntry, allBindings['optionsText'], optionValue);
|
2541
2625
|
ko.utils.setTextContent(option, optionText);
|
2542
|
-
|
2543
|
-
element.appendChild(option);
|
2544
2626
|
}
|
2627
|
+
return [option];
|
2628
|
+
}
|
2545
2629
|
|
2630
|
+
function setSelectionCallback(arrayEntry, newOptions) {
|
2546
2631
|
// IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
|
2547
2632
|
// That's why we first added them without selection. Now it's time to set the selection.
|
2548
|
-
|
2549
|
-
|
2550
|
-
|
2551
|
-
if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
|
2552
|
-
ko.utils.setOptionNodeSelectionState(newOptions[i], true);
|
2553
|
-
countSelectionsRetained++;
|
2554
|
-
}
|
2633
|
+
if (previousSelectedValues) {
|
2634
|
+
var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[0])) >= 0;
|
2635
|
+
ko.utils.setOptionNodeSelectionState(newOptions[0], isSelected);
|
2555
2636
|
}
|
2637
|
+
}
|
2556
2638
|
|
2557
|
-
|
2558
|
-
|
2559
|
-
|
2560
|
-
|
2561
|
-
|
2562
|
-
// the dropdown selection state is meaningless, so we preserve the model value.
|
2563
|
-
ensureDropdownSelectionIsConsistentWithModelValue(element, ko.utils.peekObservable(allBindings['value']), /* preferModelValue */ true);
|
2639
|
+
var callback = setSelectionCallback;
|
2640
|
+
if (allBindings['optionsAfterRender']) {
|
2641
|
+
callback = function(arrayEntry, newOptions) {
|
2642
|
+
setSelectionCallback(arrayEntry, newOptions);
|
2643
|
+
ko.dependencyDetection.ignore(allBindings['optionsAfterRender'], null, [newOptions[0], arrayEntry !== captionPlaceholder ? arrayEntry : undefined]);
|
2564
2644
|
}
|
2645
|
+
}
|
2565
2646
|
|
2566
|
-
|
2567
|
-
|
2647
|
+
ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, null, callback);
|
2648
|
+
|
2649
|
+
// Clear previousSelectedValues so that future updates to individual objects don't get stale data
|
2650
|
+
previousSelectedValues = null;
|
2651
|
+
|
2652
|
+
if (selectWasPreviouslyEmpty && ('value' in allBindings)) {
|
2653
|
+
// Ensure consistency between model value and selected option.
|
2654
|
+
// If the dropdown is being populated for the first time here (or was otherwise previously empty),
|
2655
|
+
// the dropdown selection state is meaningless, so we preserve the model value.
|
2656
|
+
ensureDropdownSelectionIsConsistentWithModelValue(element, ko.utils.peekObservable(allBindings['value']), /* preferModelValue */ true);
|
2568
2657
|
}
|
2658
|
+
|
2659
|
+
// Workaround for IE bug
|
2660
|
+
ko.utils.ensureSelectElementIsRenderedCorrectly(element);
|
2661
|
+
|
2662
|
+
if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20)
|
2663
|
+
element.scrollTop = previousScrollTop;
|
2569
2664
|
}
|
2570
2665
|
};
|
2571
2666
|
ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.optionValueDomData__';
|
@@ -2577,7 +2672,7 @@ ko.bindingHandlers['selectedOptions'] = {
|
|
2577
2672
|
if (node.selected)
|
2578
2673
|
valueToWrite.push(ko.selectExtensions.readValue(node));
|
2579
2674
|
});
|
2580
|
-
ko.expressionRewriting.writeValueToProperty(value, allBindingsAccessor, '
|
2675
|
+
ko.expressionRewriting.writeValueToProperty(value, allBindingsAccessor, 'selectedOptions', valueToWrite);
|
2581
2676
|
});
|
2582
2677
|
},
|
2583
2678
|
'update': function (element, valueAccessor) {
|
@@ -2596,12 +2691,10 @@ ko.bindingHandlers['selectedOptions'] = {
|
|
2596
2691
|
ko.bindingHandlers['style'] = {
|
2597
2692
|
'update': function (element, valueAccessor) {
|
2598
2693
|
var value = ko.utils.unwrapObservable(valueAccessor() || {});
|
2599
|
-
|
2600
|
-
|
2601
|
-
|
2602
|
-
|
2603
|
-
}
|
2604
|
-
}
|
2694
|
+
ko.utils.objectForEach(value, function(styleName, styleValue) {
|
2695
|
+
styleValue = ko.utils.unwrapObservable(styleValue);
|
2696
|
+
element.style[styleName] = styleValue || ""; // Empty string removes the value, whereas null/undefined have no effect
|
2697
|
+
});
|
2605
2698
|
}
|
2606
2699
|
};
|
2607
2700
|
ko.bindingHandlers['submit'] = {
|
@@ -2687,12 +2780,7 @@ ko.bindingHandlers['value'] = {
|
|
2687
2780
|
var valueIsSelectOption = ko.utils.tagNameLower(element) === "select";
|
2688
2781
|
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
2689
2782
|
var elementValue = ko.selectExtensions.readValue(element);
|
2690
|
-
var valueHasChanged = (newValue
|
2691
|
-
|
2692
|
-
// JavaScript's 0 == "" behavious is unfortunate here as it prevents writing 0 to an empty text box (loose equality suggests the values are the same).
|
2693
|
-
// We don't want to do a strict equality comparison as that is more confusing for developers in certain cases, so we specifically special case 0 != "" here.
|
2694
|
-
if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0"))
|
2695
|
-
valueHasChanged = true;
|
2783
|
+
var valueHasChanged = (newValue !== elementValue);
|
2696
2784
|
|
2697
2785
|
if (valueHasChanged) {
|
2698
2786
|
var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
|
@@ -2797,7 +2885,7 @@ ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCal
|
|
2797
2885
|
ko.exportSymbol('templateEngine', ko.templateEngine);
|
2798
2886
|
|
2799
2887
|
ko.templateRewriting = (function () {
|
2800
|
-
var memoizeDataBindingAttributeSyntaxRegex = /(<[a-z]+\d*(
|
2888
|
+
var memoizeDataBindingAttributeSyntaxRegex = /(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi;
|
2801
2889
|
var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
|
2802
2890
|
|
2803
2891
|
function validateDataBindValuesForRewriting(keyValueArray) {
|
@@ -2818,7 +2906,7 @@ ko.templateRewriting = (function () {
|
|
2818
2906
|
}
|
2819
2907
|
}
|
2820
2908
|
|
2821
|
-
function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, templateEngine) {
|
2909
|
+
function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, nodeName, templateEngine) {
|
2822
2910
|
var dataBindKeyValueArray = ko.expressionRewriting.parseObjectLiteral(dataBindAttributeValue);
|
2823
2911
|
validateDataBindValuesForRewriting(dataBindKeyValueArray);
|
2824
2912
|
var rewrittenDataBindAttributeValue = ko.expressionRewriting.preProcessBindings(dataBindKeyValueArray);
|
@@ -2827,7 +2915,7 @@ ko.templateRewriting = (function () {
|
|
2827
2915
|
// anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
|
2828
2916
|
// extra indirection.
|
2829
2917
|
var applyBindingsToNextSiblingScript =
|
2830
|
-
"ko.__tr_ambtns(function($context,$element){return(function(){return{ " + rewrittenDataBindAttributeValue + " } })()})";
|
2918
|
+
"ko.__tr_ambtns(function($context,$element){return(function(){return{ " + rewrittenDataBindAttributeValue + " } })()},'" + nodeName.toLowerCase() + "')";
|
2831
2919
|
return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
|
2832
2920
|
}
|
2833
2921
|
|
@@ -2841,16 +2929,18 @@ ko.templateRewriting = (function () {
|
|
2841
2929
|
|
2842
2930
|
memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
|
2843
2931
|
return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
|
2844
|
-
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[
|
2932
|
+
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[4], /* tagToRetain: */ arguments[1], /* nodeName: */ arguments[2], templateEngine);
|
2845
2933
|
}).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
|
2846
|
-
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", templateEngine);
|
2934
|
+
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", /* nodeName: */ "#comment", templateEngine);
|
2847
2935
|
});
|
2848
2936
|
},
|
2849
2937
|
|
2850
|
-
applyMemoizedBindingsToNextSibling: function (bindings) {
|
2938
|
+
applyMemoizedBindingsToNextSibling: function (bindings, nodeName) {
|
2851
2939
|
return ko.memoization.memoize(function (domNode, bindingContext) {
|
2852
|
-
|
2853
|
-
|
2940
|
+
var nodeToBind = domNode.nextSibling;
|
2941
|
+
if (nodeToBind && nodeToBind.nodeName.toLowerCase() === nodeName) {
|
2942
|
+
ko.applyBindingsToNode(nodeToBind, bindings, bindingContext);
|
2943
|
+
}
|
2854
2944
|
});
|
2855
2945
|
}
|
2856
2946
|
}
|
@@ -2927,6 +3017,7 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
2927
3017
|
this.domElement = element;
|
2928
3018
|
}
|
2929
3019
|
ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
|
3020
|
+
ko.templateSources.anonymousTemplate.prototype.constructor = ko.templateSources.anonymousTemplate;
|
2930
3021
|
ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
|
2931
3022
|
if (arguments.length == 0) {
|
2932
3023
|
var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
|
@@ -3138,7 +3229,7 @@ ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextS
|
|
3138
3229
|
|
3139
3230
|
if (typeof templateName != "string") {
|
3140
3231
|
options = templateName;
|
3141
|
-
templateName = options['name'];
|
3232
|
+
templateName = ko.utils.unwrapObservable(options['name']);
|
3142
3233
|
|
3143
3234
|
// Support "if"/"ifnot" conditions
|
3144
3235
|
if ('if' in options)
|
@@ -3330,11 +3421,11 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
3330
3421
|
// Map this array value inside a dependentObservable so we re-map when any dependency changes
|
3331
3422
|
var mappedNodes = [];
|
3332
3423
|
var dependentObservable = ko.dependentObservable(function() {
|
3333
|
-
var newMappedNodes = mapping(valueToMap, index) || [];
|
3424
|
+
var newMappedNodes = mapping(valueToMap, index, fixUpNodesToBeMovedOrRemoved(mappedNodes)) || [];
|
3334
3425
|
|
3335
3426
|
// On subsequent evaluations, just replace the previously-inserted DOM nodes
|
3336
3427
|
if (mappedNodes.length > 0) {
|
3337
|
-
ko.utils.replaceDomNodes(
|
3428
|
+
ko.utils.replaceDomNodes(mappedNodes, newMappedNodes);
|
3338
3429
|
if (callbackAfterAddingNodes)
|
3339
3430
|
ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]);
|
3340
3431
|
}
|
@@ -3343,7 +3434,7 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
3343
3434
|
// of which nodes would be deleted if valueToMap was itself later removed
|
3344
3435
|
mappedNodes.splice(0, mappedNodes.length);
|
3345
3436
|
ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
|
3346
|
-
}, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return
|
3437
|
+
}, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return !ko.utils.anyDomNodeIsAttachedToDocument(mappedNodes); } });
|
3347
3438
|
return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
|
3348
3439
|
}
|
3349
3440
|
|
@@ -3356,7 +3447,7 @@ ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
|
|
3356
3447
|
var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
|
3357
3448
|
var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
|
3358
3449
|
var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
|
3359
|
-
var editScript = ko.utils.compareArrays(lastArray, array);
|
3450
|
+
var editScript = ko.utils.compareArrays(lastArray, array, options['dontLimitMoves']);
|
3360
3451
|
|
3361
3452
|
// Build the new mapping result
|
3362
3453
|
var newMappingResult = [];
|
@@ -3479,6 +3570,7 @@ ko.nativeTemplateEngine = function () {
|
|
3479
3570
|
}
|
3480
3571
|
|
3481
3572
|
ko.nativeTemplateEngine.prototype = new ko.templateEngine();
|
3573
|
+
ko.nativeTemplateEngine.prototype.constructor = ko.nativeTemplateEngine;
|
3482
3574
|
ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
|
3483
3575
|
var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly
|
3484
3576
|
templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null,
|
@@ -3555,7 +3647,7 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
|
3555
3647
|
};
|
3556
3648
|
|
3557
3649
|
this['addTemplate'] = function(templateName, templateMarkup) {
|
3558
|
-
document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "
|
3650
|
+
document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "<" + "/script>");
|
3559
3651
|
};
|
3560
3652
|
|
3561
3653
|
if (jQueryTmplVersion > 0) {
|
@@ -3570,6 +3662,7 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
|
3570
3662
|
};
|
3571
3663
|
|
3572
3664
|
ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
|
3665
|
+
ko.jqueryTmplTemplateEngine.prototype.constructor = ko.jqueryTmplTemplateEngine;
|
3573
3666
|
|
3574
3667
|
// Use this one by default *only if jquery.tmpl is referenced*
|
3575
3668
|
var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
|
@@ -3578,6 +3671,6 @@ ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
|
|
3578
3671
|
|
3579
3672
|
ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
|
3580
3673
|
})();
|
3581
|
-
});
|
3582
|
-
}
|
3674
|
+
}));
|
3675
|
+
}());
|
3583
3676
|
})();
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knockoutjs-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-07-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: railties
|
@@ -67,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
67
|
version: '0'
|
68
68
|
requirements: []
|
69
69
|
rubyforge_project:
|
70
|
-
rubygems_version: 1.8.
|
70
|
+
rubygems_version: 1.8.24
|
71
71
|
signing_key:
|
72
72
|
specification_version: 3
|
73
73
|
summary: The Knockout.js JavaScript library ready for the Rails asset pipeline.
|