@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
1,179 lines (1,176 loc) • 38.6 kB
JavaScript
// Generated by CoffeeScript 1.10.0
(function() {
var Binding, BindingInterface, BindingInterfacePrivate, GroupBinding, METHOD_bothWays, METHOD_chainTo, METHOD_condition, METHOD_conditionAll, METHOD_get, METHOD_of, METHOD_ofEvent, METHOD_pollEvery, METHOD_removeEvent, METHOD_set, METHOD_setOption, METHOD_stopPolling, METHOD_throttle, METHOD_transform, METHOD_transformAll, METHOD_transformSelf, METHOD_unBind, METHOD_updateSubsOnEvent, OPTS_NOUPDATE, SimplyBind, applyPlaceholders, arrayMutatorMethods, boundInstances, cache, checkIf, cloneObject, currentID, defaultOptions, dummyPropertyDescriptor, errors, escapeRegEx, extendState, genID, genObj, genProxiedEventInterface, genProxiedInterface, getErrSource, handleUpdateFromEvent, noop, pholderRegEx, pholderRegExSplit, placeholder, proto, setPholderRegEx, settings, targetIncludes, throwError, throwErrorBadArg, throwWarning;
OPTS_NOUPDATE = {
updateOnBind: false
};
currentID = 0;
arrayMutatorMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'];
dummyPropertyDescriptor = {};
boundInstances = {};
placeholder = ['{{', '}}'];
settings = Object.create({
silent: false,
trackArrayChildren: false
}, {
placeholder: {
get: function() {
return placeholder;
},
set: function(newPlaceholder) {
if (checkIf.isArray(newPlaceholder) && newPlaceholder.length === 2) {
placeholder = newPlaceholder;
setPholderRegEx();
}
}
}
});
defaultOptions = {
simpleSelector: false,
promiseTransforms: false,
dispatchEvents: false,
updateEvenIfSame: false,
updateOnBind: true
};
noop = function() {};
genID = function() {
return '' + (++currentID);
};
genObj = function() {
return Object.create(null);
};
genProxiedInterface = function(publisher, isSibling) {
return function(subject, specificOptions, saveOptions) {
return SimplyBind(subject, specificOptions, saveOptions, publisher, isSibling);
};
};
genProxiedEventInterface = function(publisher, isSibling) {
return function(eventName, customOutMethod, customInMethod) {
return SimplyBind(0, null, false, publisher, isSibling).ofEvent(eventName, customInMethod, customOutMethod);
};
};
targetIncludes = function(target, item) {
return 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;
},
isSimpleObject: function(subject) {
return checkIf.isFunction(subject) || checkIf.isArray(subject);
}
};
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, isSimpleObject, selector, isMultiChoice) {
if (isSimpleObject) {
return boundInstances[object._sb_ID];
} else {
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 = 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;
};
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"
};
SimplyBind = function(subject, options, saveOptions, publisher, isSibling) {
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)) {
if (publisher) {
return subject.addToPublisher(publisher);
}
return subject.selfClone(1);
} else {
return new BindingInterface(null, 0, subject, options, publisher, isSibling, saveOptions);
}
};
SimplyBind.version = '1.9.0';
SimplyBind.settings = settings;
SimplyBind.defaultOptions = defaultOptions;
SimplyBind.func = function(funcName, customOptions) {
var bindingInterface;
bindingInterface = SimplyBind(funcName, customOptions);
bindingInterface.isProxy = true;
return bindingInterface;
};
SimplyBind.unBindAll = function(object, bothWays) {
var boundID, prop, propMap;
if (object && (checkIf.isObject(object) || checkIf.isFunction(object))) {
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 arrayBinding, subjectValue;
extendState(this, state);
this.optionsDefault = this.saveOptions ? this.options : defaultOptions;
this.type = type;
this.object = object;
this.ID = genID();
this.subs = [];
this.subOpts = genObj();
this.pubsMap = genObj();
this.subsPholders = genObj();
this.myPholders = genObj();
this.attachedEvents = [];
if (this.type === 'Array' || this.type === 'Proxy') {
this.setValue = noop;
}
/* ========================================================================== */
if (!(this.type === 'Event' || (this.type === 'Func' && this.parentInterface))) {
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.convertToLive();
}
this.attachEvents();
if (this.object instanceof Array && this.type !== 'Array') {
this.arrayBinding = arrayBinding = cache.get(this.object, true);
if (arrayBinding && arrayBinding.trackedChildren && !targetIncludes(arrayBinding.trackedChildren, this.property)) {
arrayBinding.trackedChildren.push(this.property);
SimplyBind(this.property).of(this.object).to(arrayBinding.updateSelf);
}
}
return boundInstances[this.ID] = this;
};
Binding.prototype = {
convertToLive: function(force) {
var _, context, originalFn, propertyDescriptor, shouldWriteLiveProp, slice;
_ = this;
switch (this.type) {
case 'ObjectProp':
propertyDescriptor = Object.getOwnPropertyDescriptor(this.object, this.property) || dummyPropertyDescriptor;
shouldWriteLiveProp = force || propertyDescriptor.configurable;
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);
}
});
}
break;
case 'Array':
arrayMutatorMethods.forEach(function(method) {
return Object.defineProperty(_.value, method, {
configurable: true,
value: function() {
var result;
result = Array.prototype[method].apply(_.value, arguments);
_.updateAllSubs(_);
return result;
}
});
});
if (settings.trackArrayChildren && !this.trackedChildren) {
this.trackedChildren = [];
this.updateSelf = function() {
return _.updateAllSubs(_);
};
this.value.forEach(function(item, index) {
_.trackedChildren.push('' + index);
return SimplyBind(index, OPTS_NOUPDATE).of(_.value).to(_.updateSelf);
});
}
break;
case 'Proxy':
originalFn = this.original = this.value;
context = this.object;
this.value = {
result: null,
args: null
};
if (checkIf.isFunction(originalFn)) {
slice = [].slice;
this.object[this.property] = function() {
var args, result;
_.value.args = args = slice.call(arguments);
_.value.result = result = originalFn.apply(context, args);
_.updateAllSubs(_);
return result;
};
}
}
},
addSub: function(sub, options, updateOnce) {
var alreadyHadSub, j, len, ref, subItem;
if (options == null) {
options = this.optionsDefault;
}
if (sub.isMulti) {
ref = sub.bindings;
for (j = 0, len = ref.length; j < len; j++) {
subItem = ref[j];
this.addSub(subItem, options, updateOnce);
}
} else {
if (this.subOpts[sub.ID]) {
alreadyHadSub = !this.myPholders[sub.ID];
} else {
this.subs.unshift(sub);
this.subOpts[sub.ID] = cloneObject(options);
if (sub.type === 'Func' || sub.type === 'Event') {
this.subOpts[sub.ID].updateEvenIfSame = true;
}
sub.pubsMap[this.ID] = this;
if (updateOnce) {
SimplyBind('value', OPTS_NOUPDATE).of(sub).to((function(_this) {
return function() {
return _this.removeSub(sub);
};
})(this));
}
}
if (this.placeholder) {
this.myPholders[sub.ID] = this.placeholder;
} else if (this.myPholders[sub.ID]) {
delete this.myPholders[sub.ID];
}
if (sub.placeholder) {
this.subsPholders[sub.ID] = sub.placeholder;
}
}
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.subOpts[sub.ID]) {
this.subs.splice(this.subs.indexOf(sub), 1);
delete this.subsPholders[sub.ID];
delete this.subOpts[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, k, len, len1, method, 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.remove);
}
delete this.object._sb_map;
} else if (this.type === 'Array') {
delete this.object._sb_ID;
for (k = 0, len1 = arrayMutatorMethods.length; k < len1; k++) {
method = arrayMutatorMethods[k];
delete this.object[method];
}
} else if (this.type === 'Func') {
delete this.object._sb_ID;
} else {
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;
}
},
fetchDirectValue: function() {
var type;
type = this.type;
switch (false) {
case type !== 'Func':
return this.object();
case type !== 'Array':
return this.object;
default:
return this.object[this.property];
}
},
setValue: function(newValue, specificPlaceholder, publisher, fromSelf) {
var prevValue;
publisher || (publisher = this);
if (this.selfTransform) {
newValue = this.selfTransform(newValue);
}
if (specificPlaceholder) {
prevValue = this.pholderValues[specificPlaceholder];
this.pholderValues[specificPlaceholder] = newValue;
newValue = applyPlaceholders(this.pholderContexts, this.pholderValues, this.pholderIndexMap);
}
switch (this.type) {
case 'ObjectProp':
if (!this.isLiveProp && newValue !== this.value) {
this.object[this.property] = newValue;
}
break;
case 'Func':
prevValue = this.valuePassed;
if (publisher.type === 'Array' && newValue === publisher.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;
}
}
this.value = newValue;
this.updateAllSubs(publisher);
},
updateAllSubs: function(publisher) {
var arr, currentTime, i, timePassed;
if (this.subs.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.updateAllSubs(publisher);
};
})(this)), this.throttleRate - timePassed);
} else {
this.lastUpdate = currentTime;
}
}
i = (arr = this.subs).length;
while (i--) {
this.updateSub(arr[i], publisher);
}
}
},
updateSub: function(sub, publisher) {
var currentValue, myPlaceholder, newValue, subOptions, subPlaceholder, subValue;
if ((publisher === sub) || (publisher !== this && publisher.subOpts[sub.ID])) {
return;
}
subOptions = this.subOpts[sub.ID];
myPlaceholder = this.placeholder && this.myPholders[sub.ID];
subPlaceholder = sub.placeholder && this.subsPholders[sub.ID];
currentValue = myPlaceholder ? this.pholderValues[myPlaceholder] : this.value;
subValue = subPlaceholder ? sub.pholderValues[subPlaceholder] : sub.value;
newValue = this.transforms && this.transforms[sub.ID] ? this.transforms[sub.ID](currentValue, subValue, sub.object) : currentValue;
if (newValue === subValue && !subOptions.updateEvenIfSame || this.conditions && this.conditions[sub.ID] && !this.conditions[sub.ID](newValue, subValue, sub.object)) {
return;
}
if (subOptions.promiseTransforms && newValue && checkIf.isFunction(newValue.then)) {
newValue.then(function(newValue) {
sub.setValue(newValue, subPlaceholder, publisher);
});
} else {
sub.setValue(newValue, subPlaceholder, publisher);
}
},
addModifierFn: function(targetCollection, subInterfaces, targetFn, updateOnBind) {
var j, len, subInterface, subscriber;
if (!checkIf.isFunction(targetFn)) {
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(targetCollection, subscriber.bindings, targetFn);
} else {
this.setModifierFn(subscriber.ID, targetCollection, targetFn);
if (this.pubsMap[subscriber.ID]) {
subscriber.setModifierFn(this.ID, targetCollection, targetFn);
}
if ((updateOnBind || this.type === 'Func') && targetCollection === 'transforms') {
this.updateSub(subscriber, this);
}
}
}
return true;
}
},
setModifierFn: function(targetID, targetCollection, targetFn) {
if (this[targetCollection] == null) {
this[targetCollection] = genObj();
}
this[targetCollection][targetID] = targetFn;
},
setSelfTransform: function(transformFn, updateOnBind) {
if (this.type !== 'Array') {
this.selfTransform = transformFn;
if (updateOnBind) {
this.setValue(this.value);
}
}
},
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));
}
},
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;
},
attachEvents: function() {
if (this.eventName) {
this.registerEvent(this.eventName, this.customEventMethod["in"]);
}
},
registerEvent: function(eventName, customInMethod) {
var attachmentMethod, defaultInMethod;
if (!targetIncludes(this.attachedEvents, eventName)) {
defaultInMethod = 'on';
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 = 'removeListener';
this.attachedEvents.splice(indexOfEvent, 1);
removalMethod = customMethod || defaultRemoveMethod;
this.invokeEventMethod(eventName, removalMethod, defaultRemoveMethod);
},
invokeEventMethod: function(eventName, eventMethod, backupMethod) {
var subject;
subject = 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 = 'emit';
emitMethod = this.customEventMethod.out || defaultOutMethod;
if (!subject[emitMethod]) {
emitMethod = defaultOutMethod;
}
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);
}
};
/**
* 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(inheritedState, stage, subject, options, parentInterface, isSibling, saveOptions) {
var key, split;
if (inheritedState) {
extendState(this, inheritedState);
}
this.stage = stage || 0;
if (this.subs == null) {
this.subs = [];
}
if (saveOptions) {
this.saveOptions = saveOptions;
}
if (isSibling) {
this.isSibling = isSibling;
}
if (parentInterface) {
this.parentInterface = parentInterface;
}
if (this.stage === 0) {
this.optionsPassed = options || (options = {});
this.options = {};
for (key in defaultOptions) {
this.options[key] = options[key] != null ? options[key] : defaultOptions[key];
}
if (checkIf.isSimpleObject(subject)) {
this.stage = 1;
return this.setObject(subject, true);
} else {
if (checkIf.isNumber(subject)) {
subject = subject.toString();
}
this.selector = this.property = subject;
if (!this.options.simpleSelector) {
if (targetIncludes(this.selector, ':')) {
split = this.property.split(':');
this.descriptor = split[0];
this.property = split[1];
}
if (targetIncludes(this.selector, '.')) {
split = this.property.split('.');
this.property = split[0];
this.placeholder = split.slice(1).join('.');
}
this.selector = this.property;
}
}
}
return this;
};
BindingInterfacePrivate = {
selfClone: function(newStage) {
return new BindingInterface(this, newStage);
},
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, isSimpleObject) {
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);
cache.set(newBinding, isSimpleObject);
return newBinding;
}
},
patchCachedBinding: function(cachedBinding) {
var key, option, ref, ref1, value;
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.convertToLive(true);
}
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;
},
setObject: function(subject, isSimpleObject) {
var newObjectType;
switch (false) {
case !isSimpleObject:
newObjectType = checkIf.isFunction(subject) ? 'Func' : 'Array';
break;
case !this.eventName:
newObjectType = 'Event';
break;
case !this.isProxy:
newObjectType = 'Proxy';
break;
default:
newObjectType = 'ObjectProp';
}
if (this.descriptor === 'multi') {
if (!subject.length) {
throwError('emptyList');
}
this.defineMainProps(new GroupBinding(this, subject, newObjectType));
} else {
this.defineMainProps(this.createBinding(subject, newObjectType, this, isSimpleObject));
}
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.parentInterface) {
return this.addToPublisher(this.parentInterface);
} else {
return this;
}
},
addToPublisher: function(publisherInterface) {
var alreadyHadSub, binding, j, len, ref;
if (this.isSibling) {
publisherInterface._.addBinding(this._);
} else {
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._);
}
}
}
return publisherInterface;
}
};
BindingInterface.prototype = Object.create(BindingInterfacePrivate, {
of: {
get: function() {
if (!this.stage) {
return METHOD_of;
}
}
},
ofEvent: {
get: function() {
if (!this.stage) {
return METHOD_ofEvent;
}
}
},
chainTo: {
get: function() {
if (this.stage) {
return METHOD_chainTo;
}
}
},
set: {
get: function() {
if (this.stage) {
return METHOD_set;
}
}
},
get: {
get: function() {
if (this.stage) {
return METHOD_get;
}
}
},
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 === 2) {
return METHOD_pollEvery;
}
}
},
stopPolling: {
get: function() {
if (this.stage === 2) {
return METHOD_stopPolling;
}
}
},
updateSubsOnEvent: {
get: function() {
if (this.stage === 2) {
return METHOD_updateSubsOnEvent;
}
}
},
removeEvent: {
get: function() {
if (this.stage === 2) {
return METHOD_removeEvent;
}
}
},
throttle: {
get: function() {
if (this.stage) {
return METHOD_throttle;
}
}
},
setOption: {
get: function() {
if (this.stage === 2) {
return METHOD_setOption;
}
}
},
to: {
get: function() {
if (this.stage === 1) {
return genProxiedInterface(this);
}
}
},
toEvent: {
get: function() {
if (this.stage === 1) {
return genProxiedEventInterface(this);
}
}
},
and: {
get: function() {
var cloneBinding, cloneInterface;
cloneInterface = this.selfClone(1);
if (this.stage === 2) {
return cloneInterface;
} else if (this.stage === 1) {
if (!cloneInterface._.isMulti) {
cloneBinding = cloneInterface._;
cloneInterface._ = new GroupBinding(cloneInterface);
cloneInterface._.addBinding(cloneBinding);
}
return genProxiedInterface(cloneInterface, true);
}
}
},
once: {
get: function() {
var interfaceToReturn;
if (this.stage === 1) {
interfaceToReturn = this.selfClone(1);
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_ofEvent = function(eventName, customInMethod, customOutMethod) {
if (!eventName || !checkIf.isString(eventName)) {
throwErrorBadArg(eventName);
} else if (isNaN(parseInt(this.property))) {
throwWarning('badEventArg', 1);
}
this.eventName = eventName;
this.selector = this.property + '#' + this.eventName;
this.customEventMethod = {
'in': customInMethod,
'out': customOutMethod
};
return this;
};
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, this.placeholder);
return this;
};
METHOD_get = function() {
if (!this.placeholder || this._.isMulti) {
return this._.value;
} else {
return this._.pholderValues[this.placeholder];
}
};
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('transforms', this.subs.slice(-1), transformFn, this.options.updateOnBind);
return this;
};
METHOD_transformAll = function(transformFn) {
this._.addModifierFn('transforms', this.subs, transformFn, this.options.updateOnBind);
return this;
};
METHOD_condition = function(conditionFn) {
this._.addModifierFn('conditions', this.subs.slice(-1), conditionFn);
return this;
};
METHOD_conditionAll = function(conditionFn) {
this._.addModifierFn('conditions', this.subs, conditionFn);
return this;
};
METHOD_bothWays = function(altTransform) {
var binding, bindings, j, len, originCondition, originTransform, sub, subBinding, transformToUse;
sub = this.subs[this.subs.length - 1];
subBinding = sub._;
bindings = this._.isMulti ? this._.bindings : [this._];
subBinding.addSub(this._, sub.options);
for (j = 0, len = bindings.length; j < len; j++) {
binding = bindings[j];
originTransform = binding.transforms && binding.transforms[subBinding.ID];
originCondition = binding.conditions && binding.conditions[subBinding.ID];
if (originTransform || altTransform) {
transformToUse = checkIf.isFunction(altTransform) ? altTransform : originTransform;
if (transformToUse && altTransform !== false) {
subBinding.setModifierFn(this._.ID, 'transforms', transformToUse);
}
}
if (originCondition) {
subBinding.setModifierFn(this._.ID, 'conditions', originCondition);
}
}
return this;
};
METHOD_unBind = function(bothWays) {
var j, len, ref, sub;
ref = this.subs;
for (j = 0, len = ref.length; j < len; j++) {
sub = ref[j];
this._.removeSub(sub._, bothWays);
}
return this;
};
METHOD_pollEvery = function(time) {
this._.addPollInterval(time);
return this;
};
METHOD_stopPolling = function() {
this._.removePollInterval();
return this;
};
METHOD_updateSubsOnEvent = function(eventName, customMethod) {
this._.registerEvent(eventName, customMethod);
return this;
};
METHOD_removeEvent = function(eventName, customMethod) {
this._.unRegisterEvent(eventName, customMethod);
return this;
};
METHOD_throttle = function(delay) {
if (delay && checkIf.isNumber(delay)) {
this._.throttleRate = delay;
} else if (delay === false) {
delete this._.throttleRate;
}
return this;
};
METHOD_setOption = function(optionName, newValue) {
this._.subOpts[this.subs[this.subs.length - 1]._.ID][optionName] = newValue;
return this;
};
GroupBinding = function(bindingInterface, objects, objectType) {
var bindings, j, len, object;
extendState(this, this["interface"] = bindingInterface);
this.isMulti = true;
this.bindings = bindings = [];
if (objects) {
for (j = 0, len = objects.length; j < len; j++) {
object = objects[j];
this.addBinding(object, objectType);
}
}
return Object.defineProperties(this, {
'type': {
get: function() {
return bindings.map(function(binding) {
return binding.type;
});
}
},
'value': {
get: function() {
return bindings.map(function(binding) {
if (binding.placeholder) {
return binding.pholderValues[binding.placeholder];
} else {
return binding.value;
}
});
}
},
'throttleRate': {
set: function(newRate) {
return bindings.forEach(function(binding) {
return binding.throttleRate = newRate;
});
}
}
});
};
proto = GroupBinding.prototype = Object.create(BindingInterfacePrivate);
Object.keys(Binding.prototype).forEach(function(methodName) {
return proto[methodName] = function(a, b, c, d) {
var binding, j, len, ref;
ref = this.bindings;
for (j = 0, len = ref.length; j < len; j++) {
binding = ref[j];
if (methodName === 'updateSub') {
b = binding;
}
binding[methodName](a, b, c, d);
}
};
});
proto.addBinding = function(object, objectType) {
this.bindings.push(!objectType ? object : this.createBinding(object, objectType, this["interface"]));
};
return module.exports = SimplyBind;
})();