Advertisement
Guest User

Untitled

a guest
Jan 18th, 2019
123
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*!
  2.   Knockout Fast Foreach v0.4.1 (2015-07-17T14:06:15.974Z)
  3.   By: Brian M Hunt (C) 2015
  4.   License: MIT
  5.  
  6.   Adds `fastForEach` to `ko.bindingHandlers`.
  7. */
  8. (function (root, factory) {
  9.   if (typeof define === 'function' && define.amd) {
  10.     define(['knockout'], factory);
  11.   } else if (typeof exports === 'object') {
  12.     module.exports = factory(require('knockout'));
  13.   } else {
  14.     root.KnockoutFastForeach = factory(root.ko);
  15.   }
  16. }(this, function (ko) {
  17.   "use strict";
  18. // index.js
  19. // --------
  20. // Fast For Each
  21. //
  22. // Employing sound techniques to make a faster Knockout foreach binding.
  23. // --------
  24.  
  25. //      Utilities
  26.  
  27. // from https://github.com/jonschlinkert/is-plain-object
  28. function isPlainObject(o) {
  29.   return !!o && typeof o === 'object' && o.constructor === Object;
  30. }
  31.  
  32. // From knockout/src/virtualElements.js
  33. var commentNodesHaveTextProperty = document && document.createComment("test").text === "<!--test-->";
  34. var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+([\s\S]+))?\s*-->$/ : /^\s*ko(?:\s+([\s\S]+))?\s*$/;
  35. var supportsDocumentFragment = document && typeof document.createDocumentFragment === "function";
  36. function isVirtualNode(node) {
  37.   return (node.nodeType === 8) && startCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue);
  38. }
  39.  
  40.  
  41. // Get a copy of the (possibly virtual) child nodes of the given element,
  42. // put them into a container, then empty the given node.
  43. function makeTemplateNode(sourceNode) {
  44.   var container = document.createElement("div");
  45.   var parentNode;
  46.   if (sourceNode.content) {
  47.     // For e.g. <template> tags
  48.     parentNode = sourceNode.content;
  49.   } else if (sourceNode.tagName === 'SCRIPT') {
  50.     parentNode = document.createElement("div");
  51.     parentNode.innerHTML = sourceNode.text;
  52.   } else {
  53.     // Anything else e.g. <div>
  54.     parentNode = sourceNode;
  55.   }
  56.   ko.utils.arrayForEach(ko.virtualElements.childNodes(parentNode), function (child) {
  57.     // FIXME - This cloneNode could be expensive; we may prefer to iterate over the
  58.     // parentNode children in reverse (so as not to foul the indexes as childNodes are
  59.     // removed from parentNode when inserted into the container)
  60.     if (child) {
  61.       container.insertBefore(child.cloneNode(true), null);
  62.     }
  63.   });
  64.   return container;
  65. }
  66.  
  67. function insertAllAfter(containerNode, nodeOrNodeArrayToInsert, insertAfterNode) {
  68.   var frag, len, i;
  69.   // poor man's node and array check, should be enough for this
  70.   if (typeof nodeOrNodeArrayToInsert.nodeType !== "undefined" && typeof nodeOrNodeArrayToInsert.length === "undefined") {
  71.     throw new Error("Expected a single node or a node array");
  72.   }
  73.  
  74.   if (typeof nodeOrNodeArrayToInsert.nodeType !== "undefined") {
  75.     ko.virtualElements.insertAfter(containerNode, nodeOrNodeArrayToInsert, insertAfterNode);
  76.     return;
  77.   }
  78.  
  79.   if (nodeOrNodeArrayToInsert.length === 1) {
  80.     ko.virtualElements.insertAfter(containerNode, nodeOrNodeArrayToInsert[0], insertAfterNode);
  81.     return;
  82.   }
  83.  
  84.   if (supportsDocumentFragment) {
  85.     frag = document.createDocumentFragment();
  86.  
  87.     for (i = 0, len = nodeOrNodeArrayToInsert.length; i !== len; ++i) {
  88.       frag.appendChild(nodeOrNodeArrayToInsert[i]);
  89.     }
  90.     ko.virtualElements.insertAfter(containerNode, frag, insertAfterNode);
  91.   } else {
  92.     // Nodes are inserted in reverse order - pushed down immediately after
  93.     // the last node for the previous item or as the first node of element.
  94.     for (i = nodeOrNodeArrayToInsert.length - 1; i >= 0; --i) {
  95.       var child = nodeOrNodeArrayToInsert[i];
  96.       if (!child) {
  97.         return;
  98.       }
  99.       ko.virtualElements.insertAfter(containerNode, child, insertAfterNode);
  100.     }
  101.   }
  102. }
  103.  
  104. // Mimic a KO change item 'add'
  105. function valueToChangeAddItem(value, index) {
  106.   return {
  107.     status: 'added',
  108.     value: value,
  109.     index: index
  110.   };
  111. }
  112.  
  113. function isAdditionAdjacentToLast(changeIndex, arrayChanges) {
  114.   return changeIndex > 0 &&
  115.     changeIndex < arrayChanges.length &&
  116.     arrayChanges[changeIndex].status === "added" &&
  117.     arrayChanges[changeIndex - 1].status === "added" &&
  118.     arrayChanges[changeIndex - 1].index === arrayChanges[changeIndex].index - 1;
  119. }
  120.  
  121. function FastForEach(spec) {
  122.   this.element = spec.element;
  123.   this.container = isVirtualNode(this.element) ?
  124.                    this.element.parentNode : this.element;
  125.   this.$context = spec.$context;
  126.   this.data = spec.data;
  127.   this.as = spec.as;
  128.   this.noContext = spec.noContext;
  129.   this.templateNode = makeTemplateNode(
  130.     spec.name ? document.getElementById(spec.name).cloneNode(true) : spec.element
  131.   );
  132.   this.afterQueueFlush = spec.afterQueueFlush;
  133.   this.beforeQueueFlush = spec.beforeQueueFlush;
  134.   this.changeQueue = [];
  135.   this.lastNodesList = [];
  136.   this.indexesToDelete = [];
  137.   this.rendering_queued = false;
  138.  
  139.   // Remove existing content.
  140.   ko.virtualElements.emptyNode(this.element);
  141.  
  142.   // Prime content
  143.   var primeData = ko.unwrap(this.data);
  144.   if (primeData.map) {
  145.     this.onArrayChange(primeData.map(valueToChangeAddItem));
  146.   }
  147.  
  148.   // Watch for changes
  149.   if (ko.isObservable(this.data)) {
  150.     if (!this.data.indexOf) {
  151.       // Make sure the observable is trackable.
  152.       this.data = this.data.extend({trackArrayChanges: true});
  153.     }
  154.     this.changeSubs = this.data.subscribe(this.onArrayChange, this, 'arrayChange');
  155.   }
  156. }
  157.  
  158.  
  159. FastForEach.animateFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame ||
  160.   window.mozRequestAnimationFrame || window.msRequestAnimationFrame ||
  161.   function(cb) { return window.setTimeout(cb, 1000 / 60); };
  162.  
  163.  
  164. FastForEach.prototype.dispose = function () {
  165.   if (this.changeSubs) {
  166.     this.changeSubs.dispose();
  167.   }
  168. };
  169.  
  170.  
  171. // If the array changes we register the change.
  172. FastForEach.prototype.onArrayChange = function (changeSet) {
  173.   var self = this;
  174.   var changeMap = {
  175.     added: [],
  176.     deleted: []
  177.   };
  178.   for (var i = 0, len = changeSet.length; i < len; i++) {
  179.     // the change is appended to a last change info object when both are 'added' and have indexes next to each other
  180.     // here I presume that ko is sending changes in monotonic order (in index variable) which happens to be true, tested with push and splice with multiple pushed values
  181.     if (isAdditionAdjacentToLast(i, changeSet)) {
  182.       var batchValues = changeMap.added[changeMap.added.length - 1].values;
  183.       if (!batchValues) {
  184.         // transform the last addition into a batch addition object
  185.         var lastAddition = changeMap.added.pop();
  186.         var batchAddition = {
  187.           isBatch: true,
  188.           status: 'added',
  189.           index: lastAddition.index,
  190.           values: [lastAddition.value]
  191.         };
  192.         batchValues = batchAddition.values;
  193.         changeMap.added.push(batchAddition);
  194.       }
  195.       batchValues.push(changeSet[i].value);
  196.     } else {
  197.       changeMap[changeSet[i].status].push(changeSet[i]);
  198.     }
  199.   }
  200.   if (changeMap.deleted.length > 0) {
  201.     this.changeQueue.push.apply(this.changeQueue, changeMap.deleted);
  202.     this.changeQueue.push({status: 'clearDeletedIndexes'});
  203.   }
  204.   this.changeQueue.push.apply(this.changeQueue, changeMap.added);
  205.   // Once a change is registered, the ticking count-down starts for the processQueue.
  206.   if (this.changeQueue.length > 0 && !this.rendering_queued) {
  207.     this.rendering_queued = true;
  208.     FastForEach.animateFrame.call(window, function () { self.processQueue(); });
  209.   }
  210. };
  211.  
  212.  
  213. // Reflect all the changes in the queue in the DOM, then wipe the queue.
  214. FastForEach.prototype.processQueue = function () {
  215.   var self = this;
  216.  
  217.   // Callback so folks can do things before the queue flush.
  218.   if (typeof this.beforeQueueFlush === 'function') {
  219.     this.beforeQueueFlush(this.changeQueue);
  220.   }
  221.  
  222.   ko.utils.arrayForEach(this.changeQueue, function (changeItem) {
  223.     // console.log(self.data(), "CI", JSON.stringify(changeItem, null, 2), JSON.stringify($(self.element).text()))
  224.     self[changeItem.status](changeItem);
  225.     // console.log("  ==> ", JSON.stringify($(self.element).text()))
  226.   });
  227.   this.rendering_queued = false;
  228.   // Callback so folks can do things.
  229.   if (typeof this.afterQueueFlush === 'function') {
  230.     this.afterQueueFlush(this.changeQueue);
  231.   }
  232.   this.changeQueue = [];
  233. };
  234.  
  235.  
  236. // Process a changeItem with {status: 'added', ...}
  237. FastForEach.prototype.added = function (changeItem) {
  238.   var index = changeItem.index;
  239.   var valuesToAdd = changeItem.isBatch ? changeItem.values : [changeItem.value];
  240.   var referenceElement = this.lastNodesList[index - 1] || null;
  241.   // gather all childnodes for a possible batch insertion
  242.   var allChildNodes = [];
  243.  
  244.   for (var i = 0, len = valuesToAdd.length; i < len; ++i) {
  245.     var templateClone = this.templateNode.cloneNode(true);
  246.     var childContext;
  247.  
  248.     if (this.noContext) {
  249.       childContext = this.$context.extend({
  250.         '$item': valuesToAdd[i]
  251.       });
  252.     } else {
  253.       childContext = this.$context.createChildContext(valuesToAdd[i], this.as || null);
  254.     }
  255.  
  256.     // apply bindings first, and then process child nodes, because bindings can add childnodes
  257.     ko.applyBindingsToDescendants(childContext, templateClone);
  258.  
  259.     var childNodes = ko.virtualElements.childNodes(templateClone);
  260.     // Note discussion at https://github.com/angular/angular.js/issues/7851
  261.     allChildNodes.push.apply(allChildNodes, Array.prototype.slice.call(childNodes));
  262.     this.lastNodesList.splice(index + i, 0, childNodes[childNodes.length - 1]);
  263.   }
  264.  
  265.   insertAllAfter(this.element, allChildNodes, referenceElement);
  266. };
  267.  
  268.  
  269. // Process a changeItem with {status: 'deleted', ...}
  270. FastForEach.prototype.deleted = function (changeItem) {
  271.   var index = changeItem.index;
  272.   var ptr = this.lastNodesList[index],
  273.       // We use this.element because that will be the last previous node
  274.       // for virtual element lists.
  275.       lastNode = this.lastNodesList[index - 1] || this.element;
  276.   do {
  277.     ptr = ptr.previousSibling;
  278.     ko.removeNode((ptr && ptr.nextSibling) || ko.virtualElements.firstChild(this.element));
  279.   } while (ptr && ptr !== lastNode);
  280.   // The "last node" in the DOM from which we begin our delets of the next adjacent node is
  281.   // now the sibling that preceded the first node of this item.
  282.   this.lastNodesList[index] = this.lastNodesList[index - 1];
  283.   this.indexesToDelete.push(index);
  284. };
  285.  
  286.  
  287. // We batch our deletion of item indexes in our parallel array.
  288. // See brianmhunt/knockout-fast-foreach#6/#8
  289. FastForEach.prototype.clearDeletedIndexes = function () {
  290.   // We iterate in reverse on the presumption (following the unit tests) that KO's diff engine
  291.   // processes diffs (esp. deletes) monotonically ascending i.e. from index 0 -> N.
  292.   for (var i = this.indexesToDelete.length - 1; i >= 0; --i) {
  293.     this.lastNodesList.splice(this.indexesToDelete[i], 1);
  294.   }
  295.   this.indexesToDelete = [];
  296. };
  297.  
  298.  
  299. ko.bindingHandlers.fastForEach = {
  300.   // Valid valueAccessors:
  301.   //    []
  302.   //    ko.observable([])
  303.   //    ko.observableArray([])
  304.   //    ko.computed
  305.   //    {data: array, name: string, as: string}
  306.   init: function init(element, valueAccessor, bindings, vm, context) {
  307.     var value = valueAccessor(),
  308.         ffe;
  309.     if (isPlainObject(value)) {
  310.       value.element = value.element || element;
  311.       value.$context = context;
  312.       ffe = new FastForEach(value);
  313.     } else {
  314.       ffe = new FastForEach({
  315.         element: element,
  316.         data: ko.unwrap(context.$rawData) === value ? context.$rawData : value,
  317.         $context: context
  318.       });
  319.     }
  320.     ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
  321.       ffe.dispose();
  322.     });
  323.     return {controlsDescendantBindings: true};
  324.   },
  325.  
  326.   // Export for testing, debugging, and overloading.
  327.   FastForEach: FastForEach
  328. };
  329.  
  330. ko.virtualElements.allowedBindings.fastForEach = true;
  331. }));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement