migrated knockout asset to bower
[openemr.git] / public / assets / knockout-2-2-1 / src / utils.js
blob4128500fc72b3c9c89b60d13c0a6355df8a46ae5
1 ko.utils = new (function () {
2     var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
4     // 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)
5     var knownEvents = {}, knownEventTypesByEventName = {};
6     var keyEventTypeName = /Firefox\/2/i.test(navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents';
7     knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
8     knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
9     for (var eventType in knownEvents) {
10         var knownEventsForType = knownEvents[eventType];
11         if (knownEventsForType.length) {
12             for (var i = 0, j = knownEventsForType.length; i < j; i++)
13                 knownEventTypesByEventName[knownEventsForType[i]] = eventType;
14         }
15     }
16     var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
18     // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
19     // Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
20     // Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
21     // If there is a future need to detect specific versions of IE10+, we will amend this.
22     var ieVersion = (function() {
23         var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
25         // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
26         while (
27             div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
28             iElems[0]
29         );
30         return version > 4 ? version : undefined;
31     }());
32     var isIe6 = ieVersion === 6,
33         isIe7 = ieVersion === 7;
35     function isClickOnCheckableElement(element, eventType) {
36         if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false;
37         if (eventType.toLowerCase() != "click") return false;
38         var inputType = element.type;
39         return (inputType == "checkbox") || (inputType == "radio");
40     }
42     return {
43         fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
45         arrayForEach: function (array, action) {
46             for (var i = 0, j = array.length; i < j; i++)
47                 action(array[i]);
48         },
50         arrayIndexOf: function (array, item) {
51             if (typeof Array.prototype.indexOf == "function")
52                 return Array.prototype.indexOf.call(array, item);
53             for (var i = 0, j = array.length; i < j; i++)
54                 if (array[i] === item)
55                     return i;
56             return -1;
57         },
59         arrayFirst: function (array, predicate, predicateOwner) {
60             for (var i = 0, j = array.length; i < j; i++)
61                 if (predicate.call(predicateOwner, array[i]))
62                     return array[i];
63             return null;
64         },
66         arrayRemoveItem: function (array, itemToRemove) {
67             var index = ko.utils.arrayIndexOf(array, itemToRemove);
68             if (index >= 0)
69                 array.splice(index, 1);
70         },
72         arrayGetDistinctValues: function (array) {
73             array = array || [];
74             var result = [];
75             for (var i = 0, j = array.length; i < j; i++) {
76                 if (ko.utils.arrayIndexOf(result, array[i]) < 0)
77                     result.push(array[i]);
78             }
79             return result;
80         },
82         arrayMap: function (array, mapping) {
83             array = array || [];
84             var result = [];
85             for (var i = 0, j = array.length; i < j; i++)
86                 result.push(mapping(array[i]));
87             return result;
88         },
90         arrayFilter: function (array, predicate) {
91             array = array || [];
92             var result = [];
93             for (var i = 0, j = array.length; i < j; i++)
94                 if (predicate(array[i]))
95                     result.push(array[i]);
96             return result;
97         },
99         arrayPushAll: function (array, valuesToPush) {
100             if (valuesToPush instanceof Array)
101                 array.push.apply(array, valuesToPush);
102             else
103                 for (var i = 0, j = valuesToPush.length; i < j; i++)
104                     array.push(valuesToPush[i]);
105             return array;
106         },
108         extend: function (target, source) {
109             if (source) {
110                 for(var prop in source) {
111                     if(source.hasOwnProperty(prop)) {
112                         target[prop] = source[prop];
113                     }
114                 }
115             }
116             return target;
117         },
119         emptyDomNode: function (domNode) {
120             while (domNode.firstChild) {
121                 ko.removeNode(domNode.firstChild);
122             }
123         },
125         moveCleanedNodesToContainerElement: function(nodes) {
126             // Ensure it's a real array, as we're about to reparent the nodes and
127             // we don't want the underlying collection to change while we're doing that.
128             var nodesArray = ko.utils.makeArray(nodes);
130             var container = document.createElement('div');
131             for (var i = 0, j = nodesArray.length; i < j; i++) {
132                 container.appendChild(ko.cleanNode(nodesArray[i]));
133             }
134             return container;
135         },
137         cloneNodes: function (nodesArray, shouldCleanNodes) {
138             for (var i = 0, j = nodesArray.length, newNodesArray = []; i < j; i++) {
139                 var clonedNode = nodesArray[i].cloneNode(true);
140                 newNodesArray.push(shouldCleanNodes ? ko.cleanNode(clonedNode) : clonedNode);
141             }
142             return newNodesArray;
143         },
145         setDomNodeChildren: function (domNode, childNodes) {
146             ko.utils.emptyDomNode(domNode);
147             if (childNodes) {
148                 for (var i = 0, j = childNodes.length; i < j; i++)
149                     domNode.appendChild(childNodes[i]);
150             }
151         },
153         replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
154             var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
155             if (nodesToReplaceArray.length > 0) {
156                 var insertionPoint = nodesToReplaceArray[0];
157                 var parent = insertionPoint.parentNode;
158                 for (var i = 0, j = newNodesArray.length; i < j; i++)
159                     parent.insertBefore(newNodesArray[i], insertionPoint);
160                 for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
161                     ko.removeNode(nodesToReplaceArray[i]);
162                 }
163             }
164         },
166         setOptionNodeSelectionState: function (optionNode, isSelected) {
167             // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
168             if (ieVersion < 7)
169                 optionNode.setAttribute("selected", isSelected);
170             else
171                 optionNode.selected = isSelected;
172         },
174         stringTrim: function (string) {
175             return (string || "").replace(stringTrimRegex, "");
176         },
178         stringTokenize: function (string, delimiter) {
179             var result = [];
180             var tokens = (string || "").split(delimiter);
181             for (var i = 0, j = tokens.length; i < j; i++) {
182                 var trimmed = ko.utils.stringTrim(tokens[i]);
183                 if (trimmed !== "")
184                     result.push(trimmed);
185             }
186             return result;
187         },
189         stringStartsWith: function (string, startsWith) {
190             string = string || "";
191             if (startsWith.length > string.length)
192                 return false;
193             return string.substring(0, startsWith.length) === startsWith;
194         },
196         domNodeIsContainedBy: function (node, containedByNode) {
197             if (containedByNode.compareDocumentPosition)
198                 return (containedByNode.compareDocumentPosition(node) & 16) == 16;
199             while (node != null) {
200                 if (node == containedByNode)
201                     return true;
202                 node = node.parentNode;
203             }
204             return false;
205         },
207         domNodeIsAttachedToDocument: function (node) {
208             return ko.utils.domNodeIsContainedBy(node, node.ownerDocument);
209         },
211         tagNameLower: function(element) {
212             // For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case.
213             // Possible future optimization: If we know it's an element from an XHTML document (not HTML),
214             // we don't need to do the .toLowerCase() as it will always be lower case anyway.
215             return element && element.tagName && element.tagName.toLowerCase();
216         },
218         registerEventHandler: function (element, eventType, handler) {
219             var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
220             if (!mustUseAttachEvent && typeof jQuery != "undefined") {
221                 if (isClickOnCheckableElement(element, eventType)) {
222                     // For click events on checkboxes, jQuery interferes with the event handling in an awkward way:
223                     // it toggles the element checked state *after* the click event handlers run, whereas native
224                     // click events toggle the checked state *before* the event handler.
225                     // Fix this by intecepting the handler and applying the correct checkedness before it runs.
226                     var originalHandler = handler;
227                     handler = function(event, eventData) {
228                         var jQuerySuppliedCheckedState = this.checked;
229                         if (eventData)
230                             this.checked = eventData.checkedStateBeforeEvent !== true;
231                         originalHandler.call(this, event);
232                         this.checked = jQuerySuppliedCheckedState; // Restore the state jQuery applied
233                     };
234                 }
235                 jQuery(element)['bind'](eventType, handler);
236             } else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
237                 element.addEventListener(eventType, handler, false);
238             else if (typeof element.attachEvent != "undefined")
239                 element.attachEvent("on" + eventType, function (event) {
240                     handler.call(element, event);
241                 });
242             else
243                 throw new Error("Browser doesn't support addEventListener or attachEvent");
244         },
246         triggerEvent: function (element, eventType) {
247             if (!(element && element.nodeType))
248                 throw new Error("element must be a DOM node when calling triggerEvent");
250             if (typeof jQuery != "undefined") {
251                 var eventData = [];
252                 if (isClickOnCheckableElement(element, eventType)) {
253                     // Work around the jQuery "click events on checkboxes" issue described above by storing the original checked state before triggering the handler
254                     eventData.push({ checkedStateBeforeEvent: element.checked });
255                 }
256                 jQuery(element)['trigger'](eventType, eventData);
257             } else if (typeof document.createEvent == "function") {
258                 if (typeof element.dispatchEvent == "function") {
259                     var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
260                     var event = document.createEvent(eventCategory);
261                     event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
262                     element.dispatchEvent(event);
263                 }
264                 else
265                     throw new Error("The supplied element doesn't support dispatchEvent");
266             } else if (typeof element.fireEvent != "undefined") {
267                 // Unlike other browsers, IE doesn't change the checked state of checkboxes/radiobuttons when you trigger their "click" event
268                 // so to make it consistent, we'll do it manually here
269                 if (isClickOnCheckableElement(element, eventType))
270                     element.checked = element.checked !== true;
271                 element.fireEvent("on" + eventType);
272             }
273             else
274                 throw new Error("Browser doesn't support triggering events");
275         },
277         unwrapObservable: function (value) {
278             return ko.isObservable(value) ? value() : value;
279         },
281         peekObservable: function (value) {
282             return ko.isObservable(value) ? value.peek() : value;
283         },
285         toggleDomNodeCssClass: function (node, classNames, shouldHaveClass) {
286             if (classNames) {
287                 var cssClassNameRegex = /[\w-]+/g,
288                     currentClassNames = node.className.match(cssClassNameRegex) || [];
289                 ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
290                     var indexOfClass = ko.utils.arrayIndexOf(currentClassNames, className);
291                     if (indexOfClass >= 0) {
292                         if (!shouldHaveClass)
293                             currentClassNames.splice(indexOfClass, 1);
294                     } else {
295                         if (shouldHaveClass)
296                             currentClassNames.push(className);
297                     }
298                 });
299                 node.className = currentClassNames.join(" ");
300             }
301         },
303         setTextContent: function(element, textContent) {
304             var value = ko.utils.unwrapObservable(textContent);
305             if ((value === null) || (value === undefined))
306                 value = "";
308             if (element.nodeType === 3) {
309                 element.data = value;
310             } else {
311                 // We need there to be exactly one child: a text node.
312                 // If there are no children, more than one, or if it's not a text node,
313                 // we'll clear everything and create a single text node.
314                 var innerTextNode = ko.virtualElements.firstChild(element);
315                 if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) {
316                     ko.virtualElements.setDomNodeChildren(element, [document.createTextNode(value)]);
317                 } else {
318                     innerTextNode.data = value;
319                 }
321                 ko.utils.forceRefresh(element);
322             }
323         },
325         setElementName: function(element, name) {
326             element.name = name;
328             // Workaround IE 6/7 issue
329             // - https://github.com/SteveSanderson/knockout/issues/197
330             // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
331             if (ieVersion <= 7) {
332                 try {
333                     element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
334                 }
335                 catch(e) {} // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View"
336             }
337         },
339         forceRefresh: function(node) {
340             // Workaround for an IE9 rendering bug - https://github.com/SteveSanderson/knockout/issues/209
341             if (ieVersion >= 9) {
342                 // For text nodes and comment nodes (most likely virtual elements), we will have to refresh the container
343                 var elem = node.nodeType == 1 ? node : node.parentNode;
344                 if (elem.style)
345                     elem.style.zoom = elem.style.zoom;
346             }
347         },
349         ensureSelectElementIsRenderedCorrectly: function(selectElement) {
350             // 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.
351             // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
352             if (ieVersion >= 9) {
353                 var originalWidth = selectElement.style.width;
354                 selectElement.style.width = 0;
355                 selectElement.style.width = originalWidth;
356             }
357         },
359         range: function (min, max) {
360             min = ko.utils.unwrapObservable(min);
361             max = ko.utils.unwrapObservable(max);
362             var result = [];
363             for (var i = min; i <= max; i++)
364                 result.push(i);
365             return result;
366         },
368         makeArray: function(arrayLikeObject) {
369             var result = [];
370             for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
371                 result.push(arrayLikeObject[i]);
372             };
373             return result;
374         },
376         isIe6 : isIe6,
377         isIe7 : isIe7,
378         ieVersion : ieVersion,
380         getFormFields: function(form, fieldName) {
381             var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
382             var isMatchingField = (typeof fieldName == 'string')
383                 ? function(field) { return field.name === fieldName }
384                 : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
385             var matches = [];
386             for (var i = fields.length - 1; i >= 0; i--) {
387                 if (isMatchingField(fields[i]))
388                     matches.push(fields[i]);
389             };
390             return matches;
391         },
393         parseJson: function (jsonString) {
394             if (typeof jsonString == "string") {
395                 jsonString = ko.utils.stringTrim(jsonString);
396                 if (jsonString) {
397                     if (window.JSON && window.JSON.parse) // Use native parsing where available
398                         return window.JSON.parse(jsonString);
399                     return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
400                 }
401             }
402             return null;
403         },
405         stringifyJson: function (data, replacer, space) {   // replacer and space are optional
406             if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined"))
407                 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");
408             return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
409         },
411         postJson: function (urlOrForm, data, options) {
412             options = options || {};
413             var params = options['params'] || {};
414             var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
415             var url = urlOrForm;
417             // If we were given a form, use its 'action' URL and pick out any requested field values
418             if((typeof urlOrForm == 'object') && (ko.utils.tagNameLower(urlOrForm) === "form")) {
419                 var originalForm = urlOrForm;
420                 url = originalForm.action;
421                 for (var i = includeFields.length - 1; i >= 0; i--) {
422                     var fields = ko.utils.getFormFields(originalForm, includeFields[i]);
423                     for (var j = fields.length - 1; j >= 0; j--)
424                         params[fields[j].name] = fields[j].value;
425                 }
426             }
428             data = ko.utils.unwrapObservable(data);
429             var form = document.createElement("form");
430             form.style.display = "none";
431             form.action = url;
432             form.method = "post";
433             for (var key in data) {
434                 var input = document.createElement("input");
435                 input.name = key;
436                 input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
437                 form.appendChild(input);
438             }
439             for (var key in params) {
440                 var input = document.createElement("input");
441                 input.name = key;
442                 input.value = params[key];
443                 form.appendChild(input);
444             }
445             document.body.appendChild(form);
446             options['submitter'] ? options['submitter'](form) : form.submit();
447             setTimeout(function () { form.parentNode.removeChild(form); }, 0);
448         }
449     }
450 })();
452 ko.exportSymbol('utils', ko.utils);
453 ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach);
454 ko.exportSymbol('utils.arrayFirst', ko.utils.arrayFirst);
455 ko.exportSymbol('utils.arrayFilter', ko.utils.arrayFilter);
456 ko.exportSymbol('utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues);
457 ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
458 ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
459 ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
460 ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
461 ko.exportSymbol('utils.extend', ko.utils.extend);
462 ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
463 ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
464 ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable);
465 ko.exportSymbol('utils.postJson', ko.utils.postJson);
466 ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
467 ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler);
468 ko.exportSymbol('utils.stringifyJson', ko.utils.stringifyJson);
469 ko.exportSymbol('utils.range', ko.utils.range);
470 ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);
471 ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
472 ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
474 if (!Function.prototype['bind']) {
475     // 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)
476     // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
477     Function.prototype['bind'] = function (object) {
478         var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift();
479         return function () {
480             return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
481         };
482     };