todomvc
Version:
> Helping you select an MV\* framework
1,371 lines (1,206 loc) • 34 kB
JavaScript
/**
* @license Emily <VERSION> http://flams.github.com/emily
*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
*/
/**
* Emily
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
define('Tools',[],
/**
* @class
* Tools is a collection of tools
*/
function Tools(){
return {
/**
* For applications that don't run in a browser, window is not the global object.
* This function returns the global object wherever the application runs.
* @returns {Object} the global object
*/
getGlobal: function getGlobal() {
var func = function() {
return this;
};
return func.call(null);
},
/**
* Mixes an object into another
* @param {Object} source object to get values from
* @param {Object} destination object to mix values into
* @param {Boolean} optional, set to true to prevent overriding
* @returns {Object} the destination object
*/
mixin: function mixin(source, destination, dontOverride) {
this.loop(source, function (value, idx) {
if (!destination[idx] || !dontOverride) {
destination[idx] = source[idx];
}
});
return destination;
},
/**
* Count the number of properties in an object
* It doesn't look up in the prototype chain
* @param {Object} object the object to count
* @returns {Number}
*/
count: function count(object) {
var nbItems = 0;
this.loop(object, function () {
nbItems++;
});
return nbItems;
},
/**
* Compares the properties of two objects and returns true if they're the same
* It's doesn't do it recursively
* @param {Object} first object
* @param {Object} second object
* @returns {Boolean} true if the two objets have the same properties
*/
compareObjects: function compareObjects(object1, object2) {
var getOwnProperties = function (object) {
return Object.getOwnPropertyNames(object).sort().join("");
};
return getOwnProperties(object1) == getOwnProperties(object2);
},
/**
* Compares two numbers and tells if the first one is bigger (1), smaller (-1) or equal (0)
* @param {Number} number1 the first number
* @param {Number} number2 the second number
* @returns 1 if number1>number2, -1 if number2>number1, 0 if equal
*/
compareNumbers: function compareNumbers(number1, number2) {
if (number1>number2) {
return 1;
} else if (number1<number2) {
return -1;
} else {
return 0;
}
},
/**
* Transform array-like objects to array, such as nodeLists or arguments
* @param {Array-like object}
* @returns {Array}
*/
toArray: function toArray(array) {
return [].slice.call(array);
},
/**
* Small adapter for looping over objects and arrays
* Warning: it's not meant to be used with nodeList
* To use with nodeList, convert to array first
* @param {Array/Object} iterated the array or object to loop through
* @param {Function} callback the function to execute for each iteration
* @param {Object} scope the scope in which to execute the callback
* @returns {Boolean} true if executed
*/
loop: function loop(iterated, callback, scope) {
var i,
length;
if (iterated instanceof Object && callback instanceof Function) {
if (iterated instanceof Array) {
for (i=0; i<iterated.length; i++) {
callback.call(scope, iterated[i], i, iterated);
}
} else {
for (i in iterated) {
if (iterated.hasOwnProperty(i)) {
callback.call(scope, iterated[i], i, iterated);
}
}
}
return true;
} else {
return false;
}
},
/**
* Make a diff between two objects
* @param {Array/Object} before is the object as it was before
* @param {Array/Object} after is what it is now
* @example:
* With objects:
*
* before = {a:1, b:2, c:3, d:4, f:6}
* after = {a:1, b:20, d: 4, e: 5}
* will return :
* {
* unchanged: ["a", "d"],
* updated: ["b"],
* deleted: ["f"],
* added: ["e"]
* }
*
* It also works with Arrays:
*
* before = [10, 20, 30]
* after = [15, 20]
* will return :
* {
* unchanged: [1],
* updated: [0],
* deleted: [2],
* added: []
* }
*
* @returns object
*/
objectsDiffs : function objectsDiffs(before, after) {
if (before instanceof Object && after instanceof Object) {
var unchanged = [],
updated = [],
deleted = [],
added = [];
// Look through the after object
this.loop(after, function (value, idx) {
// To get the added
if (typeof before[idx] == "undefined") {
added.push(idx);
// The updated
} else if (value !== before[idx]) {
updated.push(idx);
// And the unchanged
} else if (value === before[idx]) {
unchanged.push(idx);
}
});
// Loop through the before object
this.loop(before, function (value, idx) {
// To get the deleted
if (typeof after[idx] == "undefined") {
deleted.push(idx);
}
});
return {
updated: updated,
unchanged: unchanged,
added: added,
deleted: deleted
};
} else {
return false;
}
},
/**
* Transforms Arrays and Objects into valid JSON
* @param {Object/Array} object the object to JSONify
* @returns the JSONified object or false if failed
*/
jsonify: function jsonify(object) {
if (object instanceof Object) {
return JSON.parse(JSON.stringify(object));
} else {
return false;
}
},
/**
* Clone an Array or an Object
* @param {Array/Object} object the object to clone
* @returns {Array/Object} the cloned object
*/
clone: function clone(object) {
if (object instanceof Array) {
return object.slice(0);
} else if (typeof object == "object" && object !== null && !(object instanceof RegExp)) {
return this.mixin(object, {});
} else {
return false;
}
},
/**
*
*
*
*
* Refactoring needed for the following
*
*
*
*
*
*/
/**
* Get the property of an object nested in one or more objects
* given an object such as a.b.c.d = 5, getNestedProperty(a, "b.c.d") will return 5.
* @param {Object} object the object to get the property from
* @param {String} property the path to the property as a string
* @returns the object or the the property value if found
*/
getNestedProperty: function getNestedProperty(object, property) {
if (object && object instanceof Object) {
if (typeof property == "string" && property != "") {
var split = property.split(".");
return split.reduce(function (obj, prop) {
return obj && obj[prop];
}, object);
} else if (typeof property == "number") {
return object[property];
} else {
return object;
}
} else {
return object;
}
},
/**
* Set the property of an object nested in one or more objects
* If the property doesn't exist, it gets created.
* @param {Object} object
* @param {String} property
* @param value the value to set
* @returns object if no assignment was made or the value if the assignment was made
*/
setNestedProperty: function setNestedProperty(object, property, value) {
if (object && object instanceof Object) {
if (typeof property == "string" && property != "") {
var split = property.split(".");
return split.reduce(function (obj, prop, idx) {
obj[prop] = obj[prop] || {};
if (split.length == (idx + 1)) {
obj[prop] = value;
}
return obj[prop];
}, object);
} else if (typeof property == "number") {
object[property] = value;
return object[property];
} else {
return object;
}
} else {
return object;
}
}
};
});
/**
* Emily
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
define('Observable',["Tools"],
/**
* @class
* Observable is an implementation of the Observer design pattern,
* which is also known as publish/subscribe.
*
* This service creates an Observable on which you can add subscribers.
*/
function Observable(Tools) {
/**
* Defines the Observable
* @private
* @returns {_Observable}
*/
return function ObservableConstructor() {
/**
* The list of topics
* @private
*/
var _topics = {};
/**
* Add an observer
* @param {String} topic the topic to observe
* @param {Function} callback the callback to execute
* @param {Object} scope the scope in which to execute the callback
* @returns handle
*/
this.watch = function watch(topic, callback, scope) {
if (typeof callback == "function") {
var observers = _topics[topic] = _topics[topic] || [],
observer = [callback, scope];
observers.push(observer);
return [topic,observers.indexOf(observer)];
} else {
return false;
}
};
/**
* Remove an observer
* @param {Handle} handle returned by the watch method
* @returns {Boolean} true if there were subscribers
*/
this.unwatch = function unwatch(handle) {
var topic = handle[0], idx = handle[1];
if (_topics[topic] && _topics[topic][idx]) {
// delete value so the indexes don't move
delete _topics[topic][idx];
// If the topic is only set with falsy values, delete it;
if (!_topics[topic].some(function (value) {
return !!value;
})) {
delete _topics[topic];
}
return true;
} else {
return false;
}
};
/**
* Notifies observers that a topic has a new message
* @param {String} topic the name of the topic to publish to
* @param subject
* @returns {Boolean} true if there was subscribers
*/
this.notify = function notify(topic) {
var observers = _topics[topic],
args = Tools.toArray(arguments).slice(1);
if (observers) {
Tools.loop(observers, function (value) {
try {
value && value[0].apply(value[1] || null, args);
} catch (err) { }
});
return true;
} else {
return false;
}
},
/**
* Check if topic has the described observer
* @param {Handle}
* @returns {Boolean} true if exists
*/
this.hasObserver = function hasObserver(handle) {
return !!( handle && _topics[handle[0]] && _topics[handle[0]][handle[1]]);
};
/**
* Check if a topic has observers
* @param {String} topic the name of the topic
* @returns {Boolean} true if topic is listened
*/
this.hasTopic = function hasTopic(topic) {
return !!_topics[topic];
};
/**
* Unwatch all or unwatch all from topic
* @param {String} topic optional unwatch all from topic
* @returns {Boolean} true if ok
*/
this.unwatchAll = function unwatchAll(topic) {
if (_topics[topic]) {
delete _topics[topic];
} else {
_topics = {};
}
return true;
};
};
});
/**
* Emily
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
define('StateMachine',["Tools"],
/**
* @class
* Create a stateMachine
*/
function StateMachine(Tools) {
/**
* @param initState {String} the initial state
* @param diagram {Object} the diagram that describes the state machine
* @example
*
* diagram = {
* "State1" : [
* [ message1, action, nextState], // Same as the state's add function
* [ message2, action2, nextState]
* ],
*
* "State2" :
* [ message3, action3, scope3, nextState]
* ... and so on ....
*
* }
*
* @return the stateMachine object
*/
function StateMachineConstructor($initState, $diagram) {
/**
* The list of states
* @private
*/
var _states = {},
/**
* The current state
* @private
*/
_currentState = "";
/**
* Set the initialization state
* @param {String} name the name of the init state
* @returns {Boolean}
*/
this.init = function init(name) {
if (_states[name]) {
_currentState = name;
return true;
} else {
return false;
}
};
/**
* Add a new state
* @private
* @param {String} name the name of the state
* @returns {State} a new state
*/
this.add = function add(name) {
if (!_states[name]) {
return _states[name] = new Transition();
} else {
return _states[name];
}
};
/**
* Get an existing state
* @private
* @param {String} name the name of the state
* @returns {State} the state
*/
this.get = function get(name) {
return _states[name];
};
/**
* Get the current state
* @returns {String}
*/
this.getCurrent = function getCurrent() {
return _currentState;
};
/**
* Tell if the state machine has the given state
* @param {String} state the name of the state
* @returns {Boolean} true if it has the given state
*/
this.has = function has(state) {
return _states.hasOwnProperty(state);
};
/**
* Advances the state machine to a given state
* @param {String} state the name of the state to advance the state machine to
* @returns {Boolean} true if it has the given state
*/
this.advance = function advance(state) {
if (this.has(state)) {
_currentState = state;
return true;
} else {
return false;
}
};
/**
* Pass an event to the state machine
* @param {String} name the name of the event
* @returns {Boolean} true if the event exists in the current state
*/
this.event = function event(name) {
var nextState;
nextState = _states[_currentState].event.apply(_states[_currentState].event, Tools.toArray(arguments));
// False means that there's no such event
// But undefined means that the state doesn't change
if (nextState === false) {
return false;
} else {
// There could be no next state, so the current one remains
if (nextState) {
// Call the exit action if any
_states[_currentState].event("exit");
_currentState = nextState;
// Call the new state's entry action if any
_states[_currentState].event("entry");
}
return true;
}
};
/**
* Initializes the StateMachine with the given diagram
*/
Tools.loop($diagram, function (transition, state) {
var myState = this.add(state);
transition.forEach(function (params){
myState.add.apply(null, params);
});
}, this);
/**
* Sets its initial state
*/
this.init($initState);
}
/**
* Each state has associated transitions
* @constructor
*/
function Transition() {
/**
* The list of transitions associated to a state
* @private
*/
var _transitions = {};
/**
* Add a new transition
* @private
* @param {String} event the event that will trigger the transition
* @param {Function} action the function that is executed
* @param {Object} scope [optional] the scope in which to execute the action
* @param {String} next [optional] the name of the state to transit to.
* @returns {Boolean} true if success, false if the transition already exists
*/
this.add = function add(event, action, scope, next) {
var arr = [];
if (_transitions[event]) {
return false;
}
if (typeof event == "string"
&& typeof action == "function") {
arr[0] = action;
if (typeof scope == "object") {
arr[1] = scope;
}
if (typeof scope == "string") {
arr[2] = scope;
}
if (typeof next == "string") {
arr[2] = next;
}
_transitions[event] = arr;
return true;
}
return false;
};
/**
* Check if a transition can be triggered with given event
* @private
* @param {String} event the name of the event
* @returns {Boolean} true if exists
*/
this.has = function has(event) {
return !!_transitions[event];
};
/**
* Get a transition from it's event
* @private
* @param {String} event the name of the event
* @return the transition
*/
this.get = function get(event) {
return _transitions[event] || false;
};
/**
* Execute the action associated to the given event
* @param {String} event the name of the event
* @param {params} params to pass to the action
* @private
* @returns false if error, the next state or undefined if success (that sounds weird)
*/
this.event = function event(event) {
var _transition = _transitions[event];
if (_transition) {
_transition[0].apply(_transition[1], Tools.toArray(arguments).slice(1));
return _transition[2];
} else {
return false;
}
};
};
return StateMachineConstructor;
});
/**
* Emily
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
define('Promise',["Observable", "StateMachine"],
/**
* @class
* Create a promise/A+
*/
function Promise(Observable, StateMachine) {
return function PromiseConstructor() {
/**
* The fulfilled value
* @private
*/
var _value = null,
/**
* The rejection reason
* @private
*/
_reason = null,
/**
* The funky observable
* @private
*/
_observable = new Observable,
/**
* The state machine States & transitions
* @private
*/
_states = {
// The promise is pending
"Pending": [
// It can only be fulfilled when pending
["fulfill", function onFulfill(value) {
_value = value;
_observable.notify("fulfill", value);
// Then it transits to the fulfilled state
}, "Fulfilled"],
// it can only be rejected when pending
["reject", function onReject(reason) {
_reason = reason;
_observable.notify("reject", reason);
// Then it transits to the rejected state
}, "Rejected"],
// When pending, add the resolver to an observable
["toFulfill", function toFulfill(resolver) {
_observable.watch("fulfill", resolver);
}],
// When pending, add the resolver to an observable
["toReject", function toReject(resolver) {
_observable.watch("reject", resolver);
}]],
// When fulfilled,
"Fulfilled": [
// We directly call the resolver with the value
["toFulfill", function toFulfill(resolver) {
setTimeout(function () {
resolver(_value);
}, 0);
}]],
// When rejected
"Rejected": [
// We directly call the resolver with the reason
["toReject", function toReject(resolver) {
setTimeout(function () {
resolver(_reason);
}, 0);
}]]
},
/**
* The stateMachine
* @private
*/
_stateMachine = new StateMachine("Pending", _states);
/**
* Fulfilled the promise.
* A promise can be fulfilld only once.
* @param the fulfillment value
* @returns the promise
*/
this.fulfill = function fulfill(value) {
_stateMachine.event("fulfill", value);
return this;
};
/**
* Reject the promise.
* A promise can be rejected only once.
* @param the rejection value
* @returns true if the rejection function was called
*/
this.reject = function reject(reason) {
_stateMachine.event("reject", reason);
return this;
};
/**
* The callbacks to call after fulfillment or rejection
* @param {Function} fulfillmentCallback the first parameter is a success function, it can be followed by a scope
* @param {Function} the second, or third parameter is the rejection callback, it can also be followed by a scope
* @examples:
*
* then(fulfillment)
* then(fulfillment, scope, rejection, scope)
* then(fulfillment, rejection)
* then(fulfillment, rejection, scope)
* then(null, rejection, scope)
* @returns {Promise} the new promise
*/
this.then = function then() {
var promise = new PromiseConstructor;
// If a fulfillment callback is given
if (arguments[0] instanceof Function) {
// If the second argument is also a function, then no scope is given
if (arguments[1] instanceof Function) {
_stateMachine.event("toFulfill", this.makeResolver(promise, arguments[0]));
} else {
// If the second argument is not a function, it's the scope
_stateMachine.event("toFulfill", this.makeResolver(promise, arguments[0], arguments[1]));
}
} else {
// If no fulfillment callback given, give a default one
_stateMachine.event("toFulfill", this.makeResolver(promise, function () {
promise.fulfill(_value);
}));
}
// if the second arguments is a callback, it's the rejection one, and the next argument is the scope
if (arguments[1] instanceof Function) {
_stateMachine.event("toReject", this.makeResolver(promise, arguments[1], arguments[2]));
}
// if the third arguments is a callback, it's the rejection one, and the next arguments is the sopce
if (arguments[2] instanceof Function) {
_stateMachine.event("toReject", this.makeResolver(promise, arguments[2], arguments[3]));
}
// If no rejection callback is given, give a default one
if (!(arguments[1] instanceof Function) &&
!(arguments[2] instanceof Function)) {
_stateMachine.event("toReject", this.makeResolver(promise, function () {
promise.reject(_reason);
}));
}
return promise;
};
/**
* Synchronize this promise with a thenable
* @returns {Boolean} false if the given sync is not a thenable
*/
this.sync = function sync(syncWith) {
if (syncWith instanceof Object && syncWith.then) {
var onFulfilled = function onFulfilled(value) {
this.fulfill(value);
},
onRejected = function onRejected(reason) {
this.reject(reason);
};
syncWith.then(onFulfilled.bind(this),
onRejected.bind(this));
return true;
} else {
return false;
}
};
/**
* Make a resolver
* for debugging only
* @private
* @returns {Function} a closure
*/
this.makeResolver = function makeResolver(promise, func, scope) {
return function resolver(value) {
var returnedPromise;
try {
returnedPromise = func.call(scope, value);
if (!promise.sync(returnedPromise)) {
promise.fulfill(returnedPromise);
}
} catch (err) {
promise.reject(err);
}
}
};
/**
* Returns the reason
* for debugging only
* @private
*/
this.getReason = function getReason() {
return _reason;
};
/**
* Returns the reason
* for debugging only
* @private
*/
this.getValue = function getValue() {
return _value;
};
/**
* Get the promise's observable
* for debugging only
* @private
* @returns {Observable}
*/
this.getObservable = function getObservable() {
return _observable;
};
/**
* Get the promise's stateMachine
* for debugging only
* @private
* @returns {StateMachine}
*/
this.getStateMachine = function getStateMachine() {
return _stateMachine;
};
/**
* Get the statesMachine's states
* for debugging only
* @private
* @returns {Object}
*/
this.getStates = function getStates() {
return _states;
};
}
});
/**
* Emily
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
define('Store',["Observable", "Tools"],
/**
* @class
* Store creates an observable structure based on a key/values object
* or on an array
*/
function Store(Observable, Tools) {
/**
* Defines the Store
* @param {Array/Object} the data to initialize the store with
* @returns
*/
return function StoreConstructor($data) {
/**
* Where the data is stored
* @private
*/
var _data = Tools.clone($data) || {},
/**
* The observable for publishing changes on the store iself
* @private
*/
_storeObservable = new Observable(),
/**
* The observable for publishing changes on a value
* @private
*/
_valueObservable = new Observable(),
/**
* Gets the difference between two objects and notifies them
* @private
* @param {Object} previousData
*/
_notifyDiffs = function _notifyDiffs(previousData) {
var diffs = Tools.objectsDiffs(previousData, _data);
["updated",
"deleted",
"added"].forEach(function (value) {
diffs[value].forEach(function (dataIndex) {
_storeObservable.notify(value, dataIndex, _data[dataIndex]);
_valueObservable.notify(dataIndex, _data[dataIndex], value);
});
});
};
/**
* Get the number of items in the store
* @returns {Number} the number of items in the store
*/
this.getNbItems = function() {
return _data instanceof Array ? _data.length : Tools.count(_data);
};
/**
* Count is an alias for getNbItems
* @returns {Number} the number of items in the store
*/
this.count = this.getNbItems;
/**
* Get a value from its index
* @param {String} name the name of the index
* @returns the value
*/
this.get = function get(name) {
return _data[name];
};
/**
* Checks if the store has a given value
* @param {String} name the name of the index
* @returns {Boolean} true if the value exists
*/
this.has = function has(name) {
return _data.hasOwnProperty(name);
};
/**
* Set a new value and overrides an existing one
* @param {String} name the name of the index
* @param value the value to assign
* @returns true if value is set
*/
this.set = function set(name, value) {
var hasPrevious,
previousValue,
action;
if (typeof name != "undefined") {
hasPrevious = this.has(name);
previousValue = this.get(name);
_data[name] = value;
action = hasPrevious ? "updated" : "added";
_storeObservable.notify(action, name, _data[name], previousValue);
_valueObservable.notify(name, _data[name], action, previousValue);
return true;
} else {
return false;
}
};
/**
* Update the property of an item.
* @param {String} name the name of the index
* @param {String} property the property to modify.
* @param value the value to assign
* @returns false if the Store has no name index
*/
this.update = function update(name, property, value) {
var item;
if (this.has(name)) {
item = this.get(name);
Tools.setNestedProperty(item, property, value);
_storeObservable.notify("updated", property, value);
_valueObservable.notify(name, item, "updated");
return true;
} else {
return false;
}
};
/**
* Delete value from its index
* @param {String} name the name of the index from which to delete the value
* @returns true if successfully deleted.
*/
this.del = function del(name) {
if (this.has(name)) {
if (!this.alter("splice", name, 1)) {
delete _data[name];
_storeObservable.notify("deleted", name);
_valueObservable.notify(name, _data[name], "deleted");
}
return true;
} else {
return false;
}
};
/**
* Delete multiple indexes. Prefer this one over multiple del calls.
* @param {Array}
* @returns false if param is not an array.
*/
this.delAll = function delAll(indexes) {
if (indexes instanceof Array) {
// Indexes must be removed from the greatest to the lowest
// To avoid trying to remove indexes that don't exist.
// i.e: given [0, 1, 2], remove 1, then 2, 2 doesn't exist anymore
indexes.sort(Tools.compareNumbers)
.reverse()
.forEach(this.del, this);
return true;
} else {
return false;
}
};
/**
* Alter the data be calling one of it's method
* When the modifications are done, it notifies on changes.
* @param {String} func the name of the method
* @returns the result of the method call
*/
this.alter = function alter(func) {
var apply,
previousData;
if (_data[func]) {
previousData = Tools.clone(_data);
apply = _data[func].apply(_data, Array.prototype.slice.call(arguments, 1));
_notifyDiffs(previousData);
return apply;
} else {
return false;
}
};
/**
* proxy is an alias for alter
*/
this.proxy = this.alter;
/**
* Watch the store's modifications
* @param {String} added/updated/deleted
* @param {Function} func the function to execute
* @param {Object} scope the scope in which to execute the function
* @returns {Handle} the subscribe's handler to use to stop watching
*/
this.watch = function watch(name, func, scope) {
return _storeObservable.watch(name, func, scope);
};
/**
* Unwatch the store modifications
* @param {Handle} handle the handler returned by the watch function
* @returns
*/
this.unwatch = function unwatch(handle) {
return _storeObservable.unwatch(handle);
};
/**
* Get the observable used for watching store's modifications
* Should be used only for debugging
* @returns {Observable} the Observable
*/
this.getStoreObservable = function getStoreObservable() {
return _storeObservable;
};
/**
* Watch a value's modifications
* @param {String} name the name of the value to watch for
* @param {Function} func the function to execute
* @param {Object} scope the scope in which to execute the function
* @returns handler to pass to unwatchValue
*/
this.watchValue = function watchValue(name, func, scope) {
return _valueObservable.watch(name, func, scope);
};
/**
* Unwatch the value's modifications
* @param {Handler} handler the handler returned by the watchValue function
* @private
* @returns true if unwatched
*/
this.unwatchValue = function unwatchValue(handler) {
return _valueObservable.unwatch(handler);
};
/**
* Get the observable used for watching value's modifications
* Should be used only for debugging
* @private
* @returns {Observable} the Observable
*/
this.getValueObservable = function getValueObservable() {
return _valueObservable;
};
/**
* Loop through the data
* @param {Function} func the function to execute on each data
* @param {Object} scope the scope in wich to run the callback
*/
this.loop = function loop(func, scope) {
Tools.loop(_data, func, scope);
};
/**
* Reset all data and get notifications on changes
* @param {Arra/Object} data the new data
* @returns {Boolean}
*/
this.reset = function reset(data) {
if (data instanceof Object) {
var previousData = Tools.clone(_data);
_data = Tools.clone(data) || {};
_notifyDiffs(previousData);
return true;
} else {
return false;
}
};
/**
* Returns a JSON version of the data
* Use dump if you want all the data as a plain js object
* @returns {String} the JSON
*/
this.toJSON = function toJSON() {
return JSON.stringify(_data);
};
/**
* Returns the store's data
* @returns {Object} the data
*/
this.dump = function dump() {
return _data;
};
};
});
/**
* Emily
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
define('Transport',[],
/**
* @class
* Transport hides and centralizes the logic behind requests.
* It can issue requests to request handlers, which in turn can issue requests
* to anything your node.js server has access to (HTTP, FileSystem, SIP...)
*/
function Transport() {
/**
* Create a Transport
* @param {Emily Store} [optionanl] $reqHandlers an object containing the request handlers
* @returns
*/
return function TransportConstructor($reqHandlers) {
/**
* The request handlers
* @private
*/
var _reqHandlers = null;
/**
* Set the requests handlers object
* @param {Emily Store} reqHandlers an object containing the requests handlers
* @returns
*/
this.setReqHandlers = function setReqHandlers(reqHandlers) {
if (reqHandlers instanceof Object) {
_reqHandlers = reqHandlers;
return true;
} else {
return false;
}
};
/**
* Get the requests handlers
* @returns{ Emily Store} reqHandlers the object containing the requests handlers
*/
this.getReqHandlers = function getReqHandlers() {
return _reqHandlers;
};
/**
* Issue a request to a request handler
* @param {String} reqHandler the name of the request handler to issue the request to
* @param {Object} data the data, or payload, to send to the request handler
* @param {Function} callback the function to execute with the result
* @param {Object} scope the scope in which to execute the callback
* @returns
*/
this.request = function request(reqHandler, data, callback, scope) {
if (_reqHandlers.has(reqHandler)
&& typeof data != "undefined") {
_reqHandlers.get(reqHandler)(data, function () {
callback && callback.apply(scope, arguments);
});
return true;
} else {
return false;
}
};
/**
* Issue a request to a reqHandler but keep listening for the response as it can be sent in several chunks
* or remain open as long as the abort funciton is not called
* @param {String} reqHandler the name of the request handler to issue the request to
* @param {Object} data the data, or payload, to send to the request handler
* @param {Function} callback the function to execute with the result
* @param {Object} scope the scope in which to execute the callback
* @returns {Function} the abort function to call to stop listening
*/
this.listen = function listen(reqHandler, data, callback, scope) {
if (_reqHandlers.has(reqHandler)
&& typeof data != "undefined"
&& typeof callback == "function") {
var func = function () {
callback.apply(scope, arguments);
},
abort;
abort = _reqHandlers.get(reqHandler)(data, func, func);
return function () {
if (typeof abort == "function") {
abort();
} else if (typeof abort == "object" && typeof abort.func == "function") {
abort.func.call(abort.scope);
}
};
} else {
return false;
}
};
this.setReqHandlers($reqHandlers);
};
});