Tab layout, first commit with new third party packages
[openemr.git] / public / assets / knockout-3-4-0 / src / binding / expressionRewriting.js
bloba272b1bdf21849a93cf7b93270c61a37ed743808
1 ko.expressionRewriting = (function () {
2     var javaScriptReservedWords = ["true", "false", "null", "undefined"];
4     // Matches something that can be assigned to--either an isolated identifier or something ending with a property accessor
5     // This is designed to be simple and avoid false negatives, but could produce false positives (e.g., a+b.c).
6     // This also will not properly handle nested brackets (e.g., obj1[obj2['prop']]; see #911).
7     var javaScriptAssignmentTarget = /^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i;
9     function getWriteableValue(expression) {
10         if (ko.utils.arrayIndexOf(javaScriptReservedWords, expression) >= 0)
11             return false;
12         var match = expression.match(javaScriptAssignmentTarget);
13         return match === null ? false : match[1] ? ('Object(' + match[1] + ')' + match[2]) : expression;
14     }
16     // The following regular expressions will be used to split an object-literal string into tokens
18         // These two match strings, either with double quotes or single quotes
19     var stringDouble = '"(?:[^"\\\\]|\\\\.)*"',
20         stringSingle = "'(?:[^'\\\\]|\\\\.)*'",
21         // Matches a regular expression (text enclosed by slashes), but will also match sets of divisions
22         // as a regular expression (this is handled by the parsing loop below).
23         stringRegexp = '/(?:[^/\\\\]|\\\\.)*/\w*',
24         // These characters have special meaning to the parser and must not appear in the middle of a
25         // token, except as part of a string.
26         specials = ',"\'{}()/:[\\]',
27         // Match text (at least two characters) that does not contain any of the above special characters,
28         // although some of the special characters are allowed to start it (all but the colon and comma).
29         // The text can contain spaces, but leading or trailing spaces are skipped.
30         everyThingElse = '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']',
31         // Match any non-space character not matched already. This will match colons and commas, since they're
32         // not matched by "everyThingElse", but will also match any other single character that wasn't already
33         // matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace).
34         oneNotSpace = '[^\\s]',
36         // Create the actual regular expression by or-ing the above strings. The order is important.
37         bindingToken = RegExp(stringDouble + '|' + stringSingle + '|' + stringRegexp + '|' + everyThingElse + '|' + oneNotSpace, 'g'),
39         // Match end of previous token to determine whether a slash is a division or regex.
40         divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/,
41         keywordRegexLookBehind = {'in':1,'return':1,'typeof':1};
43     function parseObjectLiteral(objectLiteralString) {
44         // Trim leading and trailing spaces from the string
45         var str = ko.utils.stringTrim(objectLiteralString);
47         // Trim braces '{' surrounding the whole object literal
48         if (str.charCodeAt(0) === 123) str = str.slice(1, -1);
50         // Split into tokens
51         var result = [], toks = str.match(bindingToken), key, values = [], depth = 0;
53         if (toks) {
54             // Append a comma so that we don't need a separate code block to deal with the last item
55             toks.push(',');
57             for (var i = 0, tok; tok = toks[i]; ++i) {
58                 var c = tok.charCodeAt(0);
59                 // A comma signals the end of a key/value pair if depth is zero
60                 if (c === 44) { // ","
61                     if (depth <= 0) {
62                         result.push((key && values.length) ? {key: key, value: values.join('')} : {'unknown': key || values.join('')});
63                         key = depth = 0;
64                         values = [];
65                         continue;
66                     }
67                 // Simply skip the colon that separates the name and value
68                 } else if (c === 58) { // ":"
69                     if (!depth && !key && values.length === 1) {
70                         key = values.pop();
71                         continue;
72                     }
73                 // A set of slashes is initially matched as a regular expression, but could be division
74                 } else if (c === 47 && i && tok.length > 1) {  // "/"
75                     // Look at the end of the previous token to determine if the slash is actually division
76                     var match = toks[i-1].match(divisionLookBehind);
77                     if (match && !keywordRegexLookBehind[match[0]]) {
78                         // The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash)
79                         str = str.substr(str.indexOf(tok) + 1);
80                         toks = str.match(bindingToken);
81                         toks.push(',');
82                         i = -1;
83                         // Continue with just the slash
84                         tok = '/';
85                     }
86                 // Increment depth for parentheses, braces, and brackets so that interior commas are ignored
87                 } else if (c === 40 || c === 123 || c === 91) { // '(', '{', '['
88                     ++depth;
89                 } else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']'
90                     --depth;
91                 // The key will be the first token; if it's a string, trim the quotes
92                 } else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'"
93                     tok = tok.slice(1, -1);
94                 }
95                 values.push(tok);
96             }
97         }
98         return result;
99     }
101     // Two-way bindings include a write function that allow the handler to update the value even if it's not an observable.
102     var twoWayBindings = {};
104     function preProcessBindings(bindingsStringOrKeyValueArray, bindingOptions) {
105         bindingOptions = bindingOptions || {};
107         function processKeyValue(key, val) {
108             var writableVal;
109             function callPreprocessHook(obj) {
110                 return (obj && obj['preprocess']) ? (val = obj['preprocess'](val, key, processKeyValue)) : true;
111             }
112             if (!bindingParams) {
113                 if (!callPreprocessHook(ko['getBindingHandler'](key)))
114                     return;
116                 if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
117                     // For two-way bindings, provide a write method in case the value
118                     // isn't a writable observable.
119                     propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
120                 }
121             }
122             // Values are wrapped in a function so that each value can be accessed independently
123             if (makeValueAccessors) {
124                 val = 'function(){return ' + val + ' }';
125             }
126             resultStrings.push("'" + key + "':" + val);
127         }
129         var resultStrings = [],
130             propertyAccessorResultStrings = [],
131             makeValueAccessors = bindingOptions['valueAccessors'],
132             bindingParams = bindingOptions['bindingParams'],
133             keyValueArray = typeof bindingsStringOrKeyValueArray === "string" ?
134                 parseObjectLiteral(bindingsStringOrKeyValueArray) : bindingsStringOrKeyValueArray;
136         ko.utils.arrayForEach(keyValueArray, function(keyValue) {
137             processKeyValue(keyValue.key || keyValue['unknown'], keyValue.value);
138         });
140         if (propertyAccessorResultStrings.length)
141             processKeyValue('_ko_property_writers', "{" + propertyAccessorResultStrings.join(",") + " }");
143         return resultStrings.join(",");
144     }
146     return {
147         bindingRewriteValidators: [],
149         twoWayBindings: twoWayBindings,
151         parseObjectLiteral: parseObjectLiteral,
153         preProcessBindings: preProcessBindings,
155         keyValueArrayContainsKey: function(keyValueArray, key) {
156             for (var i = 0; i < keyValueArray.length; i++)
157                 if (keyValueArray[i]['key'] == key)
158                     return true;
159             return false;
160         },
162         // Internal, private KO utility for updating model properties from within bindings
163         // property:            If the property being updated is (or might be) an observable, pass it here
164         //                      If it turns out to be a writable observable, it will be written to directly
165         // allBindings:         An object with a get method to retrieve bindings in the current execution context.
166         //                      This will be searched for a '_ko_property_writers' property in case you're writing to a non-observable
167         // key:                 The key identifying the property to be written. Example: for { hasFocus: myValue }, write to 'myValue' by specifying the key 'hasFocus'
168         // value:               The value to be written
169         // checkIfDifferent:    If true, and if the property being written is a writable observable, the value will only be written if
170         //                      it is !== existing value on that writable observable
171         writeValueToProperty: function(property, allBindings, key, value, checkIfDifferent) {
172             if (!property || !ko.isObservable(property)) {
173                 var propWriters = allBindings.get('_ko_property_writers');
174                 if (propWriters && propWriters[key])
175                     propWriters[key](value);
176             } else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
177                 property(value);
178             }
179         }
180     };
181 })();
183 ko.exportSymbol('expressionRewriting', ko.expressionRewriting);
184 ko.exportSymbol('expressionRewriting.bindingRewriteValidators', ko.expressionRewriting.bindingRewriteValidators);
185 ko.exportSymbol('expressionRewriting.parseObjectLiteral', ko.expressionRewriting.parseObjectLiteral);
186 ko.exportSymbol('expressionRewriting.preProcessBindings', ko.expressionRewriting.preProcessBindings);
188 // Making bindings explicitly declare themselves as "two way" isn't ideal in the long term (it would be better if
189 // all bindings could use an official 'property writer' API without needing to declare that they might). However,
190 // since this is not, and has never been, a public API (_ko_property_writers was never documented), it's acceptable
191 // as an internal implementation detail in the short term.
192 // For those developers who rely on _ko_property_writers in their custom bindings, we expose _twoWayBindings as an
193 // undocumented feature that makes it relatively easy to upgrade to KO 3.0. However, this is still not an official
194 // public API, and we reserve the right to remove it at any time if we create a real public property writers API.
195 ko.exportSymbol('expressionRewriting._twoWayBindings', ko.expressionRewriting.twoWayBindings);
197 // For backward compatibility, define the following aliases. (Previously, these function names were misleading because
198 // they referred to JSON specifically, even though they actually work with arbitrary JavaScript object literal expressions.)
199 ko.exportSymbol('jsonExpressionRewriting', ko.expressionRewriting);
200 ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.expressionRewriting.preProcessBindings);