@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
1,242 lines (1,239 loc) • 43.9 kB
JavaScript
// Generated by CoffeeScript 1.10.0
(function() {
var Binding, BindingInterface, SimplyBind, applyPlaceholders, arrayMutatorMethods, boundInstances, cache, changeEvent, computeProxied, errRegEx, errors, escapeRegEx, extractElCollection, genID, genObj, getErrSource, globalOptions, handleUpdateFromEvent, isBindingInterface, isElCollection, maybeUpdateDep, methodNames, pholderRegEx, pholderRegExSplit, setOptionsForBinding, setPholderRegEx, throwError, throwErrorBadArg, throwErrorUnavail, throwWarning;
isElCollection = function(subject) {
return (subject instanceof NodeList) || (subject instanceof HTMLCollection) || ((typeof jQuery !== "undefined" && jQuery !== null) && subject instanceof jQuery);
};
extractElCollection = function(collection) {
var el;
if (collection.length > 1) {
if (collection[0].type === 'radio' || collection[0].type === 'checkbox') {
return (function() {
var i, len, results;
results = [];
for (i = 0, len = collection.length; i < len; i++) {
el = collection[i];
results.push(el);
}
return results;
})();
} else {
throwWarning('onlyOneDOMElement');
}
}
return collection[0];
};
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'];
genID = function() {
return 'sb_' + (Math.floor((1 + Math.random()) * 1000000000000).toString(16));
};
genObj = function() {
return Object.create(null);
};
isBindingInterface = function(subject) {
return subject instanceof BindingInterface;
};
setOptionsForBinding = function(binding, newOptions, makeLive) {
var option, value;
for (option in newOptions) {
value = newOptions[option];
if (globalOptions[option] != null) {
binding.options[option] = value;
}
}
if (makeLive) {
return binding.makePropertyLive();
}
};
cache = {
textNodes: {},
object: {
set: function(B) {
var propsMap, selector;
selector = B.selector || B.property;
if (B.object._sb_map) {
return B.object._sb_map[selector] = B.ID;
} else {
propsMap = {};
propsMap[selector] = B.ID;
return Object.defineProperty(B.object, '_sb_map', {
'configurable': true,
'value': propsMap
});
}
},
get: function(object, selector) {
if (object._sb_map && object._sb_map[selector]) {
return boundInstances[object._sb_map[selector]];
}
}
},
simpleObject: {
set: function(B) {
return Object.defineProperty(B.object, '_sb_ID', {
'configurable': true,
'value': B.ID
});
},
get: function(object) {
return boundInstances[object._sb_ID];
}
}
};
escapeRegEx = /[.*+?^${}()|[\]\\]/g;
pholderRegEx = null;
pholderRegExSplit = null;
setPholderRegEx = function() {
var end, middle, start;
start = globalOptions.placeholderStart.replace(escapeRegEx, '\\$&');
end = globalOptions.placeholderEnd.replace(escapeRegEx, '\\$&');
middle = "[^" + end + "]+";
pholderRegEx = new RegExp(start + "(" + middle + ")" + end, 'g');
return 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;
};
throwError = function(errorName, nonTemplate) {
var err, errSource;
errSource = getErrSource();
err = nonTemplate ? errorName : errors[errorName];
err += "\n\nCall Source:\n" + errSource;
throw new Error('SimplyBind: ' + err);
};
throwWarning = function(warningName) {
var errSource, warn;
if (!globalOptions.silent) {
errSource = getErrSource();
warn = errors[warningName];
warn += "\n\nCall Source:\n" + errSource;
return console.log('SimplyBind: ' + warn);
}
};
throwErrorUnavail = function(methodName) {
return throwError("You can't use/invoke ." + methodName + "() at this stage", true);
};
throwErrorBadArg = function(methodName, arg) {
return throwError("Invalid argument/s (" + arg + ") passed to ." + methodName + "()", true);
};
getErrSource = function() {
return (new Error).stack.split('\n').slice(3, 10).map(function(line) {
return line.replace(errRegEx, function(all, extras, source) {
return "@ " + source;
});
}).join('\n');
};
errRegEx = /[@(?:\wat\s)](.*)\/(.+)$/;
Error.prototype.stack = '\n\n\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 1 DOM element to SimplyBind unless it's a collection of radio/checkbox inputs",
emptyElList: "You can't bind an empty element list to anything..."
};
boundInstances = {};
globalOptions = {
silent: false,
liveProps: true,
dispatchEvents: false,
updateEvenIfUndefined: false,
updateEvenIfSame: false,
updateOnBind: true,
mutateInherited: false,
trackArrayChildren: false,
simpleSelector: false,
promiseTransforms: false,
placeholderStart: '{{',
placeholderEnd: '}}'
};
setPholderRegEx();
SimplyBind = function(subject, options, isBeingProxied) {
if ((!subject && subject !== 0) || (typeof subject !== 'string' && typeof subject !== 'number' && typeof subject !== 'function' && !(subject instanceof Array))) {
if (!isBindingInterface(subject)) {
throwError('invalidParamName');
}
}
if (typeof subject === 'object' && !(subject instanceof Array)) {
return new BindingInterface(subject._, 1);
} else {
return new BindingInterface(null, 0, null, subject, isBeingProxied, options);
}
};
Binding = function(type, selector, property, object, objects, options, extraState, isBeingProxied) {
var arrayBinding, key, subjectValue, value;
for (key in extraState) {
value = extraState[key];
this[key] = value;
}
this.options = options;
this.type = type;
this.ID = genID();
this.property = property;
this.selector = selector;
this.object = object;
this.objects = objects;
this.valueOriginal = null;
this.value = null;
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') {
subjectValue = this.fetchDirectValue(null, isBeingProxied);
if (typeof subjectValue === 'undefined' && this.type === 'ObjectProp') {
this.object[this.property] = subjectValue = null;
}
this.value = this.valueOriginal = subjectValue;
if (this.placeholder && !this.pholderValues) {
this.scanForPholders();
}
}
this.makePropertyLive();
this.attachEvents();
if (this.object instanceof Array && this.type !== 'Array') {
this.arrayBinding = arrayBinding = cache.simpleObject.get(this.object);
if (arrayBinding && arrayBinding.options.trackArrayChildren && arrayBinding.trackedChildren.indexOf(this.property) === -1) {
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(Private, stage, inheritedState, subject, isBeingProxied, options) {
var i, key, len, newObjectType, node, nodeName, object, ref, ref1, split, value;
ref = inheritedState || {};
for (key in ref) {
value = ref[key];
this[key] = value;
}
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 (typeof subject === 'function') {
Private = this.setObject(subject, 'Func', true, isBeingProxied);
this.stage = 1;
} else if (subject instanceof Array) {
Private = this.setObject(subject, 'Array', true);
this.stage = 1;
} else {
if (typeof subject === 'number') {
subject = subject.toString();
}
this.selector = this.property = subject;
if (!this.options.simpleSelector) {
if (this.selector.indexOf(':') !== -1) {
split = this.property.split(':');
this.descriptor = split[0];
this.property = split[1];
}
if (this.selector.indexOf('.') !== -1) {
split = this.property.split('.');
this.property = split[0];
this.placeholder = split.slice(1).join('.');
}
this.selector = this.property;
}
}
break;
case 1:
if (Private) {
break;
}
if (isElCollection(subject)) {
subject = extractElCollection(subject);
if (!subject) {
return throwWarning('emptyElList');
}
this.objects = [].concat(subject);
ref1 = this.objects;
for (i = 0, len = ref1.length; i < len; i++) {
node = ref1[i];
if (node.checked) {
object = node;
}
}
subject = object || (object = this.objects[0]);
if (!(this.objects.length > 1)) {
delete this.objects;
}
}
object = subject;
this.isDom = object.nodeName && object.nodeType === 1;
if (this.isDom) {
nodeName = this.objects ? this.objects[0].nodeName.toUpperCase() : object.nodeName.toUpperCase();
this.isDomInput = nodeName === 'INPUT' || nodeName === 'TEXTAREA' || nodeName === 'SELECT';
}
newObjectType = (function() {
switch (false) {
case !this.state.hasEventName:
return 'Event';
case !(this.isDomInput && this.property === 'value'):
return 'DOMValue';
case !(this.isDom && this.property === 'textContent'):
return 'DOMText';
case this.descriptor !== 'attr':
return 'DOMAttr';
default:
return 'ObjectProp';
}
}).call(this);
Private = this.setObject(object, newObjectType);
}
Object.defineProperties(this, {
'_': {
value: Private
},
'ID': {
get: function() {
return Private.ID;
}
},
'value': {
get: function() {
return Private.value;
}
},
'original': {
get: function() {
return Private.objects || Private.object;
}
},
'dependents': {
get: function() {
return Private.deps.slice().map(function(dep) {
return dep.object;
});
}
},
'lastProxied': {
get: function() {
return this.proxies[this.proxies.length - 1];
}
},
'new': {
value: function(stage, object) {
return new BindingInterface(Private, stage, this.getState(), object, isBeingProxied);
}
}
});
return this;
};
Binding.prototype = {
makePropertyLive: function(force) {
var _this, opts, origGetFn, origSetFn, propertyDescriptor, ref, ref1, shouldWriteLiveProp;
if (this.options.liveProps) {
_this = this;
if (this.type === 'ObjectProp') {
propertyDescriptor = Object.getOwnPropertyDescriptor(this.object, this.property);
shouldWriteLiveProp = this.options.mutateInherited || (propertyDescriptor && propertyDescriptor.configurable);
shouldWriteLiveProp = shouldWriteLiveProp && this.object.constructor !== CSSStyleDeclaration;
if ((shouldWriteLiveProp && !this.isLiveProp) || force) {
this.isLiveProp = true;
origGetFn = (propertyDescriptor != null ? (ref = propertyDescriptor.get) != null ? ref.bind(this.object) : void 0 : void 0) || function() {
return _this.value;
};
origSetFn = (propertyDescriptor != null ? (ref1 = propertyDescriptor.set) != null ? ref1.bind(this.object) : void 0 : void 0) || function() {};
return Object.defineProperty(this.object, this.property, {
configurable: true,
enumerable: propertyDescriptor != null ? propertyDescriptor.enumerable : void 0,
get: function() {
return origGetFn();
},
set: function(newValue) {
_this.setValue(newValue);
return origSetFn(newValue);
}
});
}
} else if (this.type === 'Array') {
if (!this.isLiveProp) {
this.isLiveProp = true;
arrayMutatorMethods.forEach(function(method) {
return Object.defineProperty(_this.value, method, {
configurable: true,
value: function() {
var result;
result = Array.prototype[method].apply(_this.value, arguments);
_this.updateAllDeps();
return result;
}
});
});
}
if (this.options.trackArrayChildren && !this.trackedChildren) {
this.trackedChildren = [];
this.updateSelf = function() {
return _this.updateAllDeps();
};
opts = {
updateOnBind: false
};
return this.value.forEach(function(item, index) {
_this.trackedChildren.push('' + index);
return SimplyBind(index, opts).of(_this.value).to(_this.updateSelf);
});
}
}
}
},
addDep: function(dep, bothWays) {
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) {
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);
return 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) {
return this.destroy();
}
},
destroy: function() {
var event, i, j, len, len1, method, ref;
delete boundInstances[this.ID];
if (this.type === 'ObjectProp' || this.type === 'DOMText') {
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(type, isBeingProxied) {
if (type == null) {
type = this.type;
}
switch (type) {
case 'Func':
if (!isBeingProxied) {
return this.object();
}
break;
case 'Array':
return this.object;
case 'DOMAttr':
return this.object.getAttribute(this.property) || '';
default:
return this.object[this.property];
}
},
setValue: function(newValue, specificPlaceholder, updater, fromSelf) {
var currentValue, hasChanged, i, isNewValue, isValidValue, len, node, placeholderValue, prevValue, ref;
if (updater == null) {
updater = this;
}
currentValue = specificPlaceholder ? this.pholderValues[specificPlaceholder] : this.value;
if (this.selfTransform) {
newValue = this.selfTransform(newValue);
}
isNewValue = newValue !== currentValue || this.options.updateEvenIfSame;
isValidValue = typeof newValue !== 'undefined' || this.options.updateEvenIfUndefined;
if ((isNewValue && isValidValue || this.isEvent) && this.type !== 'Array') {
prevValue = this.value;
if (specificPlaceholder) {
this.pholderValues[specificPlaceholder] = placeholderValue = newValue;
this.value = newValue = applyPlaceholders(this.pholderContexts, this.pholderValues, this.pholderIndexMap);
} else {
this.value = newValue;
}
switch (this.type) {
case 'ObjectProp':
if (!this.isLiveProp) {
this.object[this.property] = newValue;
}
break;
case 'Func':
prevValue = this.valuePassed;
this.valuePassed = newValue;
this.value = this.object(newValue, prevValue);
break;
case 'Event':
if (!fromSelf) {
this.emitCauser = updater;
this.emitEvent(newValue);
this.emitCauser = null;
}
break;
case 'DOMValue':
if (this.objects) {
ref = this.objects;
for (i = 0, len = ref.length; i < len; i++) {
node = ref[i];
hasChanged = node.checked = node.value === newValue;
if (this.options.dispatchEvents && hasChanged) {
node.dispatchEvent(changeEvent());
}
}
} else if (this.object.value !== this.value) {
this.object.value = this.value;
if (this.options.dispatchEvents) {
this.object.dispatchEvent(changeEvent());
}
}
break;
case 'DOMAttr':
this.object.setAttribute(this.property, this.value);
break;
case 'DOMText':
this.applyTextContentReplace(this.object, specificPlaceholder, placeholderValue);
}
if (!(fromSelf && this.emitCauser)) {
this.updateAllDeps(updater);
}
}
},
updateAllDeps: function(updater) {
var dep, i, len, ref;
if (this.deps.length) {
if (this.throttleRate) {
if (+(new Date) - this.lastUpdate < this.throttleRate) {
clearTimeout(this.throttleTimeout);
return this.throttleTimeout = setTimeout(((function(_this) {
return function() {
return _this.updateAllDeps(updater);
};
})(this)), this.throttleRate);
} else {
this.lastUpdate = +(new Date);
}
}
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, isInfiniteLoop, isNewEventBinding, myPlaceholder, newValue;
isNewEventBinding = this.value === null && this.type === 'Event';
isInfiniteLoop = updater && ((updater === dep) || (updater !== this && updater.depsMap[1][dep.ID]));
if (isInfiniteLoop || isNewEventBinding) {
return;
}
updater || (updater = this);
myPlaceholder = this.myPholders[dep.ID];
currentValue = myPlaceholder ? this.pholderValues[myPlaceholder] : this.value;
depPlaceholder = this.depsPholders[dep.ID];
depValue = depPlaceholder ? dep.pholderValues[depPlaceholder] : dep.value;
newValue = currentValue;
newValue = this.applyTransform(dep, depPlaceholder, currentValue, depValue);
if (!this.checkCondition(dep, depPlaceholder, currentValue, depValue)) {
return;
}
if (this.options.promiseTransforms && (newValue != null) && typeof newValue.then === 'function') {
return newValue.then((function(_this) {
return function(newValue) {
return dep.setValue(newValue, depPlaceholder, updater);
};
})(this));
} else {
return dep.setValue(newValue, depPlaceholder, updater);
}
},
processTransform: function(transformFn, subjects) {
var i, len, prox, proxID;
if (typeof transformFn !== 'function') {
return throwWarning('fnOnly');
} else {
for (i = 0, len = subjects.length; i < len; i++) {
prox = subjects[i];
proxID = prox.ID;
proxID += prox.placeholder ? '.' + prox.placeholder : '';
this.addTransform(proxID, transformFn);
prox = prox._;
if (this.depsMap[2][prox.ID]) {
prox.addTransform(this.ID, transformFn);
}
if (this.options.updateOnBind || this.type === 'Func') {
this.updateDep(prox);
}
}
return true;
}
},
applyTransform: function(dep, placeholder, value, depValue) {
var depID;
if (!this.hasTransforms) {
return value;
} else {
depID = dep.ID;
if (placeholder) {
depID += '.' + placeholder;
}
if (this.transforms[depID]) {
return this.transforms[depID](value, depValue);
} else {
return value;
}
}
},
addTransform: function(ID, transformFn) {
this.hasTransforms = true;
return this.transforms[ID] = transformFn;
},
processCondition: function(conditionFn, subjects) {
var i, len, prox, proxID;
if (typeof conditionFn !== 'function') {
return throwWarning('fnOnly');
} else {
for (i = 0, len = subjects.length; i < len; i++) {
prox = subjects[i];
proxID = prox.ID;
proxID += prox.placeholder ? '.' + prox.placeholder : '';
this.addCondition(proxID, conditionFn);
prox = prox._;
if (this.depsMap[2][prox.ID]) {
prox.addCondition(this.ID, conditionFn);
}
}
return true;
}
},
checkCondition: function(dep, placeholder, value, depValue) {
var depID;
if (!this.hasConditions) {
return true;
} else {
depID = dep.ID;
if (placeholder) {
depID += '.' + placeholder;
}
if (this.conditions[depID]) {
return this.conditions[depID](value, depValue);
} else {
return true;
}
}
},
addCondition: function(ID, conditionFn) {
this.hasConditions = true;
return this.conditions[ID] = conditionFn;
},
scanForPholders: function() {
var index;
this.pholderValues = genObj();
this.pholderIndexMap = genObj();
this.pholderContexts = [];
if (typeof this.valueOriginal === 'string') {
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') {
return this.scanTextNodesPlaceholders();
}
},
scanTextNodesPlaceholders: function(element) {
var contexts, hasPlaceholders, i, index, indexMap, len, node, nodeID, nodeTextContent, ref, values;
if (element == null) {
element = this.object;
}
ref = element.childNodes;
for (i = 0, len = ref.length; i < len; i++) {
node = ref[i];
if (node.nodeType !== 3) {
this.scanTextNodesPlaceholders(node);
} else {
nodeID = genID();
nodeTextContent = node.textContent;
hasPlaceholders = false;
values = genObj();
indexMap = genObj();
contexts = nodeTextContent.split(pholderRegExSplit);
index = 0;
nodeTextContent.replace(pholderRegEx, (function(_this) {
return function(e, pholder) {
hasPlaceholders = true;
indexMap[index++] = pholder;
return values[pholder] = _this.pholderValues[pholder];
};
})(this));
if (!hasPlaceholders) {
values = null;
}
Object.defineProperty(node, '_sb_ID', {
'configurable': true,
'value': nodeID
});
cache.textNodes[nodeID] = [nodeTextContent, contexts, values, indexMap];
}
}
},
getNodeFullValue: function(node, placeholderToUpdate, newPlaceholderValue) {
var nodeProps, pholderValues;
nodeProps = cache.textNodes[node._sb_ID];
if (!nodeProps) {
return false;
} else {
pholderValues = nodeProps[2];
if (pholderValues) {
pholderValues[placeholderToUpdate] = newPlaceholderValue;
return applyPlaceholders(nodeProps[1], nodeProps[2], nodeProps[3]);
} else {
return nodeProps[0];
}
}
},
applyTextContentReplace: function(subject, placeholderToUpdate, newPlaceholderValue) {
var i, len, newFullValue, node, ref;
if (!this.placeholder) {
subject.textContent = this.value;
} else {
ref = subject.childNodes;
for (i = 0, len = ref.length; i < len; i++) {
node = ref[i];
if (node.nodeType !== 3) {
this.applyTextContentReplace(node, placeholderToUpdate, newPlaceholderValue);
} else {
newFullValue = this.getNodeFullValue(node, placeholderToUpdate, newPlaceholderValue);
if (newFullValue) {
node.textContent = newFullValue;
}
}
}
}
},
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;
},
attachEvents: function() {
var _this, handleUpdateEvent;
if (this.type === 'Event') {
return this.registerEvent(this.eventName, this.customEventMethod["in"]);
} else if (this.type === 'DOMValue' || this.type === 'DOMText') {
_this = this;
handleUpdateEvent = function(event) {
if (!event._sb) {
return _this.setValue(event.target.value);
}
};
if (this.isDomInput) {
if (this.object.type === 'radio' || this.object.type === 'checkbox') {
return this.objects.forEach(function(object) {
return object.addEventListener('change', handleUpdateEvent, false);
});
} else {
this.object.addEventListener('input', handleUpdateEvent, false);
return this.object.addEventListener('change', handleUpdateEvent, false);
}
}
}
},
registerEvent: function(eventName, customInMethod) {
var attachmentMethod, defaultInMethod;
if (this.attachedEvents.indexOf(eventName) !== -1) {
return;
}
defaultInMethod = 'addEventListener';
this.attachedEvents.push(eventName);
attachmentMethod = customInMethod || defaultInMethod;
return 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;
return this.invokeEventMethod(eventName, removalMethod, defaultRemoveMethod);
},
invokeEventMethod: function(eventName, eventMethod, backupMethod) {
var subject;
subject = this.object;
if (this.isDom && (typeof jQuery !== "undefined" && jQuery !== null) && eventMethod === 'on' || eventMethod === 'off') {
subject = jQuery(this.object);
}
if (!subject[eventMethod]) {
eventMethod = backupMethod;
}
if (!this.eventHandler) {
this.eventHandler = handleUpdateFromEvent.bind(this);
}
return typeof subject[eventMethod] === "function" ? subject[eventMethod](eventName, this.eventHandler) : void 0;
},
emitEvent: function(extraData) {
var defaultOutMethod, emitMethod, subject;
subject = this.object;
defaultOutMethod = 'dispatchEvent';
emitMethod = this.customEventMethod.out || defaultOutMethod;
if (this.isDom && (typeof jQuery !== "undefined" && jQuery !== null) && 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.boundData = extraData;
return subject[emitMethod](this.eventObject);
}
return subject[emitMethod](this.eventName, extraData);
}
};
handleUpdateFromEvent = function() {
var fetchedValue;
fetchedValue = this.type === 'Event' ? arguments[this.property] : this.fetchDirectValue();
return this.setValue(fetchedValue, null, this.emitCauser, true);
};
BindingInterface.prototype = {
of: function(object) {
var proxied;
if (this.stage !== 0 && this.stage !== 2) {
throwErrorUnavail(methodNames[0]);
}
if (!((typeof object === 'object') || (typeof object === 'function'))) {
throwErrorBadArg(methodNames[0], object);
}
if (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 || typeof eventName !== 'string') {
throwErrorBadArg(methodNames[1], eventName);
} else if (isNaN(parseInt(this.property))) {
throwWarning('badEventArg');
}
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) {
if (this.stage !== 1 || this.stage === 1 && this._.type === 'Array') {
throwErrorUnavail(methodNames[8]);
}
if (typeof transformFn !== 'function') {
throwWarning('fnOnly');
} else {
this._.selfTransform = transformFn;
if (this._.options.updateOnBind) {
this.set(this.value);
}
}
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 boundProxied, originCondition, originTransform, proxied, transformToUse;
if (this.stage !== 3 || this.state.hasMultiTransform) {
throwErrorUnavail(methodNames[13]);
}
proxied = this.lastProxied;
boundProxied = proxied._.addDep(this._, true);
originTransform = this._.transforms[proxied.ID];
originCondition = this._.conditions[proxied.ID];
if (originTransform || dontOrAltTransform) {
transformToUse = typeof dontOrAltTransform === 'function' ? dontOrAltTransform : originTransform;
if (transformToUse && dontOrAltTransform !== false) {
boundProxied.addTransform(this.ID, transformToUse);
}
}
if (originCondition) {
boundProxied.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 (typeof delay === 'number' && 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, true);
ref = this.proxies;
for (i = 0, len = ref.length; i < len; i++) {
proxied = ref[i];
setOptionsForBinding(proxied._, newOptions, true);
}
return this;
}
};
BindingInterface.prototype.update = BindingInterface.prototype.set;
BindingInterface.prototype.twoWay = BindingInterface.prototype.bothWays;
BindingInterface.prototype.pipe = BindingInterface.prototype.chainTo;
methodNames = Object.keys(BindingInterface.prototype);
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);
}
};
Object.defineProperties(BindingInterface.prototype, {
setObject: {
value: function(object, newObjectType, isSimpleObject, isBeingProxied) {
var cacheObjectType, extraState, fromCache, newPrivate;
this.object = object;
cacheObjectType = isSimpleObject ? 'simpleObject' : 'object';
fromCache = cache[cacheObjectType].get(object, this.selector);
if (fromCache) {
fromCache.placeholder = this.placeholder;
if (this.placeholder && !fromCache.pholderValues) {
fromCache.valueOriginal = fromCache.fetchDirectValue();
fromCache.scanForPholders();
}
if (fromCache.type === 'ObjectProp' && !(this.property in object)) {
fromCache.makePropertyLive(true);
}
setOptionsForBinding(fromCache, this.options, true);
return fromCache;
} else {
extraState = {
isDom: this.isDom,
isDomInput: this.isDomInput,
placeholder: this.placeholder,
eventName: this.eventName,
customEventMethod: this.customEventMethod,
isEvent: this.state.hasEventName
};
newPrivate = new Binding(newObjectType, this.selector, this.property, this.object, this.objects, this.options, extraState, isBeingProxied);
cache[cacheObjectType].set(newPrivate);
return newPrivate;
}
}
},
getState: {
value: function() {
return {
options: this.options,
selector: this.selector,
object: this.object,
property: this.property,
descriptor: this.descriptor,
placeholder: this.placeholder,
eventName: this.eventName,
customEventMethod: this.customEventMethod,
proxied: this.proxied,
proxies: this.proxies,
state: this.state
};
}
}
});
Object.defineProperties(SimplyBind, {
'version': {
value: '1.2.1'
},
'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 (globalOptions[option] != null) {
globalOptions[option] = newValue;
if (option.indexOf('place') !== -1) {
return 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 && ((typeof object === 'object') || (typeof object === 'function'))) {
if (isElCollection(object)) {
object = extractElCollection(object);
}
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;
})();