@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
JavaScript
// 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.