@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
1,363 lines (1,359 loc) • 48.6 kB
JavaScript
// Generated by CoffeeScript 1.10.0
(function() {
var Binding, BindingInterface, BindingInterfacePrivate, BindingInterfacePublic, BindingMulti, SimplyBind, addToNodeStore, applyPlaceholders, arrayIncludes, arrayMutatorMethods, boundInstances, cache, changeEvent, checkIf, computeProxied, dummyPropertyDescriptor, errors, escapeRegEx, extendState, genID, genObj, getErrSource, globalOptions, handleUpdateFromEvent, i, len, maybeUpdateDep, methodName, methodNames, pholderRegEx, pholderRegExSplit, proto, scanTextNodesPlaceholders, setOptionsForBinding, setPholderRegEx, throwError, throwErrorBadArg, throwErrorUnavail, throwWarning;
changeEvent = function() {
var event;
event = document.createEvent('Event');
event.initEvent('change', true, false);
event._sb = true;
return event;
};
arrayMutatorMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'];
dummyPropertyDescriptor = {};
genID = function() {
return 'sb_' + (Math.floor((1 + Math.random()) * 1000000000000).toString(16));
};
genObj = function() {
return Object.create(null);
};
arrayIncludes = function(arr, item) {
return arr.indexOf(item) !== -1;
};
checkIf = {
isDefined: function(subject) {
return subject !== void 0;
},
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.toUpperCase();
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;
}
};
setOptionsForBinding = function(binding, newOptions) {
var option, value;
for (option in newOptions) {
value = newOptions[option];
if (checkIf.isDefined(globalOptions[option])) {
binding.options[option] = value;
}
}
binding.makePropertyLive();
};
extendState = function(base, stateToInherit) {
var i, key, len, stateMapping;
stateMapping = Object.keys(stateToInherit);
for (i = 0, len = stateMapping.length; i < len; i++) {
key = stateMapping[i];
base[key] = stateToInherit[key];
}
};
cache = {
get: function(object, isSimpleObject, selector, isMultiChoice) {
var sampleItem;
if (isSimpleObject) {
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, isSimpleObject) {
var propsMap, selector;
if (isSimpleObject) {
Object.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;
Object.defineProperty(B.object, '_sb_map', {
'configurable': true,
'value': propsMap
});
}
}
}
};
escapeRegEx = /[.*+?^${}()|[\]\\]/g;
pholderRegEx = pholderRegExSplit = null;
setPholderRegEx = function() {
var end, middle, start;
start = globalOptions.placeholder[0].replace(escapeRegEx, '\\$&');
end = globalOptions.placeholder[1].replace(escapeRegEx, '\\$&');
middle = "[^" + end + "]+";
pholderRegEx = new RegExp(start + "(" + middle + ")" + end, 'g');
pholderRegExSplit = new RegExp("" + start + middle + end, 'g');
};
applyPlaceholders = function(contexts, values, indexMap) {
var contextPart, i, index, len, output;
output = '';
for (index = i = 0, len = contexts.length; i < len; index = ++i) {
contextPart = contexts[index];
output += contextPart;
if (indexMap[index]) {
output += values[indexMap[index]];
}
}
return output;
};
addToNodeStore = function(nodeStore, node, targetPlaceholder) {
if (nodeStore[targetPlaceholder] == null) {
nodeStore[targetPlaceholder] = [];
}
nodeStore[targetPlaceholder].push(node);
};
scanTextNodesPlaceholders = function(element, nodeStore) {
var i, index, j, len, len1, newFragment, newNode, node, ref, textContent, textPiece, textPieces;
ref = element.childNodes;
for (i = 0, len = ref.length; i < len; i++) {
node = ref[i];
if (node.nodeType !== 3) {
scanTextNodesPlaceholders(node, nodeStore);
} else if (node.textContent.match(pholderRegExSplit)) {
textContent = node.textContent;
textPieces = textContent.split(pholderRegEx);
if (textPieces.length === 3 && textPieces[0] + textPieces[2] === '') {
addToNodeStore(nodeStore, node, textPieces[1]);
} else {
newFragment = document.createDocumentFragment();
for (index = j = 0, len1 = textPieces.length; j < len1; index = ++j) {
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 (!globalOptions.silent) {
errSource = getErrSource(depth);
warn = errors[warningName];
warn += "\n\n" + errSource;
console.warn('SimplyBind: ' + warn);
}
};
throwErrorUnavail = function(methodName) {
throwError("You can't use/invoke ." + methodName + "() at this stage", true);
};
throwErrorBadArg = function(methodName, arg) {
throwError("Invalid argument/s (" + arg + ") passed to ." + methodName + "()", 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()",
onlyOneDOMElement: "You can only pass a single DOM element to a binding",
mixedElList: "'checked' of Mixed list of element cannot be bound",
emptyElList: "Empty element lists cannot be bound"
};
boundInstances = {};
globalOptions = {
silent: false,
liveProps: true,
dispatchEvents: false,
updateEvenIfSame: false,
updateOnBind: true,
mutateInherited: false,
trackArrayChildren: false,
simpleSelector: false,
promiseTransforms: false,
placeholder: ['{{', '}}']
};
setPholderRegEx();
SimplyBind = function(subject, options, isProxiedFunc) {
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)) {
return new BindingInterface(subject._, 1);
} else {
return new BindingInterface(null, 0, null, subject, isProxiedFunc, options);
}
};
Binding = function(object, type, state, isProxiedFunc) {
var arrayBinding, subjectValue;
extendState(this, state);
this.type = type;
this.object = object;
this.ID = genID();
this.deps = [];
this.depsMap = {
1: genObj(),
2: genObj()
};
this.depsPholders = genObj();
this.myPholders = genObj();
this.transforms = genObj();
this.conditions = genObj();
this.attachedEvents = [];
/* ========================================================================== */
if (this.type === 'Event' || this.type === 'Func') {
this.options.updateOnBind = false;
this.options.updateEvenIfSame = true;
}
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.addDep(_this).addTransform(_this.ID, function() {
return choiceBinding;
});
choiceBinding.groupBinding = _this;
};
})(this));
}
if (!(this.type === 'Event' || isProxiedFunc)) {
this.value = this.valueOriginal = subjectValue = this.fetchDirectValue();
if (this.type === 'ObjectProp' && !checkIf.isDefined(subjectValue)) {
this.object[this.property] = subjectValue;
}
if (this.placeholder && !this.pholderValues) {
this.scanForPholders();
}
this.makePropertyLive();
}
this.attachEvents();
if (this.object instanceof Array && this.type !== 'Array') {
this.arrayBinding = arrayBinding = cache.get(this.object, true);
if (arrayBinding && arrayBinding.options.trackArrayChildren && !arrayIncludes(arrayBinding.trackedChildren, this.property)) {
arrayBinding.trackedChildren.push(this.property);
SimplyBind(this.property).of(this.object).to(arrayBinding.updateSelf);
}
}
return boundInstances[this.ID] = this;
};
/**
* Stage definitions:
*
* 0: Selection: Got selector, awaiting object.
* 1: Indication: Got object, awaiting proxied property / function / Binding-object.
* 2: Binding Selection: Got proxied selector, awaiting proxied object.
* 3: Binding Complete: Complete, awaiting additional (optional) bindings/mutations.
*/
BindingInterface = function(binding, stage, inheritedState, subject, isProxiedFunc, options) {
var isDomCheckbox, isDomInput, isDomRadio, isIterable, key, newObjectType, sampleItem, split;
if (inheritedState) {
extendState(this, inheritedState);
}
this.stage = stage || 0;
if (this.proxies == null) {
this.proxies = [];
}
if (this.state == null) {
this.state = {};
}
switch (this.stage) {
case 0:
this.optionsPassed = options || (options = {});
this.options = {};
for (key in globalOptions) {
this.options[key] = options[key] != null ? options[key] : globalOptions[key];
}
if (checkIf.isFunction(subject)) {
this.stage = 1;
binding = this.createBinding(subject, 'Func', true, isProxiedFunc);
} else if (subject instanceof Array) {
this.stage = 1;
binding = this.createBinding(subject, 'Array', true);
} else {
if (checkIf.isNumber(subject)) {
subject = subject.toString();
}
this.selector = this.property = subject;
if (!this.options.simpleSelector) {
if (arrayIncludes(this.selector, ':')) {
split = this.property.split(':');
this.descriptor = split[0];
this.property = split[1];
}
if (arrayIncludes(this.selector, '.')) {
split = this.property.split('.');
this.property = split[0];
this.placeholder = split.slice(1).join('.');
}
this.selector = this.property;
}
}
break;
case 1:
if (!binding) {
isIterable = subject !== window && checkIf.isIterable(subject);
sampleItem = isIterable ? subject[0] : subject;
if (!sampleItem) {
if (isIterable && checkIf.isElCollection(subject)) {
throwWarning('emptyElList', 3);
}
} 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') {
isDomInput = checkIf.isDomInput(sampleItem);
}
if (isIterable && 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);
}
}
}
}
}
newObjectType = (function() {
switch (false) {
case !this.state.hasEventName:
return 'Event';
case !isDomInput:
return 'DOMValue';
case !isDomRadio:
return 'DOMRadio';
case !isDomCheckbox:
return 'DOMCheckbox';
case this.descriptor !== 'attr':
return 'DOMAttr';
default:
return 'ObjectProp';
}
}).call(this);
if (this.descriptor === 'multi') {
binding = new BindingMulti(subject, newObjectType, this);
} else {
binding = this.createBinding(subject, newObjectType);
}
}
}
return this.defineMainProps(binding);
};
Binding.prototype = {
makePropertyLive: function(force) {
var _, opts, propertyDescriptor, shouldWriteLiveProp;
if (this.options.liveProps) {
_ = this;
if (this.type === 'ObjectProp') {
propertyDescriptor = Object.getOwnPropertyDescriptor(this.object, this.property) || dummyPropertyDescriptor;
shouldWriteLiveProp = force || !this.isLiveProp && (propertyDescriptor.configurable || this.options.mutateInherited);
shouldWriteLiveProp = shouldWriteLiveProp && this.object.constructor !== CSSStyleDeclaration;
if (shouldWriteLiveProp) {
this.isLiveProp = true;
Object.defineProperty(this.object, this.property, {
configurable: true,
enumerable: propertyDescriptor.enumerable,
get: function() {
return _.value;
},
set: propertyDescriptor.set ? function(newValue) {
propertyDescriptor.set(newValue);
_.setValue(newValue);
} : function(newValue) {
_.setValue(newValue);
}
});
}
} else if (this.type === 'Array') {
if (!this.isLiveProp) {
this.isLiveProp = true;
arrayMutatorMethods.forEach(function(method) {
return Object.defineProperty(_.value, method, {
configurable: true,
value: function() {
var result;
result = Array.prototype[method].apply(_.value, arguments);
_.updateAllDeps(_);
return result;
}
});
});
}
if (this.options.trackArrayChildren && !this.trackedChildren) {
this.trackedChildren = [];
this.updateSelf = function() {
return _.updateAllDeps(_);
};
opts = {
updateOnBind: false
};
this.value.forEach(function(item, index) {
_.trackedChildren.push('' + index);
return SimplyBind(index, opts).of(_.value).to(_.updateSelf);
});
}
}
}
},
addDep: function(dep, bothWays) {
var depItem, i, len, ref;
if (dep.isMulti) {
ref = dep.bindings;
for (i = 0, len = ref.length; i < len; i++) {
depItem = ref[i];
this.addDep(depItem);
}
} else {
if (!this.depsMap[1][dep.ID]) {
this.depsMap[1][dep.ID] = dep;
this.deps.push(dep);
}
if (this.placeholder) {
this.myPholders[dep.ID] = this.placeholder;
} else if (this.myPholders[dep.ID]) {
delete this.myPholders[dep.ID];
}
if (dep.placeholder) {
this.depsPholders[dep.ID] = dep.placeholder;
}
if (bothWays) {
this.depsMap[2][dep.ID] = dep;
} else if (dep.depsMap[1][this.ID]) {
dep.addDep(this, true);
this.addDep(dep, true);
}
}
return this;
},
removeDep: function(dep, bothWays) {
var depItem, i, len, ref;
if (dep.isMulti) {
ref = dep.bindings;
for (i = 0, len = ref.length; i < len; i++) {
depItem = ref[i];
this.removeDep(depItem, bothWays);
}
} else {
if (this.depsMap[1][dep.ID]) {
this.deps.splice(this.deps.indexOf(dep), 1);
delete this.depsMap[1][dep.ID];
delete this.depsPholders[dep.ID];
}
if (bothWays) {
dep.removeDep(this);
delete this.depsMap[2][dep.ID];
}
}
},
removeAllDeps: function(bothWays) {
var dep, i, len, ref;
ref = this.deps.slice();
for (i = 0, len = ref.length; i < len; i++) {
dep = ref[i];
this.removeDep(dep, bothWays);
}
if (bothWays || Object.keys(this.depsMap[2]).length === 0) {
this.destroy();
}
},
destroy: function() {
var event, i, j, len, len1, method, ref;
delete boundInstances[this.ID];
if (this.type === 'ObjectProp') {
Object.defineProperty(this.object, this.property, {
'value': this.value,
'writable': true
});
delete this.object._sb_map;
delete this.object._sb_ID;
} else if (this.type === 'Event') {
ref = this.attachedEvents;
for (i = 0, len = ref.length; i < len; i++) {
event = ref[i];
this.unRegisterEvent(event, this.customEventMethod.remove);
}
delete this.object._sb_map;
} else if (this.type === 'Array') {
delete this.object._sb_ID;
for (j = 0, len1 = arrayMutatorMethods.length; j < len1; j++) {
method = arrayMutatorMethods[j];
delete this.object[method];
}
} else if (this.type === 'Func') {
delete this.object._sb_ID;
}
},
fetchDirectValue: function() {
var choiceEl, choiceName, ref, results, type;
type = this.type;
switch (false) {
case type !== 'Func':
return this.object();
case type !== 'Array':
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, specificPlaceholder, updater, fromSelf) {
var choiceBinding, choiceName, i, index, isNewValue, j, len, len1, n, newChoiceValue, newChoices, newValueArray, overwritePrevious, prevValue, ref, ref1, targetChoiceBinding, textNode, textNodes, value;
if (updater == null) {
updater = this;
}
prevValue = specificPlaceholder ? this.pholderValues[specificPlaceholder] : this.value;
if (this.selfTransform) {
newValue = this.selfTransform(newValue);
}
isNewValue = newValue !== prevValue || this.options.updateEvenIfSame;
if (isNewValue && this.type !== 'Array') {
if (specificPlaceholder) {
this.pholderValues[specificPlaceholder] = newValue;
if (this.textNodes && (textNodes = this.textNodes[specificPlaceholder])) {
for (i = 0, len = textNodes.length; i < len; i++) {
textNode = textNodes[i];
textNode[this.property] = newValue;
}
}
newValue = applyPlaceholders(this.pholderContexts, this.pholderValues, this.pholderIndexMap);
}
switch (this.type) {
case 'ObjectProp':
if (!(this.isLiveProp || specificPlaceholder && this.isDom && this.property === 'textContent')) {
this.object[this.property] = newValue;
}
break;
case 'Func':
prevValue = this.valuePassed;
if (updater.type === 'Array' && newValue === updater.value) {
newValue = newValue.slice();
}
this.valuePassed = newValue;
newValue = this.object(newValue, prevValue);
break;
case 'Event':
if (!fromSelf) {
this.isEmitter = true;
this.emitEvent(newValue);
this.isEmitter = false;
}
break;
case 'DOMValue':
if (this.object.value !== newValue) {
this.object.value = newValue;
this.emitChangeEvent();
}
break;
case 'DOMRadio':
if (this.isMultiChoice) {
targetChoiceBinding = checkIf.isBinding(newValue) ? newValue : this.choices[newValue];
if (targetChoiceBinding) {
newValue = targetChoiceBinding.object.value;
ref = this.choices;
for (n in ref) {
choiceBinding = ref[n];
choiceBinding.setValue(choiceBinding.ID === targetChoiceBinding.ID, null, updater);
}
} else {
newValue = prevValue;
}
} else {
newValue = !!newValue;
if (this.object.checked !== newValue) {
this.object.checked = newValue;
}
if (newValue) {
this.emitChangeEvent();
}
}
break;
case 'DOMCheckbox':
if (this.isMultiChoice) {
overwritePrevious = !checkIf.isBinding(newValue);
newChoices = [].concat(newValue);
for (index = j = 0, len1 = newChoices.length; j < len1; index = ++j) {
value = newChoices[index];
newChoices[index] = checkIf.isBinding(value) ? value : this.choices[value];
}
newValueArray = [];
ref1 = this.choices;
for (choiceName in ref1) {
choiceBinding = ref1[choiceName];
if (overwritePrevious) {
newChoiceValue = arrayIncludes(newChoices, choiceBinding);
} else {
newChoiceValue = choiceBinding.value;
}
choiceBinding.setValue(newChoiceValue, null, updater);
if (newChoiceValue) {
newValueArray.push(choiceName);
}
}
newValue = newValueArray;
} else {
newValue = !!newValue;
if (this.object.checked !== newValue) {
this.object.checked = newValue;
this.emitChangeEvent();
}
}
break;
case 'DOMAttr':
this.object.setAttribute(this.property, newValue);
}
this.value = newValue;
this.updateAllDeps(updater);
}
},
updateAllDeps: function(updater) {
var currentTime, dep, i, len, ref, timePassed;
if (this.deps.length) {
if (this.throttleRate) {
currentTime = +(new Date);
timePassed = currentTime - this.lastUpdate;
if (timePassed < this.throttleRate) {
clearTimeout(this.throttleTimeout);
return this.throttleTimeout = setTimeout(((function(_this) {
return function() {
return _this.updateAllDeps(updater);
};
})(this)), this.throttleRate - timePassed);
} else {
this.lastUpdate = currentTime;
}
}
ref = this.deps;
for (i = 0, len = ref.length; i < len; i++) {
dep = ref[i];
this.updateDep(dep, updater);
}
}
},
updateDep: function(dep, updater) {
var currentValue, depPlaceholder, depValue, myPlaceholder, newValue;
if ((updater === dep) || (updater !== this && updater.depsMap[1][dep.ID])) {
return;
}
myPlaceholder = this.myPholders[dep.ID];
depPlaceholder = this.depsPholders[dep.ID];
currentValue = myPlaceholder ? this.pholderValues[myPlaceholder] : this.value;
depValue = depPlaceholder ? dep.pholderValues[depPlaceholder] : dep.value;
newValue = !this.hasTransforms ? currentValue : this.applyTransform(dep, depPlaceholder, currentValue, depValue);
if (this.hasConditions && !this.checkCondition(dep, depPlaceholder, currentValue, depValue)) {
return;
}
if (this.options.promiseTransforms && newValue && checkIf.isFunction(newValue.then)) {
newValue.then(function(newValue) {
dep.setValue(newValue, depPlaceholder, updater);
});
} else {
dep.setValue(newValue, depPlaceholder, updater);
}
},
processTransform: function(transformFn, subjects) {
var i, len, prox;
if (!checkIf.isFunction(transformFn)) {
return throwWarning('fnOnly', 2);
} else {
for (i = 0, len = subjects.length; i < len; i++) {
prox = subjects[i];
prox = prox._ || prox;
if (prox.isMulti) {
this.processTransform(transformFn, prox.bindings);
} else {
this.addTransform(prox.ID, transformFn);
if (this.depsMap[2][prox.ID]) {
prox.addTransform(this.ID, transformFn);
}
if (this.options.updateOnBind || this.type === 'Func') {
this.updateDep(prox, this);
}
}
}
return true;
}
},
applyTransform: function(dep, placeholder, value, depValue) {
if (this.transforms[dep.ID]) {
return this.transforms[dep.ID](value, depValue);
} else {
return value;
}
},
addTransform: function(ID, transformFn) {
this.hasTransforms = true;
this.transforms[ID] = transformFn;
},
processCondition: function(conditionFn, subjects) {
var i, len, prox;
if (!checkIf.isFunction(conditionFn)) {
return throwWarning('fnOnly', 2);
} else {
for (i = 0, len = subjects.length; i < len; i++) {
prox = subjects[i];
prox = prox._ || prox;
if (prox.isMulti) {
this.processCondition(conditionFn, prox.bindings);
} else {
this.addCondition(prox.ID, conditionFn);
if (this.depsMap[2][prox.ID]) {
prox.addCondition(this.ID, conditionFn);
}
}
}
return true;
}
},
checkCondition: function(dep, placeholder, value, depValue) {
if (this.conditions[dep.ID]) {
return this.conditions[dep.ID](value, depValue);
} else {
return true;
}
},
addCondition: function(ID, conditionFn) {
this.hasConditions = true;
this.conditions[ID] = conditionFn;
},
scanForPholders: function() {
var index;
this.pholderValues = genObj();
this.pholderIndexMap = genObj();
this.pholderContexts = [];
if (checkIf.isString(this.valueOriginal)) {
this.pholderContexts = this.valueOriginal.split(pholderRegExSplit);
index = 0;
this.value = this.valueOriginal.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) {
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) {
if (!event._sb) {
_this.setValue(event.target[targetProperty]);
}
};
})(this), false);
},
emitChangeEvent: function() {
if (this.options.dispatchEvents) {
this.object.dispatchEvent(changeEvent());
}
},
attachEvents: function() {
if (this.eventName) {
this.registerEvent(this.eventName, this.customEventMethod["in"]);
} else if (this.type === 'DOMValue') {
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, customInMethod) {
var attachmentMethod, defaultInMethod;
if (!arrayIncludes(this.attachedEvents, eventName)) {
defaultInMethod = 'addEventListener';
this.attachedEvents.push(eventName);
attachmentMethod = customInMethod || defaultInMethod;
this.invokeEventMethod(eventName, attachmentMethod, defaultInMethod);
}
},
unRegisterEvent: function(eventName, customMethod) {
var defaultRemoveMethod, indexOfEvent, removalMethod;
indexOfEvent = this.attachedEvents.indexOf(eventName);
if (indexOfEvent === -1) {
return;
}
defaultRemoveMethod = 'removeEventListener';
this.attachedEvents.splice(indexOfEvent, 1);
removalMethod = customMethod || 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 = handleUpdateFromEvent.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.out || 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);
}
};
handleUpdateFromEvent = function() {
var fetchedValue;
if (!this.isEmitter) {
fetchedValue = this.type === 'Event' ? arguments[this.property] : this.fetchDirectValue();
this.setValue(fetchedValue, null, null, true);
}
};
BindingInterfacePrivate = {
"new": function(stage, object) {
return new BindingInterface(this._, stage, this, object);
},
defineMainProps: function(binding) {
this._ = binding;
return Object.defineProperties(this, {
'ID': {
get: function() {
return binding.ID;
}
},
'value': {
get: function() {
return binding.value;
}
},
'original': {
get: function() {
return binding.objects || binding.object;
}
},
'dependents': {
get: function() {
return binding.deps.slice().map(function(dep) {
return dep.object;
});
}
},
'lastProxied': {
get: function() {
return this.proxies[this.proxies.length - 1];
}
}
});
},
createBinding: function(subject, newObjectType, isSimpleObject, isProxiedFunc, bindingInterface) {
var cachedBinding, newBinding;
this.object = subject;
cachedBinding = cache.get(subject, isSimpleObject, this.selector, this.isMultiChoice);
if (cachedBinding) {
return this.patchCachedBinding(cachedBinding);
} else {
newBinding = new Binding(subject, newObjectType, bindingInterface || this, isProxiedFunc);
cache.set(newBinding, isSimpleObject);
return newBinding;
}
},
patchCachedBinding: function(cachedBinding) {
cachedBinding.placeholder = this.placeholder;
if (this.placeholder && !cachedBinding.pholderValues) {
cachedBinding.valueOriginal = cachedBinding.fetchDirectValue();
cachedBinding.scanForPholders();
}
if (cachedBinding.type === 'ObjectProp' && !(this.property in this.object)) {
cachedBinding.makePropertyLive(true);
}
setOptionsForBinding(cachedBinding, this.optionsPassed);
return cachedBinding;
}
};
BindingInterface.prototype = Object.create(BindingInterfacePrivate);
BindingInterfacePublic = {
of: function(object) {
var proxied;
if (this.stage !== 0 && this.stage !== 2) {
throwErrorUnavail(methodNames[0]);
}
if (!(checkIf.isObject(object) || checkIf.isFunction(object))) {
throwErrorBadArg(methodNames[0], object);
}
if (checkIf.isBindingInterface(object)) {
object = object.object;
}
if (this.stage === 2) {
this.proxies[this.proxies.length - 1] = proxied = this.lastProxied.of(object);
this.state.hasInitialBinding = true;
this.state.hasTransform = false;
this._.addDep(proxied._);
maybeUpdateDep(this._, proxied._);
}
return this["new"](this.stage + 1, object);
},
ofEvent: function(eventName, customInMethod, customOutMethod) {
if (this.stage !== 0 || this.state.hasEventName) {
throwErrorUnavail(methodNames[1]);
} else if (!eventName || !checkIf.isString(eventName)) {
throwErrorBadArg(methodNames[1], eventName);
} else if (isNaN(parseInt(this.property))) {
throwWarning('badEventArg', 1);
}
this.state.hasEventName = true;
this.eventName = eventName;
this.selector = this.property + '#' + this.eventName;
this.customEventMethod = {
'in': customInMethod,
'out': customOutMethod
};
return this;
},
to: function(subject, specificOptions) {
var newStage, proxied;
if (this.stage !== 1 || this.state.hasInitialBinding) {
throwErrorUnavail(methodNames[2]);
}
this.proxies.push(proxied = computeProxied(this, subject, specificOptions));
if (proxied.stage === 0) {
newStage = 2;
} else {
newStage = 3;
this.state.hasInitialBinding = true;
}
return this["new"](newStage);
},
and: function(subject, specificOptions) {
var newStage, proxied;
if (this.stage !== 3 || !this.state.hasInitialBinding || this.state.hasMultiTransform) {
throwErrorUnavail(methodNames[3]);
}
this.proxies.push(proxied = computeProxied(this, subject, specificOptions));
if (proxied.stage === 0) {
newStage = 2;
} else {
newStage = 3;
this.state.hasTransform = false;
}
return this["new"](newStage);
},
toEvent: function(eventName, customOutMethod, customInMethod, specificOptions) {
if (this.stage !== 1) {
throwErrorUnavail(methodNames[4]);
}
this.proxies.push(SimplyBind(0, specificOptions).ofEvent(eventName, customInMethod, customOutMethod));
return this["new"](2);
},
chainTo: function(subject, specificOptions) {
if (this.stage !== 3) {
throwErrorUnavail(methodNames[5]);
}
return SimplyBind(this.lastProxied).to(subject, specificOptions);
},
set: function(newValue) {
if (this.stage === 0 || this.stage === 2) {
throwErrorUnavail(methodNames[6]);
}
this._.setValue(newValue, this.placeholder);
return this;
},
get: function() {
if (this.stage === 0 || this.stage === 2) {
throwErrorUnavail(methodNames[7]);
}
if (this.placeholder) {
return this._.pholderValues[this.placeholder];
} else {
return this._.value;
}
},
transformSelf: function(transformFn) {
var currentValue;
if (this.stage !== 1 || this.stage === 1 && this._.type === 'Array') {
throwErrorUnavail(methodNames[8]);
}
if (!checkIf.isFunction(transformFn)) {
throwWarning('fnOnly', 1);
} else {
this._.selfTransform = transformFn;
if (this._.options.updateOnBind) {
currentValue = this._.isMulti ? this.value[0] : this.value;
this._.setValue(currentValue);
}
}
return this;
},
transform: function(transformFn) {
if (this.stage !== 3 || this.state.hasTransform || this.state.hasMultiTransform) {
throwErrorUnavail(methodNames[9]);
}
this.state.hasTransform = this._.processTransform(transformFn, this.proxies.slice(-1)) || false;
return this["new"](3);
},
transformAll: function(transformFn) {
if (this.stage !== 3 || this.state.hasTransform || this.state.hasMultiTransform) {
throwErrorUnavail(methodNames[10]);
}
this.state.hasMultiTransform = this._.processTransform(transformFn, this.proxies) || false;
return this["new"](3);
},
condition: function(conditionFn) {
if (this.stage !== 3) {
throwErrorUnavail(methodNames[11]);
}
this._.processCondition(conditionFn, this.proxies.slice(-1));
return this["new"](3);
},
conditionAll: function(conditionFn) {
if (this.stage !== 3) {
throwErrorUnavail(methodNames[12]);
}
this._.processCondition(conditionFn, this.proxies);
return this["new"](3);
},
bothWays: function(dontOrAltTransform) {
var originCondition, originTransform, proxied, proxiedBinding, transformToUse;
if (this.stage !== 3 || this.state.hasMultiTransform) {
throwErrorUnavail(methodNames[13]);
}
proxied = this.lastProxied;
proxiedBinding = proxied._.addDep(this._, true);
originTransform = this._.transforms[proxied.ID];
originCondition = this._.conditions[proxied.ID];
if (originTransform || dontOrAltTransform) {
transformToUse = checkIf.isFunction(dontOrAltTransform) ? dontOrAltTransform : originTransform;
if (transformToUse && dontOrAltTransform !== false) {
proxiedBinding.addTransform(this.ID, transformToUse);
}
}
if (originCondition) {
proxiedBinding.addCondition(this.ID, originCondition);
}
this._.addDep(proxied._, true);
return this;
},
unBind: function(bothWays) {
var i, len, proxied, ref;
if (this.stage !== 3) {
throwErrorUnavail(methodNames[14]);
}
ref = this.proxies;
for (i = 0, len = ref.length; i < len; i++) {
proxied = ref[i];
this._.removeDep(proxied._, bothWays);
}
return this;
},
pollEvery: function(time) {
if (this.stage !== 3 || this._.type === 'Event') {
throwErrorUnavail(methodNames[15]);
}
this._.addPollInterval(time);
return this;
},
stopPolling: function() {
if (this.stage !== 3) {
throwErrorUnavail(methodNames[16]);
}
this._.removePollInterval();
return this;
},
updateDepsOnEvent: function(eventName, customMethod) {
if (this.stage !== 3) {
throwErrorUnavail(methodNames[17]);
}
this._.registerEvent(eventName, customMethod);
return this;
},
removeEvent: function(eventName, customMethod) {
if (this.stage !== 3) {
throwErrorUnavail(methodNames[18]);
}
this._.unRegisterEvent(eventName, customMethod);
return this;
},
throttle: function(delay) {
if (this.stage !== 1 && this.stage !== 3) {
throwErrorUnavail(methodNames[19]);
}
if (delay && checkIf.isNumber(delay)) {
this._.throttleRate = delay;
} else if (delay === false) {
delete this._.throttleRate;
}
return this;
},
setOption: function(optionName, newValue) {
var i, len, newOptions, obj, proxied, ref;
newOptions = (
obj = {},
obj["" + optionName] = newValue,
obj
);
setOptionsForBinding(this._, newOptions);
ref = this.proxies;
for (i = 0, len = ref.length; i < len; i++) {
proxied = ref[i];
setOptionsForBinding(proxied._, newOptions);
}
return this;
}
};
BindingInterfacePublic.update = BindingInterfacePublic.set;
BindingInterfacePublic.twoWay = BindingInterfacePublic.bothWays;
BindingInterfacePublic.pipe = BindingInterfacePublic.chainTo;
methodNames = Object.keys(BindingInterfacePublic);
for (i = 0, len = methodNames.length; i < len; i++) {
methodName = methodNames[i];
BindingInterface.prototype[methodName] = BindingInterfacePublic[methodName];
}
computeProxied = function(instance, subject, specificOptions) {
var proxied;
if (specificOptions == null) {
specificOptions = {};
}
proxied = SimplyBind(subject, specificOptions, true);
if (proxied.stage !== 0) {
instance._.addDep(proxied._, instance);
maybeUpdateDep(instance._, proxied._);
}
return proxied;
};
maybeUpdateDep = function(instance, proxied) {
if (instance.options.updateOnBind || instance.type === 'Func') {
return instance.updateDep(proxied, instance);
}
};
BindingMulti = function(objects, type, bindingInterface) {
var bindings, j, len1, object;
extendState(this, bindingInterface);
this.isMulti = true;
this.type = type;
this.bindings = bindings = [];
for (j = 0, len1 = objects.length; j < len1; j++) {
object = objects[j];
bindings.push(this.createBinding(object, type, null, null, bindingInterface));
}
return Object.defineProperties(this, {
'ID': {
value: bindings[0].ID
},
'options': {
value: bindings[0].options
},
'transforms': {
value: bindings[0].transforms
},
'conditions': {
value: bindings[0].conditions
},
'updateDep': {
value: function(dep) {
return bindings.forEach(function(binding) {
return binding.updateDep(dep, binding);
});
}
},
'pholderValues': {
get: function() {
return bindings[0].pholderValues;
}
},
'value': {
get: function() {
return bindings.map(function(binding) {
return binding.value;
});
}
},
'throttleRate': {
set: function(newRate) {
return bindings.forEach(function(binding) {
return binding.throttleRate = newRate;
});
}
},
'selfTransform': {
set: function(fn) {
return bindings.forEach(function(binding) {
return binding.selfTransform = fn;
});
}
}
});
};
proto = BindingMulti.prototype = Object.create(BindingInterfacePrivate);
Object.keys(Binding.prototype).forEach(function(methodName) {
return proto[methodName] = function(a, b) {
var binding, j, len1, ref;
ref = this.bindings;
for (j = 0, len1 = ref.length; j < len1; j++) {
binding = ref[j];
binding[methodName](a, b);
}
};
});
Object.defineProperties(SimplyBind, {
'version': {
value: '1.5.4'
},
'options': {
get: function() {
var clonedOptions, key, value;
clonedOptions = {};
for (key in globalOptions) {
value = globalOptions[key];
clonedOptions[key] = value;
}
return clonedOptions;
}
}
});
SimplyBind.setOption = function(option, newValue) {
if (checkIf.isDefined(globalOptions[option])) {
globalOptions[option] = newValue;
if (option === 'placeholder') {
setPholderRegEx();
}
}
};
SimplyBind.setOptions = function(newOptions) {
var option, value;
for (option in newOptions) {
value = newOptions[option];
SimplyBind.setOption(option, value);
}
};
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].removeAllDeps(bothWays);
}
if (propMap) {
for (prop in propMap) {
boundID = propMap[prop];
boundInstances[boundID].removeAllDeps(bothWays);
}
}
}
};
return window.SimplyBind = SimplyBind;
})();