knockoutjs-rails 2.2.1 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|