123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- // REPEAT binding for Knockout http://knockoutjs.com/
- // (c) Michael Best
- // License: MIT (http://www.opensource.org/licenses/mit-license.php)
- // Version 2.1.0
- (function(factory) {
- if (typeof define === 'function' && define.amd) {
- // [1] AMD anonymous module
- define(['knockout'], factory);
- } else if (typeof exports === 'object') {
- // [2] commonJS
- factory(require('knockout'));
- } else {
- // [3] No module loader (plain <script> tag) - put directly in global namespace
- factory(window.ko);
- }
- })(function(ko) {
- if (!ko.virtualElements)
- throw Error('Repeat requires at least Knockout 2.1');
- var ko_bindingFlags = ko.bindingFlags || {};
- var ko_unwrap = ko.utils.unwrapObservable;
- var koProtoName = '__ko_proto__';
- if (ko.version >= "3.0.0") {
- // In Knockout 3.0.0, use the node preprocessor to replace a node with a repeat binding with a virtual element
- var provider = ko.bindingProvider.instance, previousPreprocessFn = provider.preprocessNode;
- provider.preprocessNode = function(node) {
- var newNodes, nodeBinding;
- if (!previousPreprocessFn || !(newNodes = previousPreprocessFn.call(this, node))) {
- if (node.nodeType === 1 && (nodeBinding = node.getAttribute('data-bind'))) {
- if (/^\s*repeat\s*:/.test(nodeBinding)) {
- var leadingComment = node.ownerDocument.createComment('ko ' + nodeBinding),
- trailingComment = node.ownerDocument.createComment('/ko');
- node.parentNode.insertBefore(leadingComment, node);
- node.parentNode.insertBefore(trailingComment, node.nextSibling);
- node.removeAttribute('data-bind');
- newNodes = [leadingComment, node, trailingComment];
- }
- }
- }
- return newNodes;
- };
- }
- ko.virtualElements.allowedBindings.repeat = true;
- ko.bindingHandlers.repeat = {
- flags: ko_bindingFlags.contentBind | ko_bindingFlags.canUseVirtual,
- init: function(element, valueAccessor, allBindingsAccessor, xxx, bindingContext) {
- // Read and set fixed options--these options cannot be changed
- var repeatParam = ko_unwrap(valueAccessor());
- if (repeatParam && typeof repeatParam == 'object' && !('length' in repeatParam)) {
- var repeatIndex = repeatParam.index,
- repeatData = repeatParam.item,
- repeatStep = repeatParam.step,
- repeatReversed = repeatParam.reverse,
- repeatBind = repeatParam.bind,
- repeatInit = repeatParam.init,
- repeatUpdate = repeatParam.update;
- }
- // Set default values for options that need it
- repeatIndex = repeatIndex || '$index';
- repeatData = repeatData || ko.bindingHandlers.repeat.itemName || '$item';
- repeatStep = repeatStep || 1;
- repeatReversed = repeatReversed || false;
- var parent = element.parentNode, placeholder;
- if (element.nodeType == 8) { // virtual element
- // Extract the "children" and find the single element node
- var childNodes = ko.utils.arrayFilter(ko.virtualElements.childNodes(element), function(node) { return node.nodeType == 1;});
- if (childNodes.length !== 1) {
- throw Error("Repeat binding requires a single element to repeat");
- }
- ko.virtualElements.emptyNode(element);
- // The placeholder is the closing comment normally, or the opening comment if reversed
- placeholder = repeatReversed ? element : element.nextSibling;
- // The element to repeat is the contained element
- element = childNodes[0];
- } else { // regular element
- // First clean the element node and remove node's binding
- var origBindString = element.getAttribute('data-bind');
- ko.cleanNode(element);
- element.removeAttribute('data-bind');
- // Original element is no longer needed: delete it and create a placeholder comment
- placeholder = element.ownerDocument.createComment('ko_repeatplaceholder ' + origBindString);
- parent.replaceChild(placeholder, element);
- }
- // extract and remove a data-repeat-bind attribute, if present
- if (!repeatBind) {
- repeatBind = element.getAttribute('data-repeat-bind');
- if (repeatBind) {
- element.removeAttribute('data-repeat-bind');
- }
- }
- // Make a copy of the element node to be copied for each repetition
- var cleanNode = element.cloneNode(true);
- if (typeof repeatBind == "string") {
- cleanNode.setAttribute('data-bind', repeatBind);
- repeatBind = null;
- }
- // Set up persistent data
- var lastRepeatCount = 0,
- notificationObservable = ko.observable(),
- repeatArray, arrayObservable;
- if (repeatInit) {
- repeatInit(parent);
- }
- var subscribable = ko.computed(function() {
- function makeArrayItemAccessor(index) {
- var f = function(newValue) {
- var item = repeatArray[index];
- // Reading the value of the item
- if (!arguments.length) {
- notificationObservable(); // for dependency tracking
- return ko_unwrap(item);
- }
- // Writing a value to the item
- if (ko.isObservable(item)) {
- item(newValue);
- } else if (arrayObservable && arrayObservable.splice) {
- arrayObservable.splice(index, 1, newValue);
- } else {
- repeatArray[index] = newValue;
- }
- return this;
- };
- // Pretend that our accessor function is an observable
- f[koProtoName] = ko.observable;
- return f;
- }
- function makeBinding(item, index, context) {
- return repeatArray
- ? function() { return repeatBind.call(bindingContext.$data, item, index, context); }
- : function() { return repeatBind.call(bindingContext.$data, index, context); }
- }
- // Read and set up variable options--these options can change and will update the binding
- var paramObservable = valueAccessor(), repeatParam = ko_unwrap(paramObservable), repeatCount = 0;
- if (repeatParam && typeof repeatParam == 'object') {
- if ('length' in repeatParam) {
- repeatArray = repeatParam;
- repeatCount = repeatArray.length;
- } else {
- if ('foreach' in repeatParam) {
- repeatArray = ko_unwrap(paramObservable = repeatParam.foreach);
- if (repeatArray && typeof repeatArray == 'object' && 'length' in repeatArray) {
- repeatCount = repeatArray.length || 0;
- } else {
- repeatCount = repeatArray || 0;
- repeatArray = null;
- }
- }
- // If a count value is provided (>0), always output that number of items
- if ('count' in repeatParam)
- repeatCount = ko_unwrap(repeatParam.count) || repeatCount;
- // If a limit is provided, don't output more than the limit
- if ('limit' in repeatParam)
- repeatCount = Math.min(repeatCount, ko_unwrap(repeatParam.limit)) || repeatCount;
- }
- arrayObservable = repeatArray && ko.isObservable(paramObservable) ? paramObservable : null;
- } else {
- repeatCount = repeatParam || 0;
- }
- // Remove nodes from end if array is shorter
- for (; lastRepeatCount > repeatCount; lastRepeatCount-=repeatStep) {
- ko.removeNode(repeatReversed ? placeholder.nextSibling : placeholder.previousSibling);
- }
- // Notify existing nodes of change
- notificationObservable.notifySubscribers();
- // Add nodes to end if array is longer (also initially populates nodes)
- for (; lastRepeatCount < repeatCount; lastRepeatCount+=repeatStep) {
- // Clone node and add to document
- var newNode = cleanNode.cloneNode(true);
- parent.insertBefore(newNode, repeatReversed ? placeholder.nextSibling : placeholder);
- newNode.setAttribute('data-repeat-index', lastRepeatCount);
- // Apply bindings to inserted node
- if (repeatArray && repeatData == '$data') {
- var newContext = bindingContext.createChildContext(makeArrayItemAccessor(lastRepeatCount));
- } else {
- var newContext = bindingContext.extend();
- if (repeatArray)
- newContext[repeatData] = makeArrayItemAccessor(lastRepeatCount);
- }
- newContext[repeatIndex] = lastRepeatCount;
- if (repeatBind) {
- var result = ko.applyBindingsToNode(newNode, makeBinding(newContext[repeatData], lastRepeatCount, newContext), newContext, true),
- shouldBindDescendants = result && result.shouldBindDescendants;
- }
- if (!repeatBind || (result && shouldBindDescendants !== false)) {
- ko.applyBindings(newContext, newNode);
- }
- }
- if (repeatUpdate) {
- repeatUpdate(parent);
- }
- }, null, {disposeWhenNodeIsRemoved: placeholder});
- return { controlsDescendantBindings: true, subscribable: subscribable };
- }
- };
- });
|