UNPKG

@danielkalen/simplybind

Version:

Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.

1,387 lines (1,382 loc) 53.5 kB
// Generated by CoffeeScript 1.10.0 (function() { var Binding, BindingInterface, BindingInterfacePrivate, GroupBinding, METHOD_bothWays, METHOD_chainTo, METHOD_condition, METHOD_conditionAll, METHOD_of, METHOD_pollEvery, METHOD_set, METHOD_setOption, METHOD_stopPolling, METHOD_transform, METHOD_transformAll, METHOD_transformSelf, METHOD_unBind, SimplyBind, addToNodeStore, applyPlaceholders, arrayMutatorMethods, boundInstances, cache, cachedEvent, changeEvent, checkIf, cloneObject, convertToLive, convertToReg, currentID, defaultOptions, defineProperty, dummyPropertyDescriptor, errors, escapeRegEx, eventUpdateHandler, extendState, fetchDescriptor, genID, genObj, genProxiedInterface, genSelfUpdater, getDescriptor, getErrSource, pholderRegEx, pholderRegExSplit, placeholder, proto, requiresDomDescriptorFix, scanTextNodesPlaceholders, setPholderRegEx, setValueNoop, settings, targetIncludes, textContent, throwError, throwErrorBadArg, throwWarning, windowPropsToIgnore; currentID = 0; arrayMutatorMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort']; dummyPropertyDescriptor = {}; boundInstances = {}; placeholder = ['{{', '}}']; settings = Object.create({ silent: false }, { placeholder: { get: function() { return placeholder; }, set: function(newPlaceholder) { if (checkIf.isArray(newPlaceholder) && newPlaceholder.length === 2) { placeholder = newPlaceholder; setPholderRegEx(); } } } }); defaultOptions = { delay: false, throttle: false, simpleSelector: false, promiseTransforms: false, dispatchEvents: false, sendArrayCopies: false, updateEvenIfSame: false, updateOnBind: true }; defineProperty = Object.defineProperty; getDescriptor = Object.getOwnPropertyDescriptor; cachedEvent = null; changeEvent = function() { var event; if (!cachedEvent) { event = cachedEvent = document.createEvent('Event'); event.initEvent('change', true, false); event._sb = true; } return cachedEvent; }; requiresDomDescriptorFix = (!('className' in Element.prototype)) || !getDescriptor(Element.prototype, 'className').get; windowPropsToIgnore = ['innerWidth', 'innerHeight', 'outerWidth', 'outerHeight', 'scrollX', 'scrollY', 'pageXOffset', 'pageYOffset', 'screenX', 'screenY', 'screenLeft', 'screenTop']; setValueNoop = function(v, publisher) { return this.updateAllSubs(publisher || this); }; genID = function() { return '' + (++currentID); }; genObj = function() { return Object.create(null); }; genProxiedInterface = function(isSub, completeCallback) { return function(subject, customOptions, saveOptions) { return SimplyBind(subject, customOptions, saveOptions, isSub, completeCallback); }; }; genSelfUpdater = function(binding, fetchValue) { return binding.selfUpdater || (binding.selfUpdater = new Binding(function() { if (fetchValue) { return binding.setValue(binding.fetchDirectValue(), binding, true); } else { return binding.updateAllSubs(binding); } }, 'Func', {})); }; targetIncludes = function(target, item) { return target && target.indexOf(item) !== -1; }; checkIf = { isDefined: function(subject) { return subject !== void 0; }, isArray: function(subject) { return subject instanceof Array; }, isObject: function(subject) { return typeof subject === 'object' && subject; }, isString: function(subject) { return typeof subject === 'string'; }, isNumber: function(subject) { return typeof subject === 'number'; }, isFunction: function(subject) { return typeof subject === 'function'; }, isBindingInterface: function(subject) { return subject instanceof BindingInterface; }, isBinding: function(subject) { return subject instanceof Binding; }, isIterable: function(subject) { return checkIf.isObject(subject) && checkIf.isNumber(subject.length); }, isDom: function(subject) { return subject.nodeName && subject.nodeType === 1; }, isDomInput: function(subject) { var nodeName; nodeName = subject.nodeName; return nodeName === 'INPUT' || nodeName === 'TEXTAREA' || nodeName === 'SELECT'; }, isDomRadio: function(subject) { return subject.type === 'radio'; }, isDomCheckbox: function(subject) { return subject.type === 'checkbox'; }, isElCollection: function(subject) { return (subject instanceof NodeList) || (subject instanceof HTMLCollection) || (checkIf.isDefined(jQuery) && subject instanceof jQuery); }, domElsAreSame: function(iterable) { var itemsWithSameType, type; type = iterable[0].type; itemsWithSameType = [].filter.call(iterable, function(item) { return item.type === type; }); return itemsWithSameType.length === iterable.length; } }; fetchDescriptor = function(object, property, isProto) { var descriptor, objectProto; descriptor = getDescriptor(object, property); if (descriptor) { if (isProto) { descriptor.configurable = true; } return descriptor; } else if (objectProto = Object.getPrototypeOf(object)) { return fetchDescriptor(objectProto, property, true); } }; convertToLive = function(bindingInstance, object, onlyArrayMethods) { var _, context, getterValue, origFn, propertyDescriptor, proxyFn, shouldIndicateUpdateIsFromSelf, shouldWriteLiveProp, slice, typeIsArray; _ = bindingInstance; if (!_.origDescriptor) { _.origDescriptor = fetchDescriptor(object, _.property); } if (onlyArrayMethods) { arrayMutatorMethods.forEach(function(method) { return defineProperty(object, method, { configurable: true, value: function() { var result; result = Array.prototype[method].apply(object, arguments); _.updateAllSubs(_); return result; } }); }); } else { if (_.type === 'Proxy') { origFn = _.origFn = _.value; context = object; _.value = { result: null, args: null }; if (checkIf.isFunction(origFn)) { slice = [].slice; getterValue = proxyFn = function() { var args, result; args = slice.call(arguments); _.value.args = args = _.selfTransform ? _.selfTransform(args) : args; _.value.result = result = origFn.apply(context, args); _.updateAllSubs(_); return result; }; defineProperty(object, _.property, { configurable: _.isLiveProp = true, get: function() { return getterValue; }, set: function(newValue) { if (!checkIf.isFunction(newValue)) { getterValue = newValue; } else if (newValue !== origFn) { if (newValue !== proxyFn) { origFn = _.origFn = newValue; } if (getterValue !== proxyFn) { getterValue = proxyFn; } } } }); } } else if (!targetIncludes(_.type, 'DOM') && !(_.object === window && targetIncludes(windowPropsToIgnore, _.property))) { propertyDescriptor = _.origDescriptor || dummyPropertyDescriptor; if (propertyDescriptor.get) { _.origGetter = propertyDescriptor.get.bind(object); } if (propertyDescriptor.set) { _.origSetter = propertyDescriptor.set.bind(object); } shouldWriteLiveProp = propertyDescriptor.configurable; shouldWriteLiveProp = shouldWriteLiveProp && object.constructor !== CSSStyleDeclaration; /** * There is a bug in webkit/blink engines in which native attributes/properties * of DOM elements are not exposed on the element's prototype and instead is * exposed directly on the element instance; when looking up the property descriptor * of the element a data descriptor is returned instead of an accessor descriptor * (i.e. descriptor with getter/setter) which means we are not able to define our * own proxy getter/setters. This was fixed only in April 2015 in Chrome v43 and * Safari v10. Although we won't be able to get notified when the objects get * their values set, we would at least provide working functionality lacking update * listeners. Since v1.14.0 HTMLInputElement::value bindings invoke the original * getter and setter methods in Binding::setValue(), and since we want to avoid * increasing the amount of logic present in Binding::setValue() for performance * reasons, we patch those setters here. We clone the target element and check for * the existence of the target property - if it exists then it indicates the target * property is a native property (since only native properties are copied over in * Element::cloneNode). This patching is only for native properties. * * https://bugs.webkit.org/show_bug.cgi?id=49739 * https://bugs.webkit.org/show_bug.cgi?id=75297 * https://bugs.chromium.org/p/chromium/issues/detail?id=43394 * https://bugs.chromium.org/p/chromium/issues/detail?id=431492 * https://bugs.chromium.org/p/chromium/issues/detail?id=13175 * https://developers.google.com/web/updates/2015/04/DOM-attributes-now-on-the-prototype-chain */ if (requiresDomDescriptorFix && _.isDom && _.property in object.cloneNode(false)) { _.origDescriptor = shouldWriteLiveProp = false; _.isLiveProp = true; _.origGetter = function() { return _.object[_.property]; }; _.origSetter = function(newValue) { return _.object[_.property] = newValue; }; } if (shouldWriteLiveProp) { typeIsArray = _.type === 'Array'; shouldIndicateUpdateIsFromSelf = !_.origSetter && !typeIsArray; defineProperty(object, _.property, { configurable: _.isLiveProp = true, enumerable: propertyDescriptor.enumerable, get: _.origGetter || function() { return _.value; }, set: function(newValue) { _.setValue(newValue, _, shouldIndicateUpdateIsFromSelf); } }); if (typeIsArray) { convertToLive(_, object[_.property], true); } } } } }; convertToReg = function(bindingInstance, object, onlyArrayMethods) { var _, j, len, method, newDescriptor, results1; if (onlyArrayMethods) { results1 = []; for (j = 0, len = arrayMutatorMethods.length; j < len; j++) { method = arrayMutatorMethods[j]; results1.push(delete object[method]); } return results1; } else { _ = bindingInstance; newDescriptor = _.origDescriptor; if (!(newDescriptor.set || newDescriptor.get)) { newDescriptor.value = _.origFn || _.value; } return defineProperty(object, _.property, newDescriptor); } }; cloneObject = function(object) { var clone, key; clone = genObj(); for (key in object) { clone[key] = object[key]; } return clone; }; extendState = function(base, stateToInherit) { var j, key, len, stateMapping; stateMapping = Object.keys(stateToInherit); for (j = 0, len = stateMapping.length; j < len; j++) { key = stateMapping[j]; base[key] = stateToInherit[key]; } }; cache = { get: function(object, isFunction, selector, isMultiChoice) { var sampleItem; if (isFunction) { return boundInstances[object._sb_ID]; } else { if (isMultiChoice && object[0]._sb_map) { sampleItem = boundInstances[object[0]._sb_map[selector]]; if (sampleItem.groupBinding) { return sampleItem.groupBinding; } } if (object._sb_map && object._sb_map[selector]) { return boundInstances[object._sb_map[selector]]; } } }, set: function(B, isFunction) { var propsMap, selector; if (isFunction) { defineProperty(B.object, '_sb_ID', { 'configurable': true, 'value': B.ID }); } else { selector = B.selector; if (B.object._sb_map) { B.object._sb_map[selector] = B.ID; } else { propsMap = {}; propsMap[selector] = B.ID; defineProperty(B.object, '_sb_map', { 'configurable': true, 'value': propsMap }); } } } }; escapeRegEx = /[.*+?^${}()|[\]\\]/g; pholderRegEx = pholderRegExSplit = null; setPholderRegEx = function() { var end, middle, start; start = settings.placeholder[0].replace(escapeRegEx, '\\$&'); end = settings.placeholder[1].replace(escapeRegEx, '\\$&'); middle = "[^" + end + "]+"; pholderRegEx = new RegExp(start + "(" + middle + ")" + end, 'g'); pholderRegExSplit = new RegExp("" + start + middle + end, 'g'); }; setPholderRegEx(); applyPlaceholders = function(contexts, values, indexMap) { var contextPart, index, j, len, output; output = ''; for (index = j = 0, len = contexts.length; j < len; index = ++j) { contextPart = contexts[index]; output += contextPart; if (indexMap[index]) { output += values[indexMap[index]]; } } return output; }; textContent = 'textContent'; addToNodeStore = function(nodeStore, node, targetPlaceholder) { if (nodeStore[targetPlaceholder] == null) { nodeStore[targetPlaceholder] = []; } nodeStore[targetPlaceholder].push(node); }; scanTextNodesPlaceholders = function(element, nodeStore) { var childNodes, index, j, k, len, len1, newFragment, newNode, node, textPiece, textPieces; childNodes = Array.prototype.slice.call(element.childNodes); for (j = 0, len = childNodes.length; j < len; j++) { node = childNodes[j]; if (node.nodeType !== 3) { scanTextNodesPlaceholders(node, nodeStore); } else if (node[textContent].match(pholderRegExSplit)) { textPieces = node[textContent].split(pholderRegEx); if (textPieces.length === 3 && textPieces[0] + textPieces[2] === '') { addToNodeStore(nodeStore, node, textPieces[1]); } else { newFragment = document.createDocumentFragment(); for (index = k = 0, len1 = textPieces.length; k < len1; index = ++k) { textPiece = textPieces[index]; newNode = newFragment.appendChild(document.createTextNode(textPiece)); if (index % 2) { addToNodeStore(nodeStore, newNode, textPiece); } } node.parentNode.replaceChild(newFragment, node); } } } }; throwError = function(errorName) { throw new Error('SimplyBind: ' + (errors[errorName] || errorName)); }; throwWarning = function(warningName, depth) { var errSource, warn; if (!settings.silent) { errSource = getErrSource(depth); warn = errors[warningName]; warn += "\n\n" + errSource; console.warn('SimplyBind: ' + warn); } }; throwErrorBadArg = function(arg) { throwError("Invalid argument/s (" + arg + ")", true); }; getErrSource = function(depth) { return ((new Error).stack || '').split('\n').slice(depth + 3).join('\n'); }; errors = { invalidParamName: "SimplyBind() and .to() only accept a function, an array, a bound object, a string, or a number.", fnOnly: "Only functions are allowed for .transform/.condition/All()", badEventArg: "Invalid argument number in .ofEvent()", emptyList: "Empty collection provided", onlyOneDOMElement: "You can only pass a single DOM element to a binding", mixedElList: "'checked' of Mixed list of element cannot be bound" }; SimplyBind = function(subject, options, saveOptions, isSub, completeCallback) { var interfaceToReturn, newInterface; if ((!subject && subject !== 0) || (!checkIf.isString(subject) && !checkIf.isNumber(subject) && !checkIf.isFunction(subject) && !(subject instanceof Array))) { if (!checkIf.isBindingInterface(subject)) { throwError('invalidParamName'); } } if (checkIf.isObject(subject) && !(subject instanceof Array)) { interfaceToReturn = completeCallback ? completeCallback(subject) : subject.selfClone(); } else { newInterface = new BindingInterface(options); newInterface.saveOptions = saveOptions; newInterface.isSub = isSub; newInterface.completeCallback = completeCallback; if (checkIf.isFunction(subject)) { interfaceToReturn = newInterface.setObject(subject, true); } else { interfaceToReturn = newInterface.setProperty(subject); } } return interfaceToReturn; }; SimplyBind.version = '1.14.2'; SimplyBind.settings = settings; SimplyBind.defaultOptions = defaultOptions; SimplyBind.unBindAll = function(object, bothWays) { var boundID, prop, propMap; if (object && (checkIf.isObject(object) || checkIf.isFunction(object))) { /** * Conditional Checks: * * 1) Make sure the subject object is iterable (and thus a possible candidate for being an element collection) * 2) Make sure the subject object isn't an array binding (since element collection objects don't get directly bound) * 3) Make sure the first element in the collection is a valid object (i.e. isn't undefined and isn't null) * 4) Make sure the first element is a DOM object */ if (checkIf.isIterable(object) && !object._sb_ID && object[0] && (checkIf.isDom(object[0]))) { object = object[0]; } propMap = object._sb_map; if (object._sb_ID) { boundInstances[object._sb_ID].removeAllSubs(bothWays); } if (propMap) { for (prop in propMap) { boundID = propMap[prop]; boundInstances[boundID].removeAllSubs(bothWays); } } } }; Binding = function(object, type, state) { var parentBinding, parentProperty, subjectValue; extendState(this, state); this.optionsDefault = this.saveOptions ? this.options : defaultOptions; this.type = type; this.object = object; this.ID = genID(); this.subs = []; this.subsMeta = genObj(); this.pubsMap = genObj(); this.attachedEvents = []; if (this.type === 'Proxy') { this.setValue = setValueNoop; } /* ========================================================================== */ if (this.isMultiChoice) { this.choices = genObj(); this.object.forEach((function(_this) { return function(choiceEl) { var choiceBinding; choiceBinding = _this.choices[choiceEl.value] = SimplyBind('checked').of(choiceEl)._; choiceBinding.addSub(_this); choiceBinding.subsMeta[_this.ID].transformFn = function() { return choiceBinding; }; choiceBinding.groupBinding = _this; }; })(this)); } if (!(this.type === 'Event' || (this.type === 'Func' && this.isSub))) { if (this.type === 'Pholder') { parentProperty = this.descriptor && !targetIncludes(this.descriptor, 'multi') ? this.descriptor + ":" + this.property : this.property; parentBinding = this.parentBinding = SimplyBind(parentProperty).of(object)._; parentBinding.scanForPholders(); this.value = parentBinding.pholderValues[this.pholder]; if (parentBinding.textNodes) { this.textNodes = parentBinding.textNodes[this.pholder]; } } else { this.value = subjectValue = this.fetchDirectValue(); if (this.type === 'ObjectProp' && !checkIf.isDefined(subjectValue)) { this.object[this.property] = subjectValue; } convertToLive(this, this.object); } } this.attachEvents(); return boundInstances[this.ID] = this; }; Binding.prototype = { addSub: function(sub, options, updateOnce, updateEvenIfSame) { var alreadyHadSub, j, len, metaData, ref, subItem; if (sub.isMulti) { ref = sub.bindings; for (j = 0, len = ref.length; j < len; j++) { subItem = ref[j]; this.addSub(subItem, options, updateOnce, updateEvenIfSame); } } else { if (metaData = this.subsMeta[sub.ID]) { alreadyHadSub = true; } else { sub.pubsMap[this.ID] = this; this.subs.unshift(sub); metaData = this.subsMeta[sub.ID] = genObj(); metaData.updateOnce = updateOnce; metaData.opts = cloneObject(options); if (updateEvenIfSame || this.type === 'Event' || this.type === 'Proxy' || this.type === 'Array') { metaData.opts.updateEvenIfSame = true; } metaData.valueRef = sub.type === 'Func' ? 'valuePassed' : 'value'; } } return alreadyHadSub; }, removeSub: function(sub, bothWays) { var j, len, ref, subItem; if (sub.isMulti) { ref = sub.bindings; for (j = 0, len = ref.length; j < len; j++) { subItem = ref[j]; this.removeSub(subItem, bothWays); } } else { if (this.subsMeta[sub.ID]) { this.subs.splice(this.subs.indexOf(sub), 1); delete this.subsMeta[sub.ID]; delete sub.pubsMap[this.ID]; } if (bothWays) { sub.removeSub(this); delete this.pubsMap[sub.ID]; } } if (this.subs.length === 0 && Object.keys(this.pubsMap).length === 0) { this.destroy(); } }, removeAllSubs: function(bothWays) { var j, len, ref, sub; ref = this.subs.slice(); for (j = 0, len = ref.length; j < len; j++) { sub = ref[j]; this.removeSub(sub, bothWays); } }, destroy: function() { var event, j, len, ref; delete boundInstances[this.ID]; this.removePollInterval(); if (this.type === 'Event') { ref = this.attachedEvents; for (j = 0, len = ref.length; j < len; j++) { event = ref[j]; this.unRegisterEvent(event, this.customEventMethod.listen); } } else if (this.type === 'Func') { delete this.object._sb_ID; } /* istanbul ignore next */ if (this.isLiveProp && this.origDescriptor) { convertToReg(this, this.object); } if (this.type === 'Array') { convertToReg(this, this.value, true); } if (this.object._sb_map) { delete this.object._sb_map[this.selector]; if (Object.keys(this.object._sb_map).length === 0) { delete this.object._sb_map; } } }, fetchDirectValue: function() { var choiceEl, choiceName, ref, results, type; type = this.type; switch (false) { case type !== 'Func': return this.object(); case type !== 'DOMAttr': return this.object.getAttribute(this.property) || ''; case !this.isMultiChoice: results = []; ref = this.choices; for (choiceName in ref) { choiceEl = ref[choiceName]; if (choiceEl.object.checked) { if (type === 'DOMRadio') { return choiceName; } else { results.push(choiceName); } } } return results; default: return this.object[this.property]; } }, setValue: function(newValue, publisher, fromSelf, fromChangeEvent) { var choiceBinding, choiceName, entireValue, index, j, k, len, len1, n, newChoiceValue, newChoices, newValueArray, overwritePrevious, parent, prevCursror, prevValue, ref, ref1, ref2, targetChoiceBinding, textNode, value; publisher || (publisher = this); if (this.selfTransform) { newValue = this.selfTransform(newValue); } if (!fromSelf) { switch (this.type) { case 'ObjectProp': if (!this.isLiveProp) { if (newValue !== this.value) { this.object[this.property] = newValue; } } else if (this.isDomInput) { if (!fromChangeEvent) { this.origSetter(newValue); if (settings.dispatchEvents) { this.object.dispatchEvent(changeEvent()); } } else if (newValue !== this.origGetter()) { prevCursror = this.object.selectionStart; this.origSetter(newValue); if (prevCursror) { this.object.setSelectionRange(prevCursror, prevCursror); } } } else if (this.origSetter) { this.origSetter(newValue); } break; case 'Pholder': parent = this.parentBinding; parent.pholderValues[this.pholder] = newValue; entireValue = applyPlaceholders(parent.pholderContexts, parent.pholderValues, parent.pholderIndexMap); if (this.textNodes && newValue !== this.value) { ref = this.textNodes; for (j = 0, len = ref.length; j < len; j++) { textNode = ref[j]; textNode[textContent] = newValue; } } if (this.property !== textContent) { parent.setValue(entireValue, publisher); } break; case 'Array': if (newValue !== this.value) { if (!checkIf.isArray(newValue)) { newValue = Array.prototype.concat(newValue); } convertToReg(this, this.value, true); convertToLive(this, newValue = newValue.slice(), true); } break; case 'Func': prevValue = this.valuePassed; this.valuePassed = newValue; newValue = this.object(newValue, prevValue); break; case 'Event': this.isEmitter = true; this.emitEvent(newValue); this.isEmitter = false; break; case 'DOMRadio': if (this.isMultiChoice) { targetChoiceBinding = checkIf.isBinding(newValue) ? newValue : this.choices[newValue]; if (targetChoiceBinding) { newValue = targetChoiceBinding.object.value; ref1 = this.choices; for (n in ref1) { choiceBinding = ref1[n]; choiceBinding.setValue(choiceBinding.ID === targetChoiceBinding.ID, publisher); } } else { newValue = this.value; } } else { newValue = !!newValue; if (newValue === this.value) { return; } if (this.object.checked !== newValue) { this.object.checked = newValue; } if (newValue && settings.dispatchEvents) { this.object.dispatchEvent(changeEvent()); } } break; case 'DOMCheckbox': if (this.isMultiChoice) { overwritePrevious = !checkIf.isBinding(newValue); newChoices = [].concat(newValue); for (index = k = 0, len1 = newChoices.length; k < len1; index = ++k) { value = newChoices[index]; newChoices[index] = checkIf.isBinding(value) ? value : this.choices[value]; } newValueArray = []; ref2 = this.choices; for (choiceName in ref2) { choiceBinding = ref2[choiceName]; if (overwritePrevious) { newChoiceValue = targetIncludes(newChoices, choiceBinding); } else { newChoiceValue = choiceBinding.value; } choiceBinding.setValue(newChoiceValue, publisher); if (newChoiceValue) { newValueArray.push(choiceName); } } newValue = newValueArray; } else { newValue = !!newValue; if (newValue === this.value) { return; } if (this.object.checked !== newValue) { this.object.checked = newValue; if (settings.dispatchEvents) { this.object.dispatchEvent(changeEvent()); } } } break; case 'DOMAttr': this.object.setAttribute(this.property, newValue); } } this.value = newValue; this.updateAllSubs(publisher); }, updateAllSubs: function(publisher) { var arr, i; if (i = (arr = this.subs).length) { while (i--) { this.updateSub(arr[i], publisher); } } }, updateSub: function(sub, publisher, isDelayedUpdate) { var currentTime, meta, newValue, subValue, timePassed, transform; if ((publisher === sub) || (publisher !== this && publisher.subsMeta[sub.ID])) { return; } meta = this.subsMeta[sub.ID]; if (meta.opts.throttle) { currentTime = +(new Date); timePassed = currentTime - meta.lastUpdate; if (timePassed < meta.opts.throttle) { clearTimeout(meta.updateTimer); return meta.updateTimer = setTimeout(((function(_this) { return function() { return _this.updateSub(sub, publisher); }; })(this)), meta.opts.throttle - timePassed); } else { meta.lastUpdate = currentTime; } } else if (meta.opts.delay && !isDelayedUpdate) { return setTimeout(((function(_this) { return function() { return _this.updateSub(sub, publisher, true); }; })(this)), meta.opts.delay); } newValue = this.type === 'Array' && meta.opts.sendArrayCopies ? this.value.slice() : this.value; subValue = sub[meta.valueRef]; newValue = (transform = meta.transformFn) ? transform(newValue, subValue, sub.object) : newValue; if (newValue === subValue && !meta.opts.updateEvenIfSame || meta.conditionFn && !meta.conditionFn(newValue, subValue, sub.object)) { return; } if (meta.opts.promiseTransforms && newValue && checkIf.isFunction(newValue.then)) { newValue.then(function(newValue) { sub.setValue(newValue, publisher); }); } else { sub.setValue(newValue, publisher); } if (meta.updateOnce) { this.removeSub(sub); } }, addModifierFn: function(target, subInterfaces, subjectFn, updateOnBind) { var j, len, subInterface, subMetaData, subscriber; if (!checkIf.isFunction(subjectFn)) { return throwWarning('fnOnly', 2); } else { for (j = 0, len = subInterfaces.length; j < len; j++) { subInterface = subInterfaces[j]; subscriber = subInterface._ || subInterface; if (subscriber.isMulti) { this.addModifierFn(target, subscriber.bindings, subjectFn, updateOnBind); } else { subMetaData = this.subsMeta[subscriber.ID]; subMetaData[target] = subjectFn; updateOnBind = updateOnBind && !subMetaData.updateOnce; if (this.pubsMap[subscriber.ID]) { subscriber.subsMeta[this.ID][target] = subjectFn; } if ((updateOnBind || this.type === 'Func') && target === 'transformFn') { this.updateSub(subscriber, this); } } } return true; } }, setSelfTransform: function(transformFn, updateOnBind) { this.selfTransform = transformFn; if (updateOnBind) { this.setValue(this.value); } }, scanForPholders: function() { var index; if (!this.pholderValues) { this.pholderValues = genObj(); this.pholderIndexMap = genObj(); this.pholderContexts = []; if (checkIf.isString(this.value)) { this.pholderContexts = this.value.split(pholderRegExSplit); index = 0; this.value = this.value.replace(pholderRegEx, (function(_this) { return function(e, pholder) { _this.pholderIndexMap[index++] = pholder; return _this.pholderValues[pholder] = pholder; }; })(this)); } if (this.isDom && this.property === textContent) { scanTextNodesPlaceholders(this.object, this.textNodes = genObj()); } } }, addPollInterval: function(time) { if (this.type !== 'Event') { this.removePollInterval(); return this.pollInterval = setInterval((function(_this) { return function() { var polledValue; polledValue = _this.fetchDirectValue(); return _this.setValue(polledValue); }; })(this), time); } }, removePollInterval: function() { clearInterval(this.pollInterval); return this.pollInterval = null; }, addUpdateListener: function(eventName, targetProperty) { this.object.addEventListener(eventName, (function(_this) { return function(event) { var shouldRedefineValue; if (!event._sb) { shouldRedefineValue = _this.selfTransform && _this.isDomInput; _this.setValue(_this.object[targetProperty], null, !shouldRedefineValue, true); } }; })(this), false); }, attachEvents: function() { if (this.eventName) { this.registerEvent(this.eventName, this.customEventMethod.listen); } else if (this.isDomInput) { this.addUpdateListener('input', 'value'); this.addUpdateListener('change', 'value'); } else if (!this.isMultiChoice && (this.type === 'DOMRadio' || this.type === 'DOMCheckbox')) { this.addUpdateListener('change', 'checked'); } }, registerEvent: function(eventName, customListenMethod) { var attachmentMethod, defaultInMethod; defaultInMethod = 'addEventListener'; this.attachedEvents.push(eventName); attachmentMethod = customListenMethod || defaultInMethod; this.invokeEventMethod(eventName, attachmentMethod, defaultInMethod); }, unRegisterEvent: function(eventName, customEmitMethod) { var defaultRemoveMethod, removalMethod; defaultRemoveMethod = 'removeEventListener'; this.attachedEvents.splice(this.attachedEvents.indexOf(eventName), 1); removalMethod = customEmitMethod || defaultRemoveMethod; this.invokeEventMethod(eventName, removalMethod, defaultRemoveMethod); }, invokeEventMethod: function(eventName, eventMethod, backupMethod) { var subject; subject = this.object; if (this.isDom && checkIf.isDefined(jQuery) && eventMethod === 'on' || eventMethod === 'off') { subject = jQuery(this.object); } if (!subject[eventMethod]) { eventMethod = backupMethod; } if (!this.eventHandler) { this.eventHandler = eventUpdateHandler.bind(this); } if (typeof subject[eventMethod] === "function") { subject[eventMethod](eventName, this.eventHandler); } }, emitEvent: function(extraData) { var defaultOutMethod, emitMethod, subject; subject = this.object; defaultOutMethod = 'dispatchEvent'; emitMethod = this.customEventMethod.emit || defaultOutMethod; if (this.isDom && checkIf.isDefined(jQuery) && emitMethod === 'trigger') { subject = jQuery(this.object); } if (!subject[emitMethod]) { emitMethod = defaultOutMethod; } if (emitMethod === defaultOutMethod) { if (!this.eventObject) { this.eventObject = document.createEvent('Event'); this.eventObject.initEvent(this.eventName, true, true); } this.eventObject.bindingData = extraData; return subject[emitMethod](this.eventObject); } subject[emitMethod](this.eventName, extraData); } }; eventUpdateHandler = function() { if (!this.isEmitter) { this.setValue(arguments[this.property], null, true); } }; /** * Stage definitions: * * 0: Selection: Got selector, awaiting object. * 1: Indication: Got object, awaiting proxied property / function / Binding-object. * 2: Binding Complete: Complete, awaiting additional (optional) bindings/mutations. */ BindingInterface = function(options, inheritedState) { var key; if (inheritedState) { extendState(this, inheritedState); this.stage = 1; } else { this.stage = 0; this.subs = []; this.optionsPassed = options || (options = {}); this.options = {}; for (key in defaultOptions) { this.options[key] = options[key] != null ? options[key] : defaultOptions[key]; } } return this; }; BindingInterfacePrivate = { selfClone: function() { return new BindingInterface(null, this); }, defineMainProps: function(binding) { this._ = binding; return Object.defineProperties(this, { 'value': { get: function() { return binding.value; } }, 'original': { get: function() { return binding.objects || binding.object; } }, 'subscribers': { get: function() { return binding.subs.slice().map(function(sub) { return sub.object; }); } } }); }, createBinding: function(subject, newObjectType, bindingInterface, isFunction) { var cachedBinding, newBinding; this.object = subject; cachedBinding = cache.get(subject, isFunction, this.selector, this.isMultiChoice); if (cachedBinding) { return this.patchCachedBinding(cachedBinding); } else { newBinding = new Binding(subject, newObjectType, bindingInterface); cache.set(newBinding, isFunction); return newBinding; } }, patchCachedBinding: function(cachedBinding) { var key, option, ref, ref1, value; if (cachedBinding.type === 'ObjectProp' && !(this.property in this.object)) { convertToLive(cachedBinding, this.object); } if (this.saveOptions) { ref = this.optionsPassed; for (option in ref) { value = ref[option]; cachedBinding.optionsDefault[option] = value; } } ref1 = cachedBinding.optionsDefault; for (key in ref1) { value = ref1[key]; this.options[key] = checkIf.isDefined(this.optionsPassed[key]) ? this.optionsPassed[key] : value; } return cachedBinding; }, setProperty: function(subject) { var split; if (checkIf.isNumber(subject)) { subject = subject.toString(); } this.selector = this.property = subject; if (!this.options.simpleSelector) { if (targetIncludes(subject, ':')) { split = subject.split(':'); this.descriptor = split.slice(0, -1).join(':'); this.property = split[split.length - 1]; } if (targetIncludes(subject, '.')) { split = this.property.split('.'); this.property = split[0]; this.pholder = split.slice(1).join('.'); } if (targetIncludes(this.descriptor, 'event')) { if (targetIncludes(subject, '#')) { split = this.property.split('#'); this.eventName = split[0]; this.property = split[1]; } else { this.eventName = this.property; this.property = 0; } if (isNaN(parseInt(this.property))) { throwWarning('badEventArg', 1); } this.customEventMethod = { listen: this.optionsPassed.listenMethod, emit: this.optionsPassed.emitMethod }; } } return this; }, setObject: function(subject, isFunction) { var isDomCheckbox, isDomRadio, isIterable, newObjectType, sampleItem; this.stage = 1; isIterable = subject !== window && checkIf.isIterable(subject) && !subject.nodeType; sampleItem = isIterable ? subject[0] : subject; if (!sampleItem) { if (isIterable && checkIf.isElCollection(subject)) { throwError('emptyList'); } } else if (this.isDom = checkIf.isDom(sampleItem)) { if (this.property === 'checked') { isDomRadio = sampleItem && checkIf.isDomRadio(sampleItem); isDomCheckbox = !isDomRadio && sampleItem && checkIf.isDomCheckbox(sampleItem); } else if (this.property === 'value') { this.isDomInput = checkIf.isDomInput(sampleItem); } if (isIterable && !targetIncludes(this.descriptor, 'multi')) { if (subject.length === 1) { subject = subject[0]; } else { if ((isDomRadio || isDomCheckbox) && !checkIf.domElsAreSame(subject)) { return throwWarning('mixedElList', 3); } else { if (isDomRadio || isDomCheckbox) { this.isMultiChoice = true; subject = [].slice.call(subject); } else { subject = subject[0]; throwWarning('onlyOneDOMElement', 3); } } } } } switch (false) { case !isFunction: newObjectType = 'Func'; break; case !this.pholder: newObjectType = 'Pholder'; break; case !targetIncludes(this.descriptor, 'array'): newObjectType = 'Array'; break; case !targetIncludes(this.descriptor, 'event'): newObjectType = 'Event'; break; case !targetIncludes(this.descriptor, 'func'): newObjectType = 'Proxy'; break; case !isDomRadio: newObjectType = 'DOMRadio'; break; case !isDomCheckbox: newObjectType = 'DOMCheckbox'; break; case !targetIncludes(this.descriptor, 'attr'): newObjectType = 'DOMAttr'; break; default: newObjectType = 'ObjectProp'; } if (targetIncludes(this.descriptor, 'multi')) { if (!subject.length) { throwError('emptyList'); } this.defineMainProps(new GroupBinding(this, subject, newObjectType)); } else { this.defineMainProps(this.createBinding(subject, newObjectType, this, isFunction)); } if (targetIncludes(this._.type, 'Event') || targetIncludes(this._.type, 'Proxy')) { this.options.updateOnBind = false; } else if (targetIncludes(this._.type, 'Func')) { this.options.updateOnBind = true; } if (this.completeCallback) { return this.completeCallback(this); } else { return this; } }, addToPublisher: function(publisherInterface) { var alreadyHadSub, binding, j, len, ref; publisherInterface.stage = 2; publisherInterface.subs.push(this); alreadyHadSub = publisherInterface._.addSub(this._, publisherInterface.options, publisherInterface.updateOnce); if (publisherInterface.updateOnce) { delete publisherInterface.updateOnce; } else if (publisherInterface.options.updateOnBind && !alreadyHadSub) { if (this._.isMulti) { ref = this._.bindings; for (j = 0, len = ref.length; j < len; j++) { binding = ref[j]; publisherInterface._.updateSub(binding, publisherInterface._); } } else { publisherInterface._.updateSub(this._, publisherInterface._); } } } }; BindingInterface.prototype = Object.create(BindingInterfacePrivate, { of: { get: function() { if (!this.stage) { return METHOD_of; } } }, set: { get: function() { if (this.stage) { return METHOD_set; } } }, chainTo: { get: function() { if (this.stage === 2) { return METHOD_chainTo; } } }, transformSelf: { get: function() { if (this.stage === 1) { return METHOD_transformSelf; } } }, transform: { get: function() { if (this.stage === 2) { return METHOD_transform; } } }, transformAll: { get: function() { if (this.stage === 2) { return METHOD_transformAll; } } }, condition: { get: function() { if (this.stage === 2) { return METHOD_condition; } } }, conditionAll: { get: function() { if (this.stage === 2) { return METHOD_conditionAll; } } }, bothWays: { get: function() { if (this.stage === 2) { return METHOD_bothWays; } } }, unBind: { get: function() { if (this.stage === 2) { return METHOD_unBind; } } }, pollEvery: { get: function() { if (this.stage) { return METHOD_pollEvery; } } }, stopPolling: { get: function() { if (this.stage) { return METHOD_stopPolling; } } }, setOption: { get: function() { if (this.stage === 2) { return METHOD_setOption; } } }, updateOn: { get: function() { var thisInterface; if (this.stage && (thisInterface = this)) { return genProxiedInterface(false, function(subInterface) { if (subInterface._ !== thisInterface._) { thisInterface._.pubsMap[subInterface._.ID] = subInterface._; subInterface._.addSub(genSelfUpdater(thisInterface._, true), subInterface.options, false, true); } return thisInterface; }); } } }, removeUpdater: { get: function() { var selfUpdater, thisInterface; if (this.stage && (thisInterface = this) && (selfUpdater = this._.selfUpdater)) { return genProxiedInterface(false, function(subInterface) { if (subInterface._.subsMeta[selfUpdater.ID]) { delete thisInterface._.pubsMap[subInterface._.ID]; subInterface._.removeSub(selfUpdater); } }); } } }, to: { get: function() { var thisInterface; if (this.stage === 1 && (thisInterface = this)) { return genProxiedInterface(true, function(subInterface) { if (subInterface._ !== thisInterface._) { subInterface.addToPublisher(thisInterface); } return thisInterface; }); } } }, and: { get: function() { var cloneBinding, cloneInterface; cloneInterface = this.selfClone(); if (this.stage === 2) { return cloneInterface; } else if (this.stage === 1) { if (!cloneInterface._.isMulti) { cloneBinding = cloneInterface._; cloneInterface._ = cloneInterface._ = new GroupBinding(cloneInterface); cloneInterface._.addBinding(cloneBinding); } return genProxiedInterface(false, function(siblingInterface) { cloneInterface._.addBinding(siblingInterface._); return cloneInterface; }); } } }, once: { get: function() { var interfaceToReturn; if (this.stage === 1) { interfaceToReturn = this.selfClone(); interfaceToReturn.updateOnce = true; return interfaceToReturn; } } }, update: { get: function() { return this.set; } }, twoWay: { get: function() { return this.bothWays; } }, pipe: { get: function() { return this.chainTo; } } }); METHOD_of = function(object) { if (!(checkIf.isObject(object) || checkIf.isFunction(object))) { throwErrorBadArg(object); } if (checkIf.isBindingInterface(object)) { object = object.object; } this.stage = 1; return this.setObject(object); }; METHOD_chainTo = function(subject, specificOptions, saveOptions) { return SimplyBind(this.subs[this.subs.length - 1]).to(subject, specificOptions, saveOptions); }; METHOD_set = function(newValue) { this._.setValue(newValue); return this; }; METHOD_transformSelf = function(transformFn) { if (!checkIf.isFunction(transformFn)) { throwWarning('fnOnly', 1); } else { this._.setSelfTransform(transformFn, this.options.updateOnBind); } return this; }; METHOD_transform = function(transformFn) { this._.addModifierFn('transformFn', this.subs.slice(-1), transformFn, this.options.updateOnBind); return this; }; METHOD_transformAll = function(transformFn) { this._.addModifierFn('transformFn', this.subs, transformFn, this.options.updateOnBind); return this; }; METHOD_condition = function(conditionFn) { this._.addModifierFn('conditionFn', this.