Commit 89c41418 authored by Pascal Hartig's avatar Pascal Hartig

Merge pull request #1075 from podefr/master

fix #1071: Olives: Update Olives and Emily to latest 1.x versions
parents b9c3b488 9df98e41
......@@ -2,8 +2,8 @@
"name": "todomvc-olives",
"version": "0.0.0",
"dependencies": {
"olives": "~1.4.0",
"emily": "~1.3.5",
"olives": "~1.6.0",
"emily": "~1.8.1",
"requirejs": "~2.1.5",
"todomvc-common": "~0.3.0"
}
......
......@@ -7,7 +7,7 @@
*/
/**
* Emily
* Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
......@@ -19,303 +19,372 @@ define('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;
},
/**
* Get the closest number in an array
* @param {Number} item the base number
* @param {Array} array the array to search into
* @param {Function} getDiff returns the difference between the base number and
* and the currently read item in the array. The item which returned the smallest difference wins.
* @private
*/
function _getClosest(item, array, getDiff) {
var closest,
diff;
/**
* 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++;
});
if (!array) {
return;
}
return nbItems;
},
array.forEach(function (comparedItem, comparedItemIndex) {
var thisDiff = getDiff(comparedItem, item);
/**
* 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);
},
if (thisDiff >= 0 && (typeof diff == "undefined" || thisDiff < diff)) {
diff = thisDiff;
closest = comparedItemIndex;
}
});
/**
* 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;
}
},
return closest;
}
/**
* 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);
},
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);
},
/**
* 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;
}
},
/**
* 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;
},
/**
* 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
};
/**
* 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++;
});
} else {
return false;
}
},
return nbItems;
},
/**
* 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;
}
},
/**
* 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);
},
/**
* 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;
}
},
/**
* 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);
},
/**
*
*
*
*
* Refactoring needed for the following
*
*
*
*
*
*/
/**
* 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;
}
},
/**
* 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;
}
},
/**
* 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;
}
},
/**
* 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;
}
}
/**
* 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;
}
},
/**
* Get the closest number in an array given a base number
* Example: closest(30, [20, 0, 50, 29]) will return 3 as 29 is the closest item
* @param {Number} item the base number
* @param {Array} array the array of numbers to search into
* @returns {Number} the index of the closest item in the array
*/
closest: function closest(item, array) {
return _getClosest(item, array, function (comparedItem, item) {
return Math.abs(comparedItem - item);
});
},
/**
* Get the closest greater number in an array given a base number
* Example: closest(30, [20, 0, 50, 29]) will return 2 as 50 is the closest greater item
* @param {Number} item the base number
* @param {Array} array the array of numbers to search into
* @returns {Number} the index of the closest item in the array
*/
closestGreater: function closestGreater(item, array) {
return _getClosest(item, array, function (comparedItem, item) {
return comparedItem - item;
});
},
/**
* Get the closest lower number in an array given a base number
* Example: closest(30, [20, 0, 50, 29]) will return 0 as 20 is the closest lower item
* @param {Number} item the base number
* @param {Array} array the array of numbers to search into
* @returns {Number} the index of the closest item in the array
*/
closestLower: function closestLower(item, array) {
return _getClosest(item, array, function (comparedItem, item) {
return item - comparedItem;
});
}
};
});
/**
* Emily
* Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
......@@ -330,6 +399,8 @@ define('Observable',["Tools"],
*/
function Observable(Tools) {
/**
* Defines the Observable
* @private
......@@ -363,6 +434,21 @@ function Observable(Tools) {
}
};
/**
* Listen to an event just once before removing the handler
* @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.once = function once(topic, callback, scope) {
var handle = this.watch(topic, function () {
callback.apply(scope, arguments);
this.unwatch(handle);
}, this);
return handle;
};
/**
* Remove an observer
* @param {Handle} handle returned by the watch method
......@@ -398,14 +484,16 @@ function Observable(Tools) {
if (observers) {
Tools.loop(observers, function (value) {
try {
value && value[0].apply(value[1] || null, args);
if (value) {
value[0].apply(value[1] || null, args);
}
} catch (err) { }
});
return true;
} else {
return false;
}
},
};
/**
* Check if topic has the described observer
......@@ -444,7 +532,7 @@ function Observable(Tools) {
});
/**
* Emily
* Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
......@@ -456,7 +544,9 @@ define('StateMachine',["Tools"],
*/
function StateMachine(Tools) {
/**
/**
* @param initState {String} the initial state
* @param diagram {Object} the diagram that describes the state machine
* @example
......@@ -475,228 +565,229 @@ function StateMachine(Tools) {
*
* @return the stateMachine object
*/
function StateMachineConstructor($initState, $diagram) {
/**
* The list of states
* @private
*/
var _states = {},
function StateMachineConstructor($initState, $diagram) {
/**
* The current state
* @private
*/
_currentState = "";
/**
* The list of states
* @private
*/
var _states = {},
/**
* 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;
}
};
/**
* The current state
* @private
*/
_currentState = "";
/**
* 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];
}
};
/**
* 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;
}
};
/**
* 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];
};
/**
* 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]) {
var transition = _states[name] = new Transition();
return transition;
} else {
return _states[name];
}
};
/**
* Get the current state
* @returns {String}
*/
this.getCurrent = function getCurrent() {
return _currentState;
};
/**
* 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];
};
/**
* 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);
};
/**
* Get the current state
* @returns {String}
*/
this.getCurrent = function getCurrent() {
return _currentState;
};
/**
* 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;
}
};
/**
* 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);
};
/**
* 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;
/**
* 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;
}
};
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;
}
};
/**
* 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);
/**
* 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);
}
/**
* Sets its initial state
*/
this.init($initState);
}
/**
* Each state has associated transitions
/**
* Each state has associated transitions
* @constructor
*/
function Transition() {
*/
function Transition() {
/**
* The list of transitions associated to a state
* @private
*/
var _transitions = {};
/**
* 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) {
/**
* 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 = [];
var arr = [];
if (_transitions[event]) {
return false;
}
if (_transitions[event]) {
return false;
}
if (typeof event == "string"
&& typeof action == "function") {
if (typeof event == "string" &&
typeof action == "function") {
arr[0] = action;
arr[0] = action;
if (typeof scope == "object") {
arr[1] = scope;
}
if (typeof scope == "object") {
arr[1] = scope;
}
if (typeof scope == "string") {
arr[2] = scope;
}
if (typeof scope == "string") {
arr[2] = scope;
}
if (typeof next == "string") {
arr[2] = next;
}
if (typeof next == "string") {
arr[2] = next;
}
_transitions[event] = arr;
return true;
}
_transitions[event] = arr;
return true;
}
return false;
};
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];
};
/**
* 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;
};
/**
* 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;
}
};
};
/**
* 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(newEvent) {
var _transition = _transitions[newEvent];
if (_transition) {
_transition[0].apply(_transition[1], Tools.toArray(arguments).slice(1));
return _transition[2];
} else {
return false;
}
};
}
return StateMachineConstructor;
return StateMachineConstructor;
});
/**
* Emily
* Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
......@@ -708,107 +799,109 @@ define('Promise',["Observable", "StateMachine"],
*/
function Promise(Observable, StateMachine) {
return function PromiseConstructor() {
/**
* The fulfilled value
* @private
*/
var _value = null,
/**
* The rejection reason
* @private
*/
_reason = null,
return function PromiseConstructor() {
/**
* The funky observable
* @private
*/
_observable = new Observable,
/**
* The fulfilled value
* @private
*/
var _value = null,
/**
* 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 rejection reason
* @private
*/
_reason = null,
/**
* The stateMachine
* @private
*/
_stateMachine = new StateMachine("Pending", _states);
/**
* The funky observable
* @private
*/
_observable = new Observable(),
/**
* 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;
};
/**
* 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);
}]]
},
/**
* 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 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
......@@ -822,88 +915,88 @@ function Promise(Observable, StateMachine) {
* @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) {
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);
}));
}
// 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;
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;
}
};
* 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);
}
}
return function resolver(value) {
var returnedPromise;
try {
returnedPromise = func.call(scope, value);
if (!promise.sync(returnedPromise)) {
promise.fulfill(returnedPromise);
}
} catch (err) {
promise.reject(err);
}
};
};
/**
......@@ -912,7 +1005,7 @@ function Promise(Observable, StateMachine) {
* @private
*/
this.getReason = function getReason() {
return _reason;
return _reason;
};
/**
......@@ -921,40 +1014,40 @@ function Promise(Observable, StateMachine) {
* @private
*/
this.getValue = function getValue() {
return _value;
return _value;
};
/**
* Get the promise's observable
* for debugging only
* @private
* @returns {Observable}
*/
this.getObservable = function getObservable() {
return _observable;
};
/**
* 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 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;
};
/**
* Get the statesMachine's states
* for debugging only
* @private
* @returns {Object}
*/
this.getStates = function getStates() {
return _states;
};
}
};
......@@ -962,7 +1055,7 @@ function Promise(Observable, StateMachine) {
});
/**
* Emily
* Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
......@@ -975,297 +1068,379 @@ define('Store',["Observable", "Tools"],
*/
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) || {},
/**
* Defines the Store
* @param {Array/Object} the data to initialize the store with
* @returns
*/
return function StoreConstructor($data) {
/**
* The observable for publishing changes on the store iself
* @private
*/
_storeObservable = new Observable(),
/**
* Where the data is stored
* @private
*/
var _data = Tools.clone($data) || {},
/**
* The observable for publishing changes on a value
* @private
*/
_valueObservable = new Observable(),
/**
* The observable for publishing changes on the store iself
* @private
*/
_storeObservable = 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);
});
});
};
/**
* The observable for publishing changes on a value
* @private
*/
_valueObservable = new Observable(),
/**
* 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);
};
/**
* Saves the handles for the subscriptions of the computed properties
* @private
*/
_computed = [],
/**
* Count is an alias for getNbItems
* @returns {Number} the number of items in the store
*/
this.count = this.getNbItems;
/**
* 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 a value from its index
* @param {String} name the name of the index
* @returns the value
*/
this.get = function get(name) {
return _data[name];
};
/**
* 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);
};
/**
* 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);
};
/**
* Count is an alias for getNbItems
* @returns {Number} the number of items in the store
*/
this.count = this.getNbItems;
/**
* 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;
}
};
/**
* 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];
};
/**
* 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;
}
};
/**
* 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);
};
/**
* 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;
}
};
/**
* 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;
}
};
/**
* 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;
}
};
/**
* 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;
}
};
/**
* 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;
}
};
/**
* 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;
}
};
/**
* proxy is an alias for alter
*/
this.proxy = this.alter;
/**
* 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;
}
};
/**
* 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);
};
/**
* Alter the data by calling one of it's method
* When the modifications are done, it notifies on changes.
* If the function called doesn't alter the data, consider using proxy instead
* which is much, much faster.
* @param {String} func the name of the method
* @params {*} any number of params to be given to the func
* @returns the result of the method call
*/
this.alter = function alter(func) {
var apply,
previousData;
if (_data[func]) {
previousData = Tools.clone(_data);
apply = this.proxy.apply(this, arguments);
_notifyDiffs(previousData);
_storeObservable.notify("altered", _data, previousData);
return apply;
} else {
return false;
}
};
/**
* Unwatch the store modifications
* @param {Handle} handle the handler returned by the watch function
* @returns
*/
this.unwatch = function unwatch(handle) {
return _storeObservable.unwatch(handle);
};
/**
* Proxy is similar to alter but doesn't trigger events.
* It's preferable to call proxy for functions that don't
* update the interal data source, like slice or filter.
* @param {String} func the name of the method
* @params {*} any number of params to be given to the func
* @returns the result of the method call
*/
this.proxy = function proxy(func) {
if (_data[func]) {
return _data[func].apply(_data, Array.prototype.slice.call(arguments, 1));
} else {
return false;
}
};
/**
* 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 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);
};
/**
* 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 store modifications
* @param {Handle} handle the handler returned by the watch function
* @returns
*/
this.unwatch = function unwatch(handle) {
return _storeObservable.unwatch(handle);
};
/**
* 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 store's modifications
* Should be used only for debugging
* @returns {Observable} the Observable
*/
this.getStoreObservable = function getStoreObservable() {
return _storeObservable;
};
/**
* 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;
};
/**
* 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);
};
/**
* 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);
};
/**
* 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);
};
/**
* 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;
}
/**
* 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);
};
/**
* 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);
};
/**
* 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);
_storeObservable.notify("resetted", _data, previousData);
return true;
} else {
return false;
}
/**
* Returns the store's data
* @returns {Object} the data
*/
this.dump = function dump() {
return _data;
};
};
};
/**
* Compute a new property from other properties.
* The computed property will look exactly similar to any none
* computed property, it can be watched upon.
* @param {String} name the name of the computed property
* @param {Array} computeFrom a list of properties to compute from
* @param {Function} callback the callback to compute the property
* @param {Object} scope the scope in which to execute the callback
* @returns {Boolean} false if wrong params given to the function
*/
this.compute = function compute(name, computeFrom, callback, scope) {
var args = [];
if (typeof name == "string" &&
typeof computeFrom == "object" &&
typeof callback == "function" &&
!this.isCompute(name)) {
_computed[name] = [];
Tools.loop(computeFrom, function (property) {
_computed[name].push(this.watchValue(property, function () {
this.set(name, callback.call(scope));
}, this));
}, this);
this.set(name, callback.call(scope));
return true;
} else {
return false;
}
};
/**
* Remove a computed property
* @param {String} name the name of the computed to remove
* @returns {Boolean} true if the property is removed
*/
this.removeCompute = function removeCompute(name) {
if (this.isCompute(name)) {
Tools.loop(_computed[name], function (handle) {
this.unwatchValue(handle);
}, this);
this.del(name);
return true;
} else {
return false;
}
};
/**
* Tells if a property is a computed property
* @param {String} name the name of the property to test
* @returns {Boolean} true if it's a computed property
*/
this.isCompute = function isCompute(name) {
return !!_computed[name];
};
/**
* 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
* Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
define('Transport',[],
/**
* @class
......@@ -1275,96 +1450,339 @@ define('Transport',[],
*/
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;
/**
* Create a Transport
* @param {Emily Store} [optionanl] $reqHandlers an object containing the request handlers
* @returns
*/
return function TransportConstructor($reqHandlers) {
/**
* 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;
}
};
/**
* The request handlers
* @private
*/
var _reqHandlers = null;
/**
* Get the requests handlers
* @returns{ Emily Store} reqHandlers the object containing the requests handlers
*/
this.getReqHandlers = function getReqHandlers() {
return _reqHandlers;
};
/**
* 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;
}
};
/**
* 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") {
/**
* Get the requests handlers
* @returns{ Emily Store} reqHandlers the object containing the requests handlers
*/
this.getReqHandlers = function getReqHandlers() {
return _reqHandlers;
};
_reqHandlers.get(reqHandler)(data, function () {
callback && callback.apply(scope, arguments);
});
return true;
} else {
return false;
}
};
/**
* 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 () {
if (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;
}
};
/**
* 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);
this.setReqHandlers($reqHandlers);
};
};
});
/**
* Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed
*/
define('Router',["Observable", "Store", "Tools"],
/**
* @class
* Routing allows for navigating in an application by defining routes.
*/
function Router(Observable, Store, Tools) {
return function RouterConstructor() {
/**
* The routes observable (the applications use it)
* @private
*/
var _routes = new Observable(),
/**
* The events observable (used by Routing)
* @private
*/
_events = new Observable(),
/**
* The routing history
* @private
*/
_history = new Store([]),
/**
* For navigating through the history, remembers the current position
* @private
*/
_currentPos = -1,
/**
* The depth of the history
* @private
*/
_maxHistory = 10;
/**
* Only for debugging
* @private
*/
this.getRoutesObservable = function getRoutesObservable() {
return _routes;
};
/**
* Only for debugging
* @private
*/
this.getEventsObservable = function getEventsObservable() {
return _events;
};
/**
* Set the maximum length of history
* As the user navigates through the application, the
* routeur keeps track of the history. Set the depth of the history
* depending on your need and the amount of memory that you can allocate it
* @param {Number} maxHistory the depth of history
* @returns {Boolean} true if maxHistory is equal or greater than 0
*/
this.setMaxHistory = function setMaxHistory(maxHistory) {
if (maxHistory >= 0) {
_maxHistory = maxHistory;
return true;
} else {
return false;
}
};
/**
* Get the current max history setting
* @returns {Number} the depth of history
*/
this.getMaxHistory = function getMaxHistory() {
return _maxHistory;
};
/**
* Set a new route
* @param {String} route the name of the route
* @param {Function} func the function to be execute when navigating to the route
* @param {Object} scope the scope in which to execute the function
* @returns a handle to remove the route
*/
this.set = function set() {
return _routes.watch.apply(_routes, arguments);
};
/**
* Remove a route
* @param {Object} handle the handle provided by the set method
* @returns true if successfully removed
*/
this.unset = function unset(handle) {
return _routes.unwatch(handle);
};
/**
* Navigate to a route
* @param {String} route the route to navigate to
* @param {*} *params
* @returns
*/
this.navigate = function get(route, params) {
if (this.load.apply(this, arguments)) {
// Before adding a new route to the history, we must clear the forward history
_history.proxy("splice", _currentPos +1, _history.count());
_history.proxy("push", Tools.toArray(arguments));
this.ensureMaxHistory(_history);
_currentPos = _history.count() -1;
return true;
} else {
return false;
}
};
/**
* Ensure that history doesn't grow bigger than the max history setting
* @param {Store} history the history store
* @private
*/
this.ensureMaxHistory = function ensureMaxHistory(history) {
var count = history.count(),
max = this.getMaxHistory(),
excess = count - max;
if (excess > 0) {
history.proxy("splice", 0, excess);
}
};
/**
* Actually loads the route
* @private
*/
this.load = function load() {
var copy = Tools.toArray(arguments);
if (_routes.notify.apply(_routes, copy)) {
copy.unshift("route");
_events.notify.apply(_events, copy);
return true;
} else {
return false;
}
};
/**
* Watch for route changes
* @param {Function} func the func to execute when the route changes
* @param {Object} scope the scope in which to execute the function
* @returns {Object} the handle to unwatch for route changes
*/
this.watch = function watch(func, scope) {
return _events.watch("route", func, scope);
};
/**
* Unwatch routes changes
* @param {Object} handle the handle was returned by the watch function
* @returns true if unwatch
*/
this.unwatch = function unwatch(handle) {
return _events.unwatch(handle);
};
/**
* Get the history store, for debugging only
* @private
*/
this.getHistoryStore = function getHistoryStore() {
return _history;
};
/**
* Get the current length of history
* @returns {Number} the length of history
*/
this.getHistoryCount = function getHistoryCount() {
return _history.count();
};
/**
* Flush the entire history
*/
this.clearHistory = function clearHistory() {
_history.reset([]);
};
/**
* Go back and forth in the history
* @param {Number} nb the amount of history to rewind/forward
* @returns true if history exists
*/
this.go = function go(nb) {
var history = _history.get(_currentPos + nb);
if (history) {
_currentPos += nb;
this.load.apply(this, history);
return true;
} else {
return false;
}
};
/**
* Go back in the history, short for go(-1)
* @returns
*/
this.back = function back() {
return this.go(-1);
};
/**
* Go forward in the history, short for go(1)
* @returns
*/
this.forward = function forward() {
return this.go(1);
};
};
});
\ No newline at end of file
......@@ -12,6 +12,8 @@
define('DomUtils',["Tools"], function (Tools) {
return {
/**
* Returns a NodeList including the given dom node,
......@@ -127,375 +129,406 @@ define('Bind.plugin',["Store", "Observable", "Tools", "DomUtils"],
*/
function BindPlugin(Store, Observable, Tools, DomUtils) {
return function BindPluginConstructor($model, $bindings) {
/**
* The model to watch
* @private
*/
var _model = null,
/**
* The list of custom bindings
* @private
*/
_bindings = {},
/**
* The list of itemRenderers
* each foreach has its itemRenderer
* @private
*/
_itemRenderers = {};
/**
* The observers handlers
* for debugging only
* @private
*/
this.observers = {};
/**
* Define the model to watch for
* @param {Store} model the model to watch for changes
* @returns {Boolean} true if the model was set
*/
this.setModel = function setModel(model) {
if (model instanceof Store) {
// Set the model
_model = model;
return true;
} else {
return false;
}
};
/**
* Get the store that is watched for
* for debugging only
* @private
* @returns the Store
*/
this.getModel = function getModel() {
return _model;
};
/**
* The item renderer defines a dom node that can be duplicated
* It is made available for debugging purpose, don't use it
* @private
*/
this.ItemRenderer = function ItemRenderer($plugins, $rootNode) {
/**
* The node that will be cloned
* @private
*/
var _node = null,
/**
* The object that contains plugins.name and plugins.apply
* @private
*/
_plugins = null,
/**
* The _rootNode where to append the created items
* @private
*/
_rootNode = null,
/**
* The lower boundary
* @private
*/
_start = null,
/**
* The number of item to display
* @private
*/
_nb = null;
/**
* Set the duplicated node
* @private
*/
this.setRenderer = function setRenderer(node) {
_node = node;
return true;
};
/**
* Returns the node that is going to be used for rendering
* @private
* @returns the node that is duplicated
*/
this.getRenderer = function getRenderer() {
return _node;
};
/**
* Sets the rootNode and gets the node to copy
* @private
* @param {HTMLElement|SVGElement} rootNode
* @returns
*/
this.setRootNode = function setRootNode(rootNode) {
var renderer;
if (DomUtils.isAcceptedType(rootNode)) {
_rootNode = rootNode;
renderer = _rootNode.querySelector("*");
this.setRenderer(renderer);
renderer && _rootNode.removeChild(renderer);
return true;
} else {
return false;
}
};
/**
* Gets the rootNode
* @private
* @returns _rootNode
*/
this.getRootNode = function getRootNode() {
return _rootNode;
};
/**
* Set the plugins objet that contains the name and the apply function
* @private
* @param plugins
* @returns true
*/
this.setPlugins = function setPlugins(plugins) {
_plugins = plugins;
return true;
};
/**
* Get the plugins object
* @private
* @returns the plugins object
*/
this.getPlugins = function getPlugins() {
return _plugins;
};
/**
* The nodes created from the items are stored here
* @private
*/
this.items = new Store([]);
/**
* Set the start limit
* @private
* @param {Number} start the value to start rendering the items from
* @returns the value
*/
this.setStart = function setStart(start) {
return _start = parseInt(start, 10);
};
/**
* Get the start value
* @private
* @returns the start value
*/
this.getStart = function getStart() {
return _start;
};
/**
* Set the number of item to display
* @private
* @param {Number/String} nb the number of item to display or "*" for all
* @returns the value
*/
this.setNb = function setNb(nb) {
return _nb = nb == "*" ? nb : parseInt(nb, 10);
};
/**
* Get the number of item to display
* @private
* @returns the value
*/
this.getNb = function getNb() {
return _nb;
};
/**
* Adds a new item and adds it in the items list
* @private
* @param {Number} id the id of the item
* @returns
*/
this.addItem = function addItem(id) {
var node,
next;
if (typeof id == "number" && !this.items.get(id)) {
node = this.create(id);
if (node) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
next = this.getNextItem(id);
next ? _rootNode.insertBefore(node, next) : _rootNode.appendChild(node);
return true;
} else {
return false;
}
} else {
return false;
}
};
/**
* Get the next item in the item store given an id.
* @private
* @param {Number} id the id to start from
* @returns
*/
this.getNextItem = function getNextItem(id) {
return this.items.alter("slice", id+1).filter(function (value) {
if (DomUtils.isAcceptedType(value)) {
return true;
}
})[0];
};
/**
* Remove an item from the dom and the items list
* @private
* @param {Number} id the id of the item to remove
* @returns
*/
this.removeItem = function removeItem(id) {
var item = this.items.get(id);
if (item) {
_rootNode.removeChild(item);
this.items.set(id);
return true;
} else {
return false;
}
};
/**
* create a new node. Actually makes a clone of the initial one
* and adds pluginname_id to each node, then calls plugins.apply to apply all plugins
* @private
* @param id
* @param pluginName
* @returns the associated node
*/
this.create = function create(id) {
if (_model.has(id)) {
var newNode = _node.cloneNode(true),
nodes = DomUtils.getNodes(newNode);
Tools.toArray(nodes).forEach(function (child) {
child.setAttribute("data-" + _plugins.name+"_id", id);
});
this.items.set(id, newNode);
_plugins.apply(newNode);
return newNode;
}
};
/**
* Renders the dom tree, adds nodes that are in the boundaries
* and removes the others
* @private
* @returns true boundaries are set
*/
this.render = function render() {
// If the number of items to render is all (*)
// Then get the number of items
var _tmpNb = _nb == "*" ? _model.getNbItems() : _nb;
// This will store the items to remove
var marked = [];
// Render only if boundaries have been set
if (_nb !== null && _start !== null) {
// Loop through the existing items
this.items.loop(function (value, idx) {
// If an item is out of the boundary
if (idx < _start || idx >= (_start + _tmpNb) || !_model.has(idx)) {
// Mark it
marked.push(idx);
}
}, this);
// Remove the marked item from the highest id to the lowest
// Doing this will avoid the id change during removal
// (removing id 2 will make id 3 becoming 2)
marked.sort(Tools.compareNumbers).reverse().forEach(this.removeItem, this);
// Now that we have removed the old nodes
// Add the missing one
for (var i=_start, l=_tmpNb+_start; i<l; i++) {
this.addItem(i);
}
return true;
} else {
return false;
}
};
this.setPlugins($plugins);
this.setRootNode($rootNode);
};
/**
* Save an itemRenderer according to its id
* @private
* @param {String} id the id of the itemRenderer
* @param {ItemRenderer} itemRenderer an itemRenderer object
*/
this.setItemRenderer = function setItemRenderer(id, itemRenderer) {
id = id || "default";
_itemRenderers[id] = itemRenderer;
};
/**
* Get an itemRenderer
* @private
* @param {String} id the name of the itemRenderer
* @returns the itemRenderer
*/
this.getItemRenderer = function getItemRenderer(id) {
return _itemRenderers[id];
};
/**
* Expands the inner dom nodes of a given dom node, filling it with model's values
* @param {HTMLElement|SVGElement} node the dom node to apply foreach to
*/
this.foreach = function foreach(node, idItemRenderer, start, nb) {
var itemRenderer = new this.ItemRenderer(this.plugins, node);
itemRenderer.setStart(start || 0);
itemRenderer.setNb(nb || "*");
itemRenderer.render();
// Add the newly created item
return function BindPluginConstructor($model, $bindings) {
/**
* The model to watch
* @private
*/
var _model = null,
/**
* The list of custom bindings
* @private
*/
_bindings = {},
/**
* The list of itemRenderers
* each foreach has its itemRenderer
* @private
*/
_itemRenderers = {},
/**
* The observers handlers
* @private
*/
_observers = {};
/**
* Exposed for debugging purpose
* @private
*/
this.observers = _observers;
function _removeObserversForId(id) {
if (_observers[id]) {
_observers[id].forEach(function (handler) {
_model.unwatchValue(handler);
});
delete _observers[id];
}
}
/**
* Define the model to watch for
* @param {Store} model the model to watch for changes
* @returns {Boolean} true if the model was set
*/
this.setModel = function setModel(model) {
if (model instanceof Store) {
// Set the model
_model = model;
return true;
} else {
return false;
}
};
/**
* Get the store that is watched for
* for debugging only
* @private
* @returns the Store
*/
this.getModel = function getModel() {
return _model;
};
/**
* The item renderer defines a dom node that can be duplicated
* It is made available for debugging purpose, don't use it
* @private
*/
this.ItemRenderer = function ItemRenderer($plugins, $rootNode) {
/**
* The node that will be cloned
* @private
*/
var _node = null,
/**
* The object that contains plugins.name and plugins.apply
* @private
*/
_plugins = null,
/**
* The _rootNode where to append the created items
* @private
*/
_rootNode = null,
/**
* The lower boundary
* @private
*/
_start = null,
/**
* The number of item to display
* @private
*/
_nb = null;
/**
* Set the duplicated node
* @private
*/
this.setRenderer = function setRenderer(node) {
_node = node;
return true;
};
/**
* Returns the node that is going to be used for rendering
* @private
* @returns the node that is duplicated
*/
this.getRenderer = function getRenderer() {
return _node;
};
/**
* Sets the rootNode and gets the node to copy
* @private
* @param {HTMLElement|SVGElement} rootNode
* @returns
*/
this.setRootNode = function setRootNode(rootNode) {
var renderer;
if (DomUtils.isAcceptedType(rootNode)) {
_rootNode = rootNode;
renderer = _rootNode.querySelector("*");
this.setRenderer(renderer);
if (renderer) {
_rootNode.removeChild(renderer);
}
return true;
} else {
return false;
}
};
/**
* Gets the rootNode
* @private
* @returns _rootNode
*/
this.getRootNode = function getRootNode() {
return _rootNode;
};
/**
* Set the plugins objet that contains the name and the apply function
* @private
* @param plugins
* @returns true
*/
this.setPlugins = function setPlugins(plugins) {
_plugins = plugins;
return true;
};
/**
* Get the plugins object
* @private
* @returns the plugins object
*/
this.getPlugins = function getPlugins() {
return _plugins;
};
/**
* The nodes created from the items are stored here
* @private
*/
this.items = {};
/**
* Set the start limit
* @private
* @param {Number} start the value to start rendering the items from
* @returns the value
*/
this.setStart = function setStart(start) {
_start = parseInt(start, 10);
return _start;
};
/**
* Get the start value
* @private
* @returns the start value
*/
this.getStart = function getStart() {
return _start;
};
/**
* Set the number of item to display
* @private
* @param {Number/String} nb the number of item to display or "*" for all
* @returns the value
*/
this.setNb = function setNb(nb) {
_nb = nb == "*" ? nb : parseInt(nb, 10);
return _nb;
};
/**
* Get the number of item to display
* @private
* @returns the value
*/
this.getNb = function getNb() {
return _nb;
};
/**
* Adds a new item and adds it in the items list
* @private
* @param {Number} id the id of the item
* @returns
*/
this.addItem = function addItem(id) {
var node,
next;
if (typeof id == "number" && !this.items[id]) {
next = this.getNextItem(id);
node = this.create(id);
if (node) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
if (next) {
_rootNode.insertBefore(node, next);
} else {
_rootNode.appendChild(node);
}
return true;
} else {
return false;
}
} else {
return false;
}
};
/**
* Get the next item in the item store given an id.
* @private
* @param {Number} id the id to start from
* @returns
*/
this.getNextItem = function getNextItem(id) {
var keys = Object.keys(this.items).map(function (string) {
return Number(string);
}),
closest = Tools.closestGreater(id, keys),
closestId = keys[closest];
// Only return if different
if (closestId != id) {
return this.items[closestId];
} else {
return;
}
};
/**
* Remove an item from the dom and the items list
* @private
* @param {Number} id the id of the item to remove
* @returns
*/
this.removeItem = function removeItem(id) {
var item = this.items[id];
if (item) {
_rootNode.removeChild(item);
delete this.items[id];
_removeObserversForId(id);
return true;
} else {
return false;
}
};
/**
* create a new node. Actually makes a clone of the initial one
* and adds pluginname_id to each node, then calls plugins.apply to apply all plugins
* @private
* @param id
* @param pluginName
* @returns the associated node
*/
this.create = function create(id) {
if (_model.has(id)) {
var newNode = _node.cloneNode(true),
nodes = DomUtils.getNodes(newNode);
Tools.toArray(nodes).forEach(function (child) {
child.setAttribute("data-" + _plugins.name+"_id", id);
});
this.items[id] = newNode;
_plugins.apply(newNode);
return newNode;
}
};
/**
* Renders the dom tree, adds nodes that are in the boundaries
* and removes the others
* @private
* @returns true boundaries are set
*/
this.render = function render() {
// If the number of items to render is all (*)
// Then get the number of items
var _tmpNb = _nb == "*" ? _model.getNbItems() : _nb;
// This will store the items to remove
var marked = [];
// Render only if boundaries have been set
if (_nb !== null && _start !== null) {
// Loop through the existing items
Tools.loop(this.items, function (value, idx) {
// If an item is out of the boundary
idx = Number(idx);
if (idx < _start || idx >= (_start + _tmpNb) || !_model.has(idx)) {
// Mark it
marked.push(idx);
}
}, this);
// Remove the marked item from the highest id to the lowest
// Doing this will avoid the id change during removal
// (removing id 2 will make id 3 becoming 2)
marked.sort(Tools.compareNumbers).reverse().forEach(this.removeItem, this);
// Now that we have removed the old nodes
// Add the missing one
for (var i=_start, l=_tmpNb+_start; i<l; i++) {
this.addItem(i);
}
return true;
} else {
return false;
}
};
this.setPlugins($plugins);
this.setRootNode($rootNode);
};
/**
* Save an itemRenderer according to its id
* @private
* @param {String} id the id of the itemRenderer
* @param {ItemRenderer} itemRenderer an itemRenderer object
*/
this.setItemRenderer = function setItemRenderer(id, itemRenderer) {
id = id || "default";
_itemRenderers[id] = itemRenderer;
};
/**
* Get an itemRenderer
* @private
* @param {String} id the name of the itemRenderer
* @returns the itemRenderer
*/
this.getItemRenderer = function getItemRenderer(id) {
return _itemRenderers[id];
};
/**
* Expands the inner dom nodes of a given dom node, filling it with model's values
* @param {HTMLElement|SVGElement} node the dom node to apply foreach to
*/
this.foreach = function foreach(node, idItemRenderer, start, nb) {
var itemRenderer = new this.ItemRenderer(this.plugins, node);
itemRenderer.setStart(start || 0);
itemRenderer.setNb(nb || "*");
itemRenderer.render();
// Add the newly created item
_model.watch("added", itemRenderer.render, itemRenderer);
// If an item is deleted
// If an item is deleted
_model.watch("deleted", function (idx) {
itemRenderer.render();
itemRenderer.render();
// Also remove all observers
this.observers[idx] && this.observers[idx].forEach(function (handler) {
_model.unwatchValue(handler);
}, this);
delete this.observers[idx];
_removeObserversForId(idx);
},this);
this.setItemRenderer(idItemRenderer, itemRenderer);
......@@ -508,13 +541,13 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns true if the foreach exists
*/
this.updateStart = function updateStart(id, start) {
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.setStart(start);
return true;
} else {
return false;
}
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.setStart(start);
return true;
} else {
return false;
}
};
/**
......@@ -524,13 +557,13 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns true if the foreach exists
*/
this.updateNb = function updateNb(id, nb) {
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.setNb(nb);
return true;
} else {
return false;
}
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.setNb(nb);
return true;
} else {
return false;
}
};
/**
......@@ -539,202 +572,202 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns true if the foreach exists
*/
this.refresh = function refresh(id) {
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.render();
return true;
} else {
return false;
}
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.render();
return true;
} else {
return false;
}
};
/**
* Both ways binding between a dom node attributes and the model
* @param {HTMLElement|SVGElement} node the dom node to apply the plugin to
* @param {String} name the name of the property to look for in the model's value
* @returns
*/
this.bind = function bind(node, property, name) {
// Name can be unset if the value of a row is plain text
name = name || "";
// In case of an array-like model the id is the index of the model's item to look for.
// The _id is added by the foreach function
var id = node.getAttribute("data-" + this.plugins.name+"_id"),
// Else, it is the first element of the following
split = name.split("."),
// So the index of the model is either id or the first element of split
modelIdx = id || split.shift(),
// And the name of the property to look for in the value is
prop = id ? name : split.join("."),
// Get the model's value
get = Tools.getNestedProperty(_model.get(modelIdx), prop),
// When calling bind like bind:newBinding,param1, param2... we need to get them
extraParam = Tools.toArray(arguments).slice(3);
// 0 and false are acceptable falsy values
if (get || get === 0 || get === false) {
// If the binding hasn't been overriden
if (!this.execBinding.apply(this,
[node, property, get]
// Extra params are passed to the new binding too
.concat(extraParam))) {
// Execute the default one which is a simple assignation
//node[property] = get;
DomUtils.setAttribute(node, property, get);
}
}
// Only watch for changes (double way data binding) if the binding
// has not been redefined
if (!this.hasBinding(property)) {
node.addEventListener("change", function (event) {
if (_model.has(modelIdx)) {
if (prop) {
_model.update(modelIdx, name, node[property]);
} else {
_model.set(modelIdx, node[property]);
}
}
}, true);
}
// Watch for changes
this.observers[modelIdx] = this.observers[modelIdx] || [];
this.observers[modelIdx].push(_model.watchValue(modelIdx, function (value) {
if (!this.execBinding.apply(this,
[node, property, Tools.getNestedProperty(value, prop)]
// passing extra params too
.concat(extraParam))) {
//node[property] = Tools.getNestedProperty(value, prop);
DomUtils.setAttribute(node, property, Tools.getNestedProperty(value, prop));
}
}, this));
};
/**
* Set the node's value into the model, the name is the model's property
* @private
* @param {HTMLElement|SVGElement} node
* @returns true if the property is added
*/
this.set = function set(node) {
if (DomUtils.isAcceptedType(node) && node.name) {
_model.set(node.name, node.value);
return true;
} else {
return false;
}
};
this.getItemIndex = function getElementId(dom) {
var dataset = DomUtils.getDataset(dom);
if (dataset && typeof dataset[this.plugins.name + "_id"] != "undefined") {
return +dataset[this.plugins.name + "_id"];
} else {
return false;
}
};
/**
* Prevents the submit and set the model with all form's inputs
* @param {HTMLFormElement} form
* @returns true if valid form
*/
this.form = function form(form) {
if (form && form.nodeName == "FORM") {
var that = this;
form.addEventListener("submit", function (event) {
Tools.toArray(form.querySelectorAll("[name]")).forEach(that.set, that);
event.preventDefault();
}, true);
return true;
} else {
return false;
}
};
/**
* Add a new way to handle a binding
* @param {String} name of the binding
* @param {Function} binding the function to handle the binding
* @returns
*/
this.addBinding = function addBinding(name, binding) {
if (name && typeof name == "string" && typeof binding == "function") {
_bindings[name] = binding;
return true;
} else {
return false;
}
};
/**
* Execute a binding
* Only used by the plugin
* @private
* @param {HTMLElement} node the dom node on which to execute the binding
* @param {String} name the name of the binding
* @param {Any type} value the value to pass to the function
* @returns
*/
this.execBinding = function execBinding(node, name) {
if (this.hasBinding(name)) {
_bindings[name].apply(node, Array.prototype.slice.call(arguments, 2));
return true;
} else {
return false;
}
};
/**
* Check if the binding exists
* @private
* @param {String} name the name of the binding
* @returns
*/
this.hasBinding = function hasBinding(name) {
return _bindings.hasOwnProperty(name);
};
/**
* Get a binding
* For debugging only
* @private
* @param {String} name the name of the binding
* @returns
*/
this.getBinding = function getBinding(name) {
return _bindings[name];
};
/**
* Add multiple binding at once
* @param {Object} list the list of bindings to add
* @returns
*/
this.addBindings = function addBindings(list) {
return Tools.loop(list, function (binding, name) {
this.addBinding(name, binding);
}, this);
};
// Inits the model
this.setModel($model);
// Inits bindings
this.addBindings($bindings);
};
/**
* Both ways binding between a dom node attributes and the model
* @param {HTMLElement|SVGElement} node the dom node to apply the plugin to
* @param {String} name the name of the property to look for in the model's value
* @returns
*/
this.bind = function bind(node, property, name) {
// Name can be unset if the value of a row is plain text
name = name || "";
// In case of an array-like model the id is the index of the model's item to look for.
// The _id is added by the foreach function
var id = node.getAttribute("data-" + this.plugins.name+"_id"),
// Else, it is the first element of the following
split = name.split("."),
// So the index of the model is either id or the first element of split
modelIdx = id || split.shift(),
// And the name of the property to look for in the value is
prop = id ? name : split.join("."),
// Get the model's value
get = Tools.getNestedProperty(_model.get(modelIdx), prop),
// When calling bind like bind:newBinding,param1, param2... we need to get them
extraParam = Tools.toArray(arguments).slice(3);
// 0 and false are acceptable falsy values
if (get || get === 0 || get === false) {
// If the binding hasn't been overriden
if (!this.execBinding.apply(this,
[node, property, get]
// Extra params are passed to the new binding too
.concat(extraParam))) {
// Execute the default one which is a simple assignation
//node[property] = get;
DomUtils.setAttribute(node, property, get);
}
}
// Only watch for changes (double way data binding) if the binding
// has not been redefined
if (!this.hasBinding(property)) {
node.addEventListener("change", function (event) {
if (_model.has(modelIdx)) {
if (prop) {
_model.update(modelIdx, name, node[property]);
} else {
_model.set(modelIdx, node[property]);
}
}
}, true);
}
// Watch for changes
this.observers[modelIdx] = this.observers[modelIdx] || [];
this.observers[modelIdx].push(_model.watchValue(modelIdx, function (value) {
if (!this.execBinding.apply(this,
[node, property, Tools.getNestedProperty(value, prop)]
// passing extra params too
.concat(extraParam))) {
//node[property] = Tools.getNestedProperty(value, prop);
DomUtils.setAttribute(node, property, Tools.getNestedProperty(value, prop));
}
}, this));
};
/**
* Set the node's value into the model, the name is the model's property
* @private
* @param {HTMLElement|SVGElement} node
* @returns true if the property is added
*/
this.set = function set(node) {
if (DomUtils.isAcceptedType(node) && node.name) {
_model.set(node.name, node.value);
return true;
} else {
return false;
}
};
this.getItemIndex = function getElementId(dom) {
var dataset = DomUtils.getDataset(dom);
if (dataset && typeof dataset[this.plugins.name + "_id"] != "undefined") {
return +dataset[this.plugins.name + "_id"];
} else {
return false;
}
};
/**
* Prevents the submit and set the model with all form's inputs
* @param {HTMLFormElement} DOMfrom
* @returns true if valid form
*/
this.form = function form(DOMform) {
if (DOMform && DOMform.nodeName == "FORM") {
var that = this;
DOMform.addEventListener("submit", function (event) {
Tools.toArray(DOMform.querySelectorAll("[name]")).forEach(that.set, that);
event.preventDefault();
}, true);
return true;
} else {
return false;
}
};
/**
* Add a new way to handle a binding
* @param {String} name of the binding
* @param {Function} binding the function to handle the binding
* @returns
*/
this.addBinding = function addBinding(name, binding) {
if (name && typeof name == "string" && typeof binding == "function") {
_bindings[name] = binding;
return true;
} else {
return false;
}
};
/**
* Execute a binding
* Only used by the plugin
* @private
* @param {HTMLElement} node the dom node on which to execute the binding
* @param {String} name the name of the binding
* @param {Any type} value the value to pass to the function
* @returns
*/
this.execBinding = function execBinding(node, name) {
if (this.hasBinding(name)) {
_bindings[name].apply(node, Array.prototype.slice.call(arguments, 2));
return true;
} else {
return false;
}
};
/**
* Check if the binding exists
* @private
* @param {String} name the name of the binding
* @returns
*/
this.hasBinding = function hasBinding(name) {
return _bindings.hasOwnProperty(name);
};
/**
* Get a binding
* For debugging only
* @private
* @param {String} name the name of the binding
* @returns
*/
this.getBinding = function getBinding(name) {
return _bindings[name];
};
/**
* Add multiple binding at once
* @param {Object} list the list of bindings to add
* @returns
*/
this.addBindings = function addBindings(list) {
return Tools.loop(list, function (binding, name) {
this.addBinding(name, binding);
}, this);
};
// Inits the model
this.setModel($model);
// Inits bindings
this.addBindings($bindings);
};
});
......@@ -754,122 +787,124 @@ define('Event.plugin',["DomUtils"],
*/
function EventPlugin(Utils) {
/**
* The event plugin constructor.
* ex: new EventPlugin({method: function(){} ...}, false);
* @param {Object} the object that has the event handling methods
* @param {Boolean} $isMobile if the event handler has to map with touch events
*/
return function EventPluginConstructor($parent, $isMobile) {
/**
* The parent callback
* @private
*/
var _parent = null,
/**
* The mapping object.
* @private
*/
_map = {
"mousedown" : "touchstart",
"mouseup" : "touchend",
"mousemove" : "touchmove"
},
/**
* Is touch device.
* @private
*/
_isMobile = !!$isMobile;
/**
* Add mapped event listener (for testing purpose).
* @private
*/
this.addEventListener = function addEventListener(node, event, callback, useCapture) {
node.addEventListener(this.map(event), callback, !!useCapture);
};
/**
* Listen to DOM events.
* @param {Object} node DOM node
* @param {String} name event's name
* @param {String} listener callback's name
* @param {String} useCapture string
*/
this.listen = function listen(node, name, listener, useCapture) {
this.addEventListener(node, name, function(e){
_parent[listener].call(_parent, e, node);
}, !!useCapture);
};
/**
* Delegate the event handling to a parent DOM element
* @param {Object} node DOM node
* @param {String} selector CSS3 selector to the element that listens to the event
* @param {String} name event's name
* @param {String} listener callback's name
* @param {String} useCapture string
*/
this.delegate = function delegate(node, selector, name, listener, useCapture) {
this.addEventListener(node, name, function(event){
if (Utils.matches(node, selector, event.target)) {
_parent[listener].call(_parent, event, node);
}
}, !!useCapture);
};
/**
* Get the parent object.
* @return {Object} the parent object
*/
this.getParent = function getParent() {
return _parent;
};
/**
* Set the parent object.
* The parent object is an object which the functions are called by node listeners.
* @param {Object} the parent object
* @return true if object has been set
*/
this.setParent = function setParent(parent) {
if (parent instanceof Object){
_parent = parent;
return true;
}
return false;
};
/**
* Get event mapping.
* @param {String} event's name
* @return the mapped event's name
*/
this.map = function map(name) {
return _isMobile ? (_map[name] || name) : name;
};
/**
* Set event mapping.
* @param {String} event's name
* @param {String} event's value
* @return true if mapped
*/
this.setMap = function setMap(name, value) {
if (typeof name == "string" &&
typeof value == "string") {
_map[name] = value;
return true;
}
return false;
};
//init
this.setParent($parent);
};
/**
* The event plugin constructor.
* ex: new EventPlugin({method: function(){} ...}, false);
* @param {Object} the object that has the event handling methods
* @param {Boolean} $isMobile if the event handler has to map with touch events
*/
return function EventPluginConstructor($parent, $isMobile) {
/**
* The parent callback
* @private
*/
var _parent = null,
/**
* The mapping object.
* @private
*/
_map = {
"mousedown" : "touchstart",
"mouseup" : "touchend",
"mousemove" : "touchmove"
},
/**
* Is touch device.
* @private
*/
_isMobile = !!$isMobile;
/**
* Add mapped event listener (for testing purpose).
* @private
*/
this.addEventListener = function addEventListener(node, event, callback, useCapture) {
node.addEventListener(this.map(event), callback, !!useCapture);
};
/**
* Listen to DOM events.
* @param {Object} node DOM node
* @param {String} name event's name
* @param {String} listener callback's name
* @param {String} useCapture string
*/
this.listen = function listen(node, name, listener, useCapture) {
this.addEventListener(node, name, function(e){
_parent[listener].call(_parent, e, node);
}, !!useCapture);
};
/**
* Delegate the event handling to a parent DOM element
* @param {Object} node DOM node
* @param {String} selector CSS3 selector to the element that listens to the event
* @param {String} name event's name
* @param {String} listener callback's name
* @param {String} useCapture string
*/
this.delegate = function delegate(node, selector, name, listener, useCapture) {
this.addEventListener(node, name, function(event){
if (Utils.matches(node, selector, event.target)) {
_parent[listener].call(_parent, event, node);
}
}, !!useCapture);
};
/**
* Get the parent object.
* @return {Object} the parent object
*/
this.getParent = function getParent() {
return _parent;
};
/**
* Set the parent object.
* The parent object is an object which the functions are called by node listeners.
* @param {Object} the parent object
* @return true if object has been set
*/
this.setParent = function setParent(parent) {
if (parent instanceof Object){
_parent = parent;
return true;
}
return false;
};
/**
* Get event mapping.
* @param {String} event's name
* @return the mapped event's name
*/
this.map = function map(name) {
return _isMobile ? (_map[name] || name) : name;
};
/**
* Set event mapping.
* @param {String} event's name
* @param {String} event's value
* @return true if mapped
*/
this.setMap = function setMap(name, value) {
if (typeof name == "string" &&
typeof value == "string") {
_map[name] = value;
return true;
}
return false;
};
//init
this.setParent($parent);
};
});
......@@ -890,6 +925,8 @@ define('LocalStore',["Store", "Tools"],
*/
function LocalStore(Store, Tools) {
function LocalStoreConstructor() {
/**
......@@ -971,7 +1008,7 @@ function LocalStore(Store, Tools) {
return function LocalStoreFactory(init) {
LocalStoreConstructor.prototype = new Store(init);
return new LocalStoreConstructor;
return new LocalStoreConstructor();
};
});
......@@ -995,6 +1032,8 @@ define('Plugins',["Tools", "DomUtils"],
*/
function Plugins(Tools, DomUtils) {
return function PluginsConstructor($plugins) {
/**
......@@ -1147,277 +1186,285 @@ define('OObject',["StateMachine", "Store", "Plugins", "DomUtils", "Tools"],
*/
function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
return function OObjectConstructor(otherStore) {
return function OObjectConstructor(otherStore) {
/**
* This function creates the dom of the UI from its template
* It then queries the dom for data- attributes
* It can't be executed if the template is not set
* @private
*/
var render = function render(UI) {
// The place where the template will be created
// is either the currentPlace where the node is placed
// or a temporary div
var baseNode = _currentPlace || document.createElement("div");
// If the template is set
if (UI.template) {
// In this function, the thisObject is the UI's prototype
// UI is the UI that has OObject as prototype
if (typeof UI.template == "string") {
// Let the browser do the parsing, can't be faster & easier.
baseNode.innerHTML = UI.template.trim();
} else if (DomUtils.isAcceptedType(UI.template)) {
// If it's already an HTML element
baseNode.appendChild(UI.template);
}
// The UI must be placed in a unique dom node
// If not, there can't be multiple UIs placed in the same parentNode
// as it wouldn't be possible to know which node would belong to which UI
// This is probably a DOM limitation.
if (baseNode.childNodes.length > 1) {
throw new Error("UI.template should have only one parent node");
} else {
UI.dom = baseNode.childNodes[0];
}
UI.plugins.apply(UI.dom);
} else {
// An explicit message I hope
throw new Error("UI.template must be set prior to render");
}
},
/**
* This function appends the dom tree to the given dom node.
* This dom node should be somewhere in the dom of the application
* @private
*/
place = function place(UI, DOMplace, beforeNode) {
if (DOMplace) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
if (beforeNode) {
DOMplace.insertBefore(UI.dom, beforeNode);
} else {
DOMplace.appendChild(UI.dom);
}
// Also save the new place, so next renderings
// will be made inside it
_currentPlace = DOMplace;
}
},
/**
* Does rendering & placing in one function
* @private
*/
renderNPlace = function renderNPlace(UI, dom) {
render(UI);
place.apply(null, Tools.toArray(arguments));
},
/**
* This stores the current place
* If this is set, this is the place where new templates
* will be appended
* @private
*/
_currentPlace = null,
/**
* The UI's stateMachine.
* Much better than if(stuff) do(stuff) else if (!stuff and stuff but not stouff) do (otherstuff)
* Please open an issue if you want to propose a better one
* @private
*/
_stateMachine = new StateMachine("Init", {
"Init": [["render", render, this, "Rendered"],
["place", renderNPlace, this, "Rendered"]],
"Rendered": [["place", place, this],
["render", render, this]]
});
/**
* The UI's Store
* It has set/get/del/has/watch/unwatch methods
* @see Emily's doc for more info on how it works.
*/
this.model = otherStore instanceof Store ? otherStore : new Store();
/**
* The module that will manage the plugins for this UI
* @see Olives/Plugins' doc for more info on how it works.
*/
this.plugins = new Plugins();
/**
* Describes the template, can either be like "&lt;p&gt;&lt;/p&gt;" or HTMLElements
* @type string or HTMLElement|SVGElement
*/
this.template = null;
/**
* This will hold the dom nodes built from the template.
*/
this.dom = null;
/**
* Place the UI in a given dom node
* @param node the node on which to append the UI
* @param beforeNode the dom before which to append the UI
*/
this.place = function place(node, beforeNode) {
_stateMachine.event("place", this, node, beforeNode);
};
/**
* Renders the template to dom nodes and applies the plugins on it
* It requires the template to be set first
*/
this.render = function render() {
_stateMachine.event("render", this);
};
/**
* Set the UI's template from a DOM element
* @param {HTMLElement|SVGElement} dom the dom element that'll become the template of the UI
* @returns true if dom is an HTMLElement|SVGElement
*/
this.setTemplateFromDom = function setTemplateFromDom(dom) {
if (DomUtils.isAcceptedType(dom)) {
this.template = dom;
return true;
} else {
return false;
}
};
/**
* Transforms dom nodes into a UI.
* It basically does a setTemplateFromDOM, then a place
* It's a helper function
* @param {HTMLElement|SVGElement} node the dom to transform to a UI
* @returns true if dom is an HTMLElement|SVGElement
*/
this.alive = function alive(dom) {
if (DomUtils.isAcceptedType(dom)) {
this.setTemplateFromDom(dom);
this.place(dom.parentNode, dom.nextElementSibling);
return true;
} else {
return false;
}
};
/**
* Get the current dom node where the UI is placed.
* for debugging purpose
* @private
* @return {HTMLElement} node the dom where the UI is placed.
*/
this.getCurrentPlace = function(){
return _currentPlace;
};
};
/**
* This function creates the dom of the UI from its template
* It then queries the dom for data- attributes
* It can't be executed if the template is not set
* @private
*/
var render = function render(UI) {
// The place where the template will be created
// is either the currentPlace where the node is placed
// or a temporary div
var baseNode = _currentPlace || document.createElement("div");
// If the template is set
if (UI.template) {
// In this function, the thisObject is the UI's prototype
// UI is the UI that has OObject as prototype
if (typeof UI.template == "string") {
// Let the browser do the parsing, can't be faster & easier.
baseNode.innerHTML = UI.template.trim();
} else if (DomUtils.isAcceptedType(UI.template)) {
// If it's already an HTML element
baseNode.appendChild(UI.template);
}
});
// The UI must be placed in a unique dom node
// If not, there can't be multiple UIs placed in the same parentNode
// as it wouldn't be possible to know which node would belong to which UI
// This is probably a DOM limitation.
if (baseNode.childNodes.length > 1) {
throw Error("UI.template should have only one parent node");
} else {
UI.dom = baseNode.childNodes[0];
}
/**
* Olives http://flams.github.com/olives
* The MIT License (MIT)
* Copyright (c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com>
*/
UI.plugins.apply(UI.dom);
define('Place.plugin',["OObject", "Tools"],
/**
* @class
* Place plugin places OObject in the DOM.
* @requires OObject, Tools
*/
function PlacePlugin(OObject, Tools) {
} else {
// An explicit message I hope
throw Error("UI.template must be set prior to render");
}
},
/**
* Intilialize a Place.plugin with a list of OObjects
* @param {Object} $uis a list of OObjects such as:
* {
* "header": new OObject(),
* "list": new OObject()
* }
* @Constructor
*/
return function PlacePluginConstructor($uis) {
/**
* The list of uis currently set in this place plugin
* @private
*/
var _uis = {};
/**
* Attach an OObject to this DOM element
* @param {HTML|SVGElement} node the dom node where to attach the OObject
* @param {String} the name of the OObject to attach
* @throws {NoSuchOObject} an error if there's no OObject for the given name
*/
this.place = function place(node, name) {
if (_uis[name] instanceof OObject) {
_uis[name].place(node);
} else {
throw new Error(name + " is not an OObject UI in place:"+name);
}
};
/**
* Add an OObject that can be attached to a dom element
* @param {String} the name of the OObject to add to the list
* @param {OObject} ui the OObject to add the list
* @returns {Boolean} true if the OObject was added
*/
this.set = function set(name, ui) {
if (typeof name == "string" && ui instanceof OObject) {
_uis[name] = ui;
return true;
} else {
return false;
}
};
/**
* Add multiple dom elements at once
* @param {Object} $uis a list of OObjects such as:
* {
* "header": new OObject(),
* "list": new OObject()
* }
*/
this.setAll = function setAll(uis) {
Tools.loop(uis, function (ui, name) {
this.set(name, ui);
}, this);
};
/**
* Returns an OObject from the list given its name
* @param {String} the name of the OObject to get
* @returns {OObject} OObject for the given name
*/
this.get = function get(name) {
return _uis[name];
};
this.setAll($uis);
};
/**
* This function appends the dom tree to the given dom node.
* This dom node should be somewhere in the dom of the application
* @private
*/
place = function place(UI, place, beforeNode) {
if (place) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
beforeNode ? place.insertBefore(UI.dom, beforeNode) : place.appendChild(UI.dom);
// Also save the new place, so next renderings
// will be made inside it
_currentPlace = place;
}
},
});
/**
* Does rendering & placing in one function
* @private
*/
renderNPlace = function renderNPlace(UI, dom) {
render(UI);
place.apply(null, Tools.toArray(arguments));
},
/**
* This stores the current place
* If this is set, this is the place where new templates
* will be appended
* @private
*/
_currentPlace = null,
/**
* The UI's stateMachine.
* Much better than if(stuff) do(stuff) else if (!stuff and stuff but not stouff) do (otherstuff)
* Please open an issue if you want to propose a better one
* @private
*/
_stateMachine = new StateMachine("Init", {
"Init": [["render", render, this, "Rendered"],
["place", renderNPlace, this, "Rendered"]],
"Rendered": [["place", place, this],
["render", render, this]]
});
/**
* The UI's Store
* It has set/get/del/has/watch/unwatch methods
* @see Emily's doc for more info on how it works.
*/
this.model = otherStore instanceof Store ? otherStore : new Store;
/**
* The module that will manage the plugins for this UI
* @see Olives/Plugins' doc for more info on how it works.
*/
this.plugins = new Plugins();
/**
* Describes the template, can either be like "&lt;p&gt;&lt;/p&gt;" or HTMLElements
* @type string or HTMLElement|SVGElement
*/
this.template = null;
/**
* This will hold the dom nodes built from the template.
*/
this.dom = null;
/**
* Place the UI in a given dom node
* @param node the node on which to append the UI
* @param beforeNode the dom before which to append the UI
*/
this.place = function place(node, beforeNode) {
_stateMachine.event("place", this, node, beforeNode);
};
/**
* Renders the template to dom nodes and applies the plugins on it
* It requires the template to be set first
*/
this.render = function render() {
_stateMachine.event("render", this);
};
/**
* Set the UI's template from a DOM element
* @param {HTMLElement|SVGElement} dom the dom element that'll become the template of the UI
* @returns true if dom is an HTMLElement|SVGElement
*/
this.setTemplateFromDom = function setTemplateFromDom(dom) {
if (DomUtils.isAcceptedType(dom)) {
this.template = dom;
return true;
} else {
return false;
}
};
/**
* Transforms dom nodes into a UI.
* It basically does a setTemplateFromDOM, then a place
* It's a helper function
* @param {HTMLElement|SVGElement} node the dom to transform to a UI
* @returns true if dom is an HTMLElement|SVGElement
*/
this.alive = function alive(dom) {
if (DomUtils.isAcceptedType(dom)) {
this.setTemplateFromDom(dom);
this.place(dom.parentNode, dom.nextElementSibling);
return true;
} else {
return false;
}
};
/**
* Get the current dom node where the UI is placed.
* for debugging purpose
* @private
* @return {HTMLElement} node the dom where the UI is placed.
*/
this.getCurrentPlace = function(){
return _currentPlace;
};
};
});
/**
* Olives http://flams.github.com/olives
* The MIT License (MIT)
* Copyright (c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com>
*/
define('Place.plugin',["OObject", "Tools"],
/**
* @class
* Place plugin places OObject in the DOM.
* @requires OObject, Tools
*/
function PlacePlugin(OObject, Tools) {
/**
* Intilialize a Place.plugin with a list of OObjects
* @param {Object} $uis a list of OObjects such as:
* {
* "header": new OObject(),
* "list": new OObject()
* }
* @Constructor
*/
return function PlacePluginConstructor($uis) {
/**
* The list of uis currently set in this place plugin
* @private
*/
var _uis = {};
/**
* Attach an OObject to this DOM element
* @param {HTML|SVGElement} node the dom node where to attach the OObject
* @param {String} the name of the OObject to attach
* @throws {NoSuchOObject} an error if there's no OObject for the given name
*/
this.place = function place(node, name) {
if (_uis[name] instanceof OObject) {
_uis[name].place(node);
} else {
throw new Error(name + " is not an OObject UI in place:"+name);
}
};
/**
* Add an OObject that can be attached to a dom element
* @param {String} the name of the OObject to add to the list
* @param {OObject} ui the OObject to add the list
* @returns {Boolean} true if the OObject was added
*/
this.set = function set(name, ui) {
if (typeof name == "string" && ui instanceof OObject) {
_uis[name] = ui;
return true;
} else {
return false;
}
};
/**
* Add multiple dom elements at once
* @param {Object} $uis a list of OObjects such as:
* {
* "header": new OObject(),
* "list": new OObject()
* }
*/
this.setAll = function setAll(uis) {
Tools.loop(uis, function (ui, name) {
this.set(name, ui);
}, this);
};
/**
* Returns an OObject from the list given its name
* @param {String} the name of the OObject to get
* @returns {OObject} OObject for the given name
*/
this.get = function get(name) {
return _uis[name];
};
this.setAll($uis);
};
});
/**
* Olives http://flams.github.com/olives
* The MIT License (MIT)
* Copyright (c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com>
*/
/**
* Olives http://flams.github.com/olives
* The MIT License (MIT)
* Copyright (c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com>
*/
define('SocketIOTransport',["Observable", "Tools"],
/**
......@@ -1427,6 +1474,8 @@ define('SocketIOTransport',["Observable", "Tools"],
*/
function SocketIOTransport(Observable, Tools) {
/**
* Defines the SocketIOTransport
* @private
......@@ -1462,7 +1511,7 @@ function SocketIOTransport(Observable, Tools) {
*/
this.getSocket = function getSocket() {
return _socket;
},
};
/**
* Subscribe to a socket event
......@@ -1471,7 +1520,7 @@ function SocketIOTransport(Observable, Tools) {
*/
this.on = function on(event, func) {
return _socket.on(event, func);
},
};
/**
* Subscribe to a socket event but disconnect as soon as it fires.
......@@ -1510,15 +1559,17 @@ function SocketIOTransport(Observable, Tools) {
* @param {Object} scope the scope in which to execute the callback
*/
this.request = function request(channel, data, func, scope) {
if (typeof channel == "string"
&& typeof data != "undefined") {
if (typeof channel == "string" &&
typeof data != "undefined") {
var reqData = {
eventId: Date.now() + Math.floor(Math.random()*1e6),
data: data
},
boundCallback = function () {
func && func.apply(scope || null, arguments);
if (func) {
func.apply(scope || null, arguments);
}
};
this.once(reqData.eventId, boundCallback);
......@@ -1540,9 +1591,9 @@ function SocketIOTransport(Observable, Tools) {
* @returns
*/
this.listen = function listen(channel, data, func, scope) {
if (typeof channel == "string"
&& typeof data != "undefined"
&& typeof func == "function") {
if (typeof channel == "string" &&
typeof data != "undefined" &&
typeof func == "function") {
var reqData = {
eventId: Date.now() + Math.floor(Math.random()*1e6),
......@@ -1550,7 +1601,9 @@ function SocketIOTransport(Observable, Tools) {
keepAlive: true
},
boundCallback = function () {
func && func.apply(scope || null, arguments);
if (func) {
func.apply(scope || null, arguments);
}
},
that = this;
......@@ -1573,3 +1626,504 @@ function SocketIOTransport(Observable, Tools) {
this.setSocket($socket);
};
});
/**
* Olives http://flams.github.com/olives
* The MIT License (MIT)
* Copyright (c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com>
*/
define('Stack',['Tools'],
/**
* @class
* A Stack is a tool for managing DOM elements as groups. Within a group, dom elements
* can be added, removed, moved around. The group can be moved to another parent node
* while keeping the DOM elements in the same order, excluding the parent dom elements's
* children that are not in the Stack.
*/
function Stack() {
var Tools = require("Tools");
return function StackConstructor($parent) {
/**
* The parent DOM element is a documentFragment by default
* @private
*/
var _parent = document.createDocumentFragment(),
/**
* The place where the dom elements hide
* @private
*/
_hidePlace = document.createElement("div"),
/**
* The list of dom elements that are part of the stack
* Helps for excluding elements that are not part of it
* @private
*/
_childNodes = [],
_lastTransit = null;
/**
* Add a DOM element to the stack. It will be appended.
* @param {HTMLElement} dom the DOM element to add
* @returns {HTMLElement} dom
*/
this.add = function add(dom) {
if (!this.has(dom) && dom instanceof HTMLElement) {
_parent.appendChild(dom);
_childNodes.push(dom);
return dom;
} else {
return false;
}
};
/**
* Remove a DOM element from the stack.
* @param {HTMLElement} dom the DOM element to remove
* @returns {HTMLElement} dom
*/
this.remove = function remove(dom) {
var index;
if (this.has(dom)) {
index = _childNodes.indexOf(dom);
_parent.removeChild(dom);
_childNodes.splice(index, 1);
return dom;
} else {
return false;
}
};
/**
* Place a stack by appending its DOM elements to a new parent
* @param {HTMLElement} newParentDom the new DOM element to append the stack to
* @returns {HTMLElement} newParentDom
*/
this.place = function place(newParentDom) {
if (newParentDom instanceof HTMLElement) {
[].slice.call(_parent.childNodes).forEach(function (childDom) {
if (this.has(childDom)) {
newParentDom.appendChild(childDom);
}
}, this);
return this._setParent(newParentDom);
} else {
return false;
}
};
/**
* Move an element up in the stack
* @param {HTMLElement} dom the dom element to move up
* @returns {HTMLElement} dom
*/
this.up = function up(dom) {
if (this.has(dom)) {
var domPosition = this.getPosition(dom);
this.move(dom, domPosition + 1);
return dom;
} else {
return false;
}
};
/**
* Move an element down in the stack
* @param {HTMLElement} dom the dom element to move down
* @returns {HTMLElement} dom
*/
this.down = function down(dom) {
if (this.has(dom)) {
var domPosition = this.getPosition(dom);
this.move(dom, domPosition - 1);
return dom;
} else {
return false;
}
};
/**
* Move an element that is already in the stack to a new position
* @param {HTMLElement} dom the dom element to move
* @param {Number} position the position to which to move the DOM element
* @returns {HTMLElement} dom
*/
this.move = function move(dom, position) {
if (this.has(dom)) {
var domIndex = _childNodes.indexOf(dom);
_childNodes.splice(domIndex, 1);
// Preventing a bug in IE when insertBefore is not given a valid
// second argument
var nextElement = getNextElementInDom(position);
if (nextElement) {
_parent.insertBefore(dom, nextElement);
} else {
_parent.appendChild(dom);
}
_childNodes.splice(position, 0, dom);
return dom;
} else {
return false;
}
};
function getNextElementInDom(position) {
if (position >= _childNodes.length) {
return;
}
var nextElement = _childNodes[position];
if (Tools.toArray(_parent.childNodes).indexOf(nextElement) == -1) {
return getNextElementInDom(position +1);
} else {
return nextElement;
}
}
/**
* Insert a new element at a specific position in the stack
* @param {HTMLElement} dom the dom element to insert
* @param {Number} position the position to which to insert the DOM element
* @returns {HTMLElement} dom
*/
this.insert = function insert(dom, position) {
if (!this.has(dom) && dom instanceof HTMLElement) {
_childNodes.splice(position, 0, dom);
_parent.insertBefore(dom, _parent.childNodes[position]);
return dom;
} else {
return false;
}
};
/**
* Get the position of an element in the stack
* @param {HTMLElement} dom the dom to get the position from
* @returns {HTMLElement} dom
*/
this.getPosition = function getPosition(dom) {
return _childNodes.indexOf(dom);
};
/**
* Count the number of elements in a stack
* @returns {Number} the number of items
*/
this.count = function count() {
return _parent.childNodes.length;
};
/**
* Tells if a DOM element is in the stack
* @param {HTMLElement} dom the dom to tell if its in the stack
* @returns {HTMLElement} dom
*/
this.has = function has(childDom) {
return this.getPosition(childDom) >= 0;
};
/**
* Hide a dom element that was previously added to the stack
* It will be taken out of the dom until displayed again
* @param {HTMLElement} dom the dom to hide
* @return {boolean} if dom element is in the stack
*/
this.hide = function hide(dom) {
if (this.has(dom)) {
_hidePlace.appendChild(dom);
return true;
} else {
return false;
}
};
/**
* Show a dom element that was previously hidden
* It will be added back to the dom
* @param {HTMLElement} dom the dom to show
* @return {boolean} if dom element is current hidden
*/
this.show = function show(dom) {
if (this.has(dom) && dom.parentNode === _hidePlace) {
this.move(dom, _childNodes.indexOf(dom));
return true;
} else {
return false;
}
};
/**
* Helper function for hiding all the dom elements
*/
this.hideAll = function hideAll() {
_childNodes.forEach(this.hide, this);
};
/**
* Helper function for showing all the dom elements
*/
this.showAll = function showAll() {
_childNodes.forEach(this.show, this);
};
/**
* Get the parent node that a stack is currently attached to
* @returns {HTMLElement} parent node
*/
this.getParent = function _getParent() {
return _parent;
};
/**
* Set the parent element (without appending the stacks dom elements to)
* @private
*/
this._setParent = function _setParent(parent) {
if (parent instanceof HTMLElement) {
_parent = parent;
return _parent;
} else {
return false;
}
};
/**
* Get the place where the DOM elements are hidden
* @private
*/
this.getHidePlace = function getHidePlace() {
return _hidePlace;
};
/**
* Set the place where the DOM elements are hidden
* @private
*/
this.setHidePlace = function setHidePlace(hidePlace) {
if (hidePlace instanceof HTMLElement) {
_hidePlace = hidePlace;
return true;
} else {
return false;
}
};
/**
* Get the last dom element that the stack transitted to
* @returns {HTMLElement} the last dom element
*/
this.getLastTransit = function getLastTransit() {
return _lastTransit;
};
/**
* Transit between views, will show the new one and hide the previous
* element that the stack transitted to, if any.
* @param {HTMLElement} dom the element to transit to
* @returns {Boolean} false if the element can't be shown
*/
this.transit = function transit(dom) {
if (_lastTransit) {
this.hide(_lastTransit);
}
if (this.show(dom)) {
_lastTransit = dom;
return true;
} else {
return false;
}
};
this._setParent($parent);
};
});
/**
* Olives http://flams.github.com/olives
* The MIT License (MIT)
* Copyright (c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com>
*/
define('LocationRouter',["Router", "Tools"],
/**
* @class
* A locationRouter is a router which navigates to the route defined in the URL and updates this URL
* while navigating. It's a subtype of Emily's Router
*/
function LocationRouter(Router, Tools) {
function LocationRouterConstructor() {
/**
* The handle on the watch
* @private
*/
var _watchHandle,
/**
* The default route to navigate to when nothing is supplied in the url
* @private
*/
_defaultRoute = "",
/**
* The last route that was navigated to
* @private
*/
_lastRoute = window.location.hash;
/**
* Navigates to the current hash or to the default route if none is supplied in the url
* @private
*/
/*jshint validthis:true*/
function doNavigate() {
if (window.location.hash) {
var parsedHash = this.parse(window.location.hash);
this.navigate.apply(this, parsedHash);
} else {
this.navigate(_defaultRoute);
}
}
/**
* Set the default route to navigate to when nothing is defined in the url
* @param {String} defaultRoute the defaultRoute to navigate to
* @returns {Boolean} true if it's not an empty string
*/
this.setDefaultRoute = function setDefaultRoute(defaultRoute) {
if (defaultRoute && typeof defaultRoute == "string") {
_defaultRoute = defaultRoute;
return true;
} else {
return false;
}
};
/**
* Get the currently set default route
* @returns {String} the default route
*/
this.getDefaultRoute = function getDefaultRoute() {
return _defaultRoute;
};
/**
* The function that parses the url to determine the route to navigate to.
* It has a default behavior explained below, but can be overriden as long as
* it has the same contract.
* @param {String} hash the hash coming from window.location.has
* @returns {Array} has to return an array with the list of arguments to call
* navigate with. The first item of the array must be the name of the route.
*
* Example: #album/holiday/2013
* will navigate to the route "album" and give two arguments "holiday" and "2013"
*/
this.parse = function parse(hash) {
return hash.split("#").pop().split("/");
};
/**
* The function that converts, or serialises the route and its arguments to a valid URL.
* It has a default behavior below, but can be overriden as long as it has the same contract.
* @param {Array} args the list of arguments to serialize
* @returns {String} the serialized arguments to add to the url hashmark
*
* Example:
* ["album", "holiday", "2013"];
* will give "album/holiday/2013"
*
*/
this.toUrl = function toUrl(args) {
return args.join("/");
};
/**
* When all the routes and handlers have been defined, start the location router
* so it parses the URL and navigates to the corresponding route.
* It will also start listening to route changes and hashmark changes to navigate.
* While navigating, the hashmark itself will also change to reflect the current route state
*/
this.start = function start(defaultRoute) {
this.setDefaultRoute(defaultRoute);
doNavigate.call(this);
this.bindOnHashChange();
this.bindOnRouteChange();
};
/**
* Remove the events handler for cleaning.
*/
this.destroy = function destroy() {
this.unwatch(_watchHandle);
window.removeEventListener("hashchange", this.boundOnHashChange, true);
};
/**
* Parse the hash and navigate to the corresponding url
* @private
*/
this.onHashChange = function onHashChange() {
if (window.location.hash != _lastRoute) {
doNavigate.call(this);
}
};
/**
* The bound version of onHashChange for add/removeEventListener
* @private
*/
this.boundOnHashChange = this.onHashChange.bind(this);
/**
* Add an event listener to hashchange to navigate to the corresponding route
* when it changes
* @private
*/
this.bindOnHashChange = function bindOnHashChange() {
window.addEventListener("hashchange", this.boundOnHashChange, true);
};
/**
* Watch route change events from the router to update the location
* @private
*/
this.bindOnRouteChange = function bindOnRouteChange() {
_watchHandle = this.watch(this.onRouteChange, this);
};
/**
* The handler for when the route changes
* It updates the location
* @private
*/
this.onRouteChange = function onRouteChange() {
window.location.hash = this.toUrl(Tools.toArray(arguments));
_lastRoute = window.location.hash;
};
this.getLastRoute = function getLastRoute() {
return _lastRoute;
};
}
return function LocationRouterFactory() {
LocationRouterConstructor.prototype = new Router();
return new LocationRouterConstructor();
};
});
......@@ -13,375 +13,406 @@ define(["Store", "Observable", "Tools", "DomUtils"],
*/
function BindPlugin(Store, Observable, Tools, DomUtils) {
return function BindPluginConstructor($model, $bindings) {
/**
* The model to watch
* @private
*/
var _model = null,
/**
* The list of custom bindings
* @private
*/
_bindings = {},
/**
* The list of itemRenderers
* each foreach has its itemRenderer
* @private
*/
_itemRenderers = {};
/**
* The observers handlers
* for debugging only
* @private
*/
this.observers = {};
/**
* Define the model to watch for
* @param {Store} model the model to watch for changes
* @returns {Boolean} true if the model was set
*/
this.setModel = function setModel(model) {
if (model instanceof Store) {
// Set the model
_model = model;
return true;
} else {
return false;
}
};
/**
* Get the store that is watched for
* for debugging only
* @private
* @returns the Store
*/
this.getModel = function getModel() {
return _model;
};
/**
* The item renderer defines a dom node that can be duplicated
* It is made available for debugging purpose, don't use it
* @private
*/
this.ItemRenderer = function ItemRenderer($plugins, $rootNode) {
/**
* The node that will be cloned
* @private
*/
var _node = null,
/**
* The object that contains plugins.name and plugins.apply
* @private
*/
_plugins = null,
/**
* The _rootNode where to append the created items
* @private
*/
_rootNode = null,
/**
* The lower boundary
* @private
*/
_start = null,
/**
* The number of item to display
* @private
*/
_nb = null;
/**
* Set the duplicated node
* @private
*/
this.setRenderer = function setRenderer(node) {
_node = node;
return true;
};
/**
* Returns the node that is going to be used for rendering
* @private
* @returns the node that is duplicated
*/
this.getRenderer = function getRenderer() {
return _node;
};
/**
* Sets the rootNode and gets the node to copy
* @private
* @param {HTMLElement|SVGElement} rootNode
* @returns
*/
this.setRootNode = function setRootNode(rootNode) {
var renderer;
if (DomUtils.isAcceptedType(rootNode)) {
_rootNode = rootNode;
renderer = _rootNode.querySelector("*");
this.setRenderer(renderer);
renderer && _rootNode.removeChild(renderer);
return true;
} else {
return false;
}
};
/**
* Gets the rootNode
* @private
* @returns _rootNode
*/
this.getRootNode = function getRootNode() {
return _rootNode;
};
/**
* Set the plugins objet that contains the name and the apply function
* @private
* @param plugins
* @returns true
*/
this.setPlugins = function setPlugins(plugins) {
_plugins = plugins;
return true;
};
/**
* Get the plugins object
* @private
* @returns the plugins object
*/
this.getPlugins = function getPlugins() {
return _plugins;
};
/**
* The nodes created from the items are stored here
* @private
*/
this.items = new Store([]);
/**
* Set the start limit
* @private
* @param {Number} start the value to start rendering the items from
* @returns the value
*/
this.setStart = function setStart(start) {
return _start = parseInt(start, 10);
};
/**
* Get the start value
* @private
* @returns the start value
*/
this.getStart = function getStart() {
return _start;
};
/**
* Set the number of item to display
* @private
* @param {Number/String} nb the number of item to display or "*" for all
* @returns the value
*/
this.setNb = function setNb(nb) {
return _nb = nb == "*" ? nb : parseInt(nb, 10);
};
/**
* Get the number of item to display
* @private
* @returns the value
*/
this.getNb = function getNb() {
return _nb;
};
/**
* Adds a new item and adds it in the items list
* @private
* @param {Number} id the id of the item
* @returns
*/
this.addItem = function addItem(id) {
var node,
next;
if (typeof id == "number" && !this.items.get(id)) {
node = this.create(id);
if (node) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
next = this.getNextItem(id);
next ? _rootNode.insertBefore(node, next) : _rootNode.appendChild(node);
return true;
} else {
return false;
}
} else {
return false;
}
};
/**
* Get the next item in the item store given an id.
* @private
* @param {Number} id the id to start from
* @returns
*/
this.getNextItem = function getNextItem(id) {
return this.items.alter("slice", id+1).filter(function (value) {
if (DomUtils.isAcceptedType(value)) {
return true;
}
})[0];
};
/**
* Remove an item from the dom and the items list
* @private
* @param {Number} id the id of the item to remove
* @returns
*/
this.removeItem = function removeItem(id) {
var item = this.items.get(id);
if (item) {
_rootNode.removeChild(item);
this.items.set(id);
return true;
} else {
return false;
}
};
/**
* create a new node. Actually makes a clone of the initial one
* and adds pluginname_id to each node, then calls plugins.apply to apply all plugins
* @private
* @param id
* @param pluginName
* @returns the associated node
*/
this.create = function create(id) {
if (_model.has(id)) {
var newNode = _node.cloneNode(true),
nodes = DomUtils.getNodes(newNode);
Tools.toArray(nodes).forEach(function (child) {
child.setAttribute("data-" + _plugins.name+"_id", id);
});
this.items.set(id, newNode);
_plugins.apply(newNode);
return newNode;
}
};
/**
* Renders the dom tree, adds nodes that are in the boundaries
* and removes the others
* @private
* @returns true boundaries are set
*/
this.render = function render() {
// If the number of items to render is all (*)
// Then get the number of items
var _tmpNb = _nb == "*" ? _model.getNbItems() : _nb;
// This will store the items to remove
var marked = [];
// Render only if boundaries have been set
if (_nb !== null && _start !== null) {
// Loop through the existing items
this.items.loop(function (value, idx) {
// If an item is out of the boundary
if (idx < _start || idx >= (_start + _tmpNb) || !_model.has(idx)) {
// Mark it
marked.push(idx);
}
}, this);
// Remove the marked item from the highest id to the lowest
// Doing this will avoid the id change during removal
// (removing id 2 will make id 3 becoming 2)
marked.sort(Tools.compareNumbers).reverse().forEach(this.removeItem, this);
// Now that we have removed the old nodes
// Add the missing one
for (var i=_start, l=_tmpNb+_start; i<l; i++) {
this.addItem(i);
}
return true;
} else {
return false;
}
};
this.setPlugins($plugins);
this.setRootNode($rootNode);
};
/**
* Save an itemRenderer according to its id
* @private
* @param {String} id the id of the itemRenderer
* @param {ItemRenderer} itemRenderer an itemRenderer object
*/
this.setItemRenderer = function setItemRenderer(id, itemRenderer) {
id = id || "default";
_itemRenderers[id] = itemRenderer;
};
/**
* Get an itemRenderer
* @private
* @param {String} id the name of the itemRenderer
* @returns the itemRenderer
*/
this.getItemRenderer = function getItemRenderer(id) {
return _itemRenderers[id];
};
/**
* Expands the inner dom nodes of a given dom node, filling it with model's values
* @param {HTMLElement|SVGElement} node the dom node to apply foreach to
*/
this.foreach = function foreach(node, idItemRenderer, start, nb) {
var itemRenderer = new this.ItemRenderer(this.plugins, node);
itemRenderer.setStart(start || 0);
itemRenderer.setNb(nb || "*");
itemRenderer.render();
// Add the newly created item
"use strict";
return function BindPluginConstructor($model, $bindings) {
/**
* The model to watch
* @private
*/
var _model = null,
/**
* The list of custom bindings
* @private
*/
_bindings = {},
/**
* The list of itemRenderers
* each foreach has its itemRenderer
* @private
*/
_itemRenderers = {},
/**
* The observers handlers
* @private
*/
_observers = {};
/**
* Exposed for debugging purpose
* @private
*/
this.observers = _observers;
function _removeObserversForId(id) {
if (_observers[id]) {
_observers[id].forEach(function (handler) {
_model.unwatchValue(handler);
});
delete _observers[id];
}
}
/**
* Define the model to watch for
* @param {Store} model the model to watch for changes
* @returns {Boolean} true if the model was set
*/
this.setModel = function setModel(model) {
if (model instanceof Store) {
// Set the model
_model = model;
return true;
} else {
return false;
}
};
/**
* Get the store that is watched for
* for debugging only
* @private
* @returns the Store
*/
this.getModel = function getModel() {
return _model;
};
/**
* The item renderer defines a dom node that can be duplicated
* It is made available for debugging purpose, don't use it
* @private
*/
this.ItemRenderer = function ItemRenderer($plugins, $rootNode) {
/**
* The node that will be cloned
* @private
*/
var _node = null,
/**
* The object that contains plugins.name and plugins.apply
* @private
*/
_plugins = null,
/**
* The _rootNode where to append the created items
* @private
*/
_rootNode = null,
/**
* The lower boundary
* @private
*/
_start = null,
/**
* The number of item to display
* @private
*/
_nb = null;
/**
* Set the duplicated node
* @private
*/
this.setRenderer = function setRenderer(node) {
_node = node;
return true;
};
/**
* Returns the node that is going to be used for rendering
* @private
* @returns the node that is duplicated
*/
this.getRenderer = function getRenderer() {
return _node;
};
/**
* Sets the rootNode and gets the node to copy
* @private
* @param {HTMLElement|SVGElement} rootNode
* @returns
*/
this.setRootNode = function setRootNode(rootNode) {
var renderer;
if (DomUtils.isAcceptedType(rootNode)) {
_rootNode = rootNode;
renderer = _rootNode.querySelector("*");
this.setRenderer(renderer);
if (renderer) {
_rootNode.removeChild(renderer);
}
return true;
} else {
return false;
}
};
/**
* Gets the rootNode
* @private
* @returns _rootNode
*/
this.getRootNode = function getRootNode() {
return _rootNode;
};
/**
* Set the plugins objet that contains the name and the apply function
* @private
* @param plugins
* @returns true
*/
this.setPlugins = function setPlugins(plugins) {
_plugins = plugins;
return true;
};
/**
* Get the plugins object
* @private
* @returns the plugins object
*/
this.getPlugins = function getPlugins() {
return _plugins;
};
/**
* The nodes created from the items are stored here
* @private
*/
this.items = {};
/**
* Set the start limit
* @private
* @param {Number} start the value to start rendering the items from
* @returns the value
*/
this.setStart = function setStart(start) {
_start = parseInt(start, 10);
return _start;
};
/**
* Get the start value
* @private
* @returns the start value
*/
this.getStart = function getStart() {
return _start;
};
/**
* Set the number of item to display
* @private
* @param {Number/String} nb the number of item to display or "*" for all
* @returns the value
*/
this.setNb = function setNb(nb) {
_nb = nb == "*" ? nb : parseInt(nb, 10);
return _nb;
};
/**
* Get the number of item to display
* @private
* @returns the value
*/
this.getNb = function getNb() {
return _nb;
};
/**
* Adds a new item and adds it in the items list
* @private
* @param {Number} id the id of the item
* @returns
*/
this.addItem = function addItem(id) {
var node,
next;
if (typeof id == "number" && !this.items[id]) {
next = this.getNextItem(id);
node = this.create(id);
if (node) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
if (next) {
_rootNode.insertBefore(node, next);
} else {
_rootNode.appendChild(node);
}
return true;
} else {
return false;
}
} else {
return false;
}
};
/**
* Get the next item in the item store given an id.
* @private
* @param {Number} id the id to start from
* @returns
*/
this.getNextItem = function getNextItem(id) {
var keys = Object.keys(this.items).map(function (string) {
return Number(string);
}),
closest = Tools.closestGreater(id, keys),
closestId = keys[closest];
// Only return if different
if (closestId != id) {
return this.items[closestId];
} else {
return;
}
};
/**
* Remove an item from the dom and the items list
* @private
* @param {Number} id the id of the item to remove
* @returns
*/
this.removeItem = function removeItem(id) {
var item = this.items[id];
if (item) {
_rootNode.removeChild(item);
delete this.items[id];
_removeObserversForId(id);
return true;
} else {
return false;
}
};
/**
* create a new node. Actually makes a clone of the initial one
* and adds pluginname_id to each node, then calls plugins.apply to apply all plugins
* @private
* @param id
* @param pluginName
* @returns the associated node
*/
this.create = function create(id) {
if (_model.has(id)) {
var newNode = _node.cloneNode(true),
nodes = DomUtils.getNodes(newNode);
Tools.toArray(nodes).forEach(function (child) {
child.setAttribute("data-" + _plugins.name+"_id", id);
});
this.items[id] = newNode;
_plugins.apply(newNode);
return newNode;
}
};
/**
* Renders the dom tree, adds nodes that are in the boundaries
* and removes the others
* @private
* @returns true boundaries are set
*/
this.render = function render() {
// If the number of items to render is all (*)
// Then get the number of items
var _tmpNb = _nb == "*" ? _model.getNbItems() : _nb;
// This will store the items to remove
var marked = [];
// Render only if boundaries have been set
if (_nb !== null && _start !== null) {
// Loop through the existing items
Tools.loop(this.items, function (value, idx) {
// If an item is out of the boundary
idx = Number(idx);
if (idx < _start || idx >= (_start + _tmpNb) || !_model.has(idx)) {
// Mark it
marked.push(idx);
}
}, this);
// Remove the marked item from the highest id to the lowest
// Doing this will avoid the id change during removal
// (removing id 2 will make id 3 becoming 2)
marked.sort(Tools.compareNumbers).reverse().forEach(this.removeItem, this);
// Now that we have removed the old nodes
// Add the missing one
for (var i=_start, l=_tmpNb+_start; i<l; i++) {
this.addItem(i);
}
return true;
} else {
return false;
}
};
this.setPlugins($plugins);
this.setRootNode($rootNode);
};
/**
* Save an itemRenderer according to its id
* @private
* @param {String} id the id of the itemRenderer
* @param {ItemRenderer} itemRenderer an itemRenderer object
*/
this.setItemRenderer = function setItemRenderer(id, itemRenderer) {
id = id || "default";
_itemRenderers[id] = itemRenderer;
};
/**
* Get an itemRenderer
* @private
* @param {String} id the name of the itemRenderer
* @returns the itemRenderer
*/
this.getItemRenderer = function getItemRenderer(id) {
return _itemRenderers[id];
};
/**
* Expands the inner dom nodes of a given dom node, filling it with model's values
* @param {HTMLElement|SVGElement} node the dom node to apply foreach to
*/
this.foreach = function foreach(node, idItemRenderer, start, nb) {
var itemRenderer = new this.ItemRenderer(this.plugins, node);
itemRenderer.setStart(start || 0);
itemRenderer.setNb(nb || "*");
itemRenderer.render();
// Add the newly created item
_model.watch("added", itemRenderer.render, itemRenderer);
// If an item is deleted
// If an item is deleted
_model.watch("deleted", function (idx) {
itemRenderer.render();
itemRenderer.render();
// Also remove all observers
this.observers[idx] && this.observers[idx].forEach(function (handler) {
_model.unwatchValue(handler);
}, this);
delete this.observers[idx];
_removeObserversForId(idx);
},this);
this.setItemRenderer(idItemRenderer, itemRenderer);
......@@ -394,13 +425,13 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns true if the foreach exists
*/
this.updateStart = function updateStart(id, start) {
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.setStart(start);
return true;
} else {
return false;
}
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.setStart(start);
return true;
} else {
return false;
}
};
/**
......@@ -410,13 +441,13 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns true if the foreach exists
*/
this.updateNb = function updateNb(id, nb) {
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.setNb(nb);
return true;
} else {
return false;
}
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.setNb(nb);
return true;
} else {
return false;
}
};
/**
......@@ -425,201 +456,201 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns true if the foreach exists
*/
this.refresh = function refresh(id) {
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.render();
return true;
} else {
return false;
}
var itemRenderer = this.getItemRenderer(id);
if (itemRenderer) {
itemRenderer.render();
return true;
} else {
return false;
}
};
/**
* Both ways binding between a dom node attributes and the model
* @param {HTMLElement|SVGElement} node the dom node to apply the plugin to
* @param {String} name the name of the property to look for in the model's value
* @returns
*/
this.bind = function bind(node, property, name) {
// Name can be unset if the value of a row is plain text
name = name || "";
// In case of an array-like model the id is the index of the model's item to look for.
// The _id is added by the foreach function
var id = node.getAttribute("data-" + this.plugins.name+"_id"),
// Else, it is the first element of the following
split = name.split("."),
// So the index of the model is either id or the first element of split
modelIdx = id || split.shift(),
// And the name of the property to look for in the value is
prop = id ? name : split.join("."),
// Get the model's value
get = Tools.getNestedProperty(_model.get(modelIdx), prop),
// When calling bind like bind:newBinding,param1, param2... we need to get them
extraParam = Tools.toArray(arguments).slice(3);
// 0 and false are acceptable falsy values
if (get || get === 0 || get === false) {
// If the binding hasn't been overriden
if (!this.execBinding.apply(this,
[node, property, get]
// Extra params are passed to the new binding too
.concat(extraParam))) {
// Execute the default one which is a simple assignation
//node[property] = get;
DomUtils.setAttribute(node, property, get);
}
}
// Only watch for changes (double way data binding) if the binding
// has not been redefined
if (!this.hasBinding(property)) {
node.addEventListener("change", function (event) {
if (_model.has(modelIdx)) {
if (prop) {
_model.update(modelIdx, name, node[property]);
} else {
_model.set(modelIdx, node[property]);
}
}
}, true);
}
// Watch for changes
this.observers[modelIdx] = this.observers[modelIdx] || [];
this.observers[modelIdx].push(_model.watchValue(modelIdx, function (value) {
if (!this.execBinding.apply(this,
[node, property, Tools.getNestedProperty(value, prop)]
// passing extra params too
.concat(extraParam))) {
//node[property] = Tools.getNestedProperty(value, prop);
DomUtils.setAttribute(node, property, Tools.getNestedProperty(value, prop));
}
}, this));
};
/**
* Set the node's value into the model, the name is the model's property
* @private
* @param {HTMLElement|SVGElement} node
* @returns true if the property is added
*/
this.set = function set(node) {
if (DomUtils.isAcceptedType(node) && node.name) {
_model.set(node.name, node.value);
return true;
} else {
return false;
}
};
this.getItemIndex = function getElementId(dom) {
var dataset = DomUtils.getDataset(dom);
if (dataset && typeof dataset[this.plugins.name + "_id"] != "undefined") {
return +dataset[this.plugins.name + "_id"];
} else {
return false;
}
};
/**
* Prevents the submit and set the model with all form's inputs
* @param {HTMLFormElement} form
* @returns true if valid form
*/
this.form = function form(form) {
if (form && form.nodeName == "FORM") {
var that = this;
form.addEventListener("submit", function (event) {
Tools.toArray(form.querySelectorAll("[name]")).forEach(that.set, that);
event.preventDefault();
}, true);
return true;
} else {
return false;
}
};
/**
* Add a new way to handle a binding
* @param {String} name of the binding
* @param {Function} binding the function to handle the binding
* @returns
*/
this.addBinding = function addBinding(name, binding) {
if (name && typeof name == "string" && typeof binding == "function") {
_bindings[name] = binding;
return true;
} else {
return false;
}
};
/**
* Execute a binding
* Only used by the plugin
* @private
* @param {HTMLElement} node the dom node on which to execute the binding
* @param {String} name the name of the binding
* @param {Any type} value the value to pass to the function
* @returns
*/
this.execBinding = function execBinding(node, name) {
if (this.hasBinding(name)) {
_bindings[name].apply(node, Array.prototype.slice.call(arguments, 2));
return true;
} else {
return false;
}
};
/**
* Check if the binding exists
* @private
* @param {String} name the name of the binding
* @returns
*/
this.hasBinding = function hasBinding(name) {
return _bindings.hasOwnProperty(name);
};
/**
* Get a binding
* For debugging only
* @private
* @param {String} name the name of the binding
* @returns
*/
this.getBinding = function getBinding(name) {
return _bindings[name];
};
/**
* Add multiple binding at once
* @param {Object} list the list of bindings to add
* @returns
*/
this.addBindings = function addBindings(list) {
return Tools.loop(list, function (binding, name) {
this.addBinding(name, binding);
}, this);
};
// Inits the model
this.setModel($model);
// Inits bindings
this.addBindings($bindings);
};
/**
* Both ways binding between a dom node attributes and the model
* @param {HTMLElement|SVGElement} node the dom node to apply the plugin to
* @param {String} name the name of the property to look for in the model's value
* @returns
*/
this.bind = function bind(node, property, name) {
// Name can be unset if the value of a row is plain text
name = name || "";
// In case of an array-like model the id is the index of the model's item to look for.
// The _id is added by the foreach function
var id = node.getAttribute("data-" + this.plugins.name+"_id"),
// Else, it is the first element of the following
split = name.split("."),
// So the index of the model is either id or the first element of split
modelIdx = id || split.shift(),
// And the name of the property to look for in the value is
prop = id ? name : split.join("."),
// Get the model's value
get = Tools.getNestedProperty(_model.get(modelIdx), prop),
// When calling bind like bind:newBinding,param1, param2... we need to get them
extraParam = Tools.toArray(arguments).slice(3);
// 0 and false are acceptable falsy values
if (get || get === 0 || get === false) {
// If the binding hasn't been overriden
if (!this.execBinding.apply(this,
[node, property, get]
// Extra params are passed to the new binding too
.concat(extraParam))) {
// Execute the default one which is a simple assignation
//node[property] = get;
DomUtils.setAttribute(node, property, get);
}
}
// Only watch for changes (double way data binding) if the binding
// has not been redefined
if (!this.hasBinding(property)) {
node.addEventListener("change", function (event) {
if (_model.has(modelIdx)) {
if (prop) {
_model.update(modelIdx, name, node[property]);
} else {
_model.set(modelIdx, node[property]);
}
}
}, true);
}
// Watch for changes
this.observers[modelIdx] = this.observers[modelIdx] || [];
this.observers[modelIdx].push(_model.watchValue(modelIdx, function (value) {
if (!this.execBinding.apply(this,
[node, property, Tools.getNestedProperty(value, prop)]
// passing extra params too
.concat(extraParam))) {
//node[property] = Tools.getNestedProperty(value, prop);
DomUtils.setAttribute(node, property, Tools.getNestedProperty(value, prop));
}
}, this));
};
/**
* Set the node's value into the model, the name is the model's property
* @private
* @param {HTMLElement|SVGElement} node
* @returns true if the property is added
*/
this.set = function set(node) {
if (DomUtils.isAcceptedType(node) && node.name) {
_model.set(node.name, node.value);
return true;
} else {
return false;
}
};
this.getItemIndex = function getElementId(dom) {
var dataset = DomUtils.getDataset(dom);
if (dataset && typeof dataset[this.plugins.name + "_id"] != "undefined") {
return +dataset[this.plugins.name + "_id"];
} else {
return false;
}
};
/**
* Prevents the submit and set the model with all form's inputs
* @param {HTMLFormElement} DOMfrom
* @returns true if valid form
*/
this.form = function form(DOMform) {
if (DOMform && DOMform.nodeName == "FORM") {
var that = this;
DOMform.addEventListener("submit", function (event) {
Tools.toArray(DOMform.querySelectorAll("[name]")).forEach(that.set, that);
event.preventDefault();
}, true);
return true;
} else {
return false;
}
};
/**
* Add a new way to handle a binding
* @param {String} name of the binding
* @param {Function} binding the function to handle the binding
* @returns
*/
this.addBinding = function addBinding(name, binding) {
if (name && typeof name == "string" && typeof binding == "function") {
_bindings[name] = binding;
return true;
} else {
return false;
}
};
/**
* Execute a binding
* Only used by the plugin
* @private
* @param {HTMLElement} node the dom node on which to execute the binding
* @param {String} name the name of the binding
* @param {Any type} value the value to pass to the function
* @returns
*/
this.execBinding = function execBinding(node, name) {
if (this.hasBinding(name)) {
_bindings[name].apply(node, Array.prototype.slice.call(arguments, 2));
return true;
} else {
return false;
}
};
/**
* Check if the binding exists
* @private
* @param {String} name the name of the binding
* @returns
*/
this.hasBinding = function hasBinding(name) {
return _bindings.hasOwnProperty(name);
};
/**
* Get a binding
* For debugging only
* @private
* @param {String} name the name of the binding
* @returns
*/
this.getBinding = function getBinding(name) {
return _bindings[name];
};
/**
* Add multiple binding at once
* @param {Object} list the list of bindings to add
* @returns
*/
this.addBindings = function addBindings(list) {
return Tools.loop(list, function (binding, name) {
this.addBinding(name, binding);
}, this);
};
// Inits the model
this.setModel($model);
// Inits bindings
this.addBindings($bindings);
};
});
......@@ -6,6 +6,8 @@
define(["Tools"], function (Tools) {
"use strict";
return {
/**
* Returns a NodeList including the given dom node,
......
......@@ -14,121 +14,123 @@ define(["DomUtils"],
*/
function EventPlugin(Utils) {
/**
* The event plugin constructor.
* ex: new EventPlugin({method: function(){} ...}, false);
* @param {Object} the object that has the event handling methods
* @param {Boolean} $isMobile if the event handler has to map with touch events
*/
return function EventPluginConstructor($parent, $isMobile) {
/**
* The parent callback
* @private
*/
var _parent = null,
/**
* The mapping object.
* @private
*/
_map = {
"mousedown" : "touchstart",
"mouseup" : "touchend",
"mousemove" : "touchmove"
},
/**
* Is touch device.
* @private
*/
_isMobile = !!$isMobile;
/**
* Add mapped event listener (for testing purpose).
* @private
*/
this.addEventListener = function addEventListener(node, event, callback, useCapture) {
node.addEventListener(this.map(event), callback, !!useCapture);
};
/**
* Listen to DOM events.
* @param {Object} node DOM node
* @param {String} name event's name
* @param {String} listener callback's name
* @param {String} useCapture string
*/
this.listen = function listen(node, name, listener, useCapture) {
this.addEventListener(node, name, function(e){
_parent[listener].call(_parent, e, node);
}, !!useCapture);
};
/**
* Delegate the event handling to a parent DOM element
* @param {Object} node DOM node
* @param {String} selector CSS3 selector to the element that listens to the event
* @param {String} name event's name
* @param {String} listener callback's name
* @param {String} useCapture string
*/
this.delegate = function delegate(node, selector, name, listener, useCapture) {
this.addEventListener(node, name, function(event){
if (Utils.matches(node, selector, event.target)) {
_parent[listener].call(_parent, event, node);
}
}, !!useCapture);
};
/**
* Get the parent object.
* @return {Object} the parent object
*/
this.getParent = function getParent() {
return _parent;
};
/**
* Set the parent object.
* The parent object is an object which the functions are called by node listeners.
* @param {Object} the parent object
* @return true if object has been set
*/
this.setParent = function setParent(parent) {
if (parent instanceof Object){
_parent = parent;
return true;
}
return false;
};
/**
* Get event mapping.
* @param {String} event's name
* @return the mapped event's name
*/
this.map = function map(name) {
return _isMobile ? (_map[name] || name) : name;
};
/**
* Set event mapping.
* @param {String} event's name
* @param {String} event's value
* @return true if mapped
*/
this.setMap = function setMap(name, value) {
if (typeof name == "string" &&
typeof value == "string") {
_map[name] = value;
return true;
}
return false;
};
//init
this.setParent($parent);
};
"use strict";
/**
* The event plugin constructor.
* ex: new EventPlugin({method: function(){} ...}, false);
* @param {Object} the object that has the event handling methods
* @param {Boolean} $isMobile if the event handler has to map with touch events
*/
return function EventPluginConstructor($parent, $isMobile) {
/**
* The parent callback
* @private
*/
var _parent = null,
/**
* The mapping object.
* @private
*/
_map = {
"mousedown" : "touchstart",
"mouseup" : "touchend",
"mousemove" : "touchmove"
},
/**
* Is touch device.
* @private
*/
_isMobile = !!$isMobile;
/**
* Add mapped event listener (for testing purpose).
* @private
*/
this.addEventListener = function addEventListener(node, event, callback, useCapture) {
node.addEventListener(this.map(event), callback, !!useCapture);
};
/**
* Listen to DOM events.
* @param {Object} node DOM node
* @param {String} name event's name
* @param {String} listener callback's name
* @param {String} useCapture string
*/
this.listen = function listen(node, name, listener, useCapture) {
this.addEventListener(node, name, function(e){
_parent[listener].call(_parent, e, node);
}, !!useCapture);
};
/**
* Delegate the event handling to a parent DOM element
* @param {Object} node DOM node
* @param {String} selector CSS3 selector to the element that listens to the event
* @param {String} name event's name
* @param {String} listener callback's name
* @param {String} useCapture string
*/
this.delegate = function delegate(node, selector, name, listener, useCapture) {
this.addEventListener(node, name, function(event){
if (Utils.matches(node, selector, event.target)) {
_parent[listener].call(_parent, event, node);
}
}, !!useCapture);
};
/**
* Get the parent object.
* @return {Object} the parent object
*/
this.getParent = function getParent() {
return _parent;
};
/**
* Set the parent object.
* The parent object is an object which the functions are called by node listeners.
* @param {Object} the parent object
* @return true if object has been set
*/
this.setParent = function setParent(parent) {
if (parent instanceof Object){
_parent = parent;
return true;
}
return false;
};
/**
* Get event mapping.
* @param {String} event's name
* @return the mapped event's name
*/
this.map = function map(name) {
return _isMobile ? (_map[name] || name) : name;
};
/**
* Set event mapping.
* @param {String} event's name
* @param {String} event's value
* @return true if mapped
*/
this.setMap = function setMap(name, value) {
if (typeof name == "string" &&
typeof value == "string") {
_map[name] = value;
return true;
}
return false;
};
//init
this.setParent($parent);
};
});
......@@ -15,6 +15,8 @@ define(["Store", "Tools"],
*/
function LocalStore(Store, Tools) {
"use strict";
function LocalStoreConstructor() {
/**
......@@ -96,7 +98,7 @@ function LocalStore(Store, Tools) {
return function LocalStoreFactory(init) {
LocalStoreConstructor.prototype = new Store(init);
return new LocalStoreConstructor;
return new LocalStoreConstructor();
};
});
......@@ -13,179 +13,185 @@ define(["StateMachine", "Store", "Plugins", "DomUtils", "Tools"],
*/
function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
return function OObjectConstructor(otherStore) {
/**
* This function creates the dom of the UI from its template
* It then queries the dom for data- attributes
* It can't be executed if the template is not set
* @private
*/
var render = function render(UI) {
// The place where the template will be created
// is either the currentPlace where the node is placed
// or a temporary div
var baseNode = _currentPlace || document.createElement("div");
// If the template is set
if (UI.template) {
// In this function, the thisObject is the UI's prototype
// UI is the UI that has OObject as prototype
if (typeof UI.template == "string") {
// Let the browser do the parsing, can't be faster & easier.
baseNode.innerHTML = UI.template.trim();
} else if (DomUtils.isAcceptedType(UI.template)) {
// If it's already an HTML element
baseNode.appendChild(UI.template);
}
// The UI must be placed in a unique dom node
// If not, there can't be multiple UIs placed in the same parentNode
// as it wouldn't be possible to know which node would belong to which UI
// This is probably a DOM limitation.
if (baseNode.childNodes.length > 1) {
throw Error("UI.template should have only one parent node");
} else {
UI.dom = baseNode.childNodes[0];
}
UI.plugins.apply(UI.dom);
} else {
// An explicit message I hope
throw Error("UI.template must be set prior to render");
}
},
/**
* This function appends the dom tree to the given dom node.
* This dom node should be somewhere in the dom of the application
* @private
*/
place = function place(UI, place, beforeNode) {
if (place) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
beforeNode ? place.insertBefore(UI.dom, beforeNode) : place.appendChild(UI.dom);
// Also save the new place, so next renderings
// will be made inside it
_currentPlace = place;
}
},
/**
* Does rendering & placing in one function
* @private
*/
renderNPlace = function renderNPlace(UI, dom) {
render(UI);
place.apply(null, Tools.toArray(arguments));
},
/**
* This stores the current place
* If this is set, this is the place where new templates
* will be appended
* @private
*/
_currentPlace = null,
/**
* The UI's stateMachine.
* Much better than if(stuff) do(stuff) else if (!stuff and stuff but not stouff) do (otherstuff)
* Please open an issue if you want to propose a better one
* @private
*/
_stateMachine = new StateMachine("Init", {
"Init": [["render", render, this, "Rendered"],
["place", renderNPlace, this, "Rendered"]],
"Rendered": [["place", place, this],
["render", render, this]]
});
/**
* The UI's Store
* It has set/get/del/has/watch/unwatch methods
* @see Emily's doc for more info on how it works.
*/
this.model = otherStore instanceof Store ? otherStore : new Store;
/**
* The module that will manage the plugins for this UI
* @see Olives/Plugins' doc for more info on how it works.
*/
this.plugins = new Plugins();
/**
* Describes the template, can either be like "&lt;p&gt;&lt;/p&gt;" or HTMLElements
* @type string or HTMLElement|SVGElement
*/
this.template = null;
/**
* This will hold the dom nodes built from the template.
*/
this.dom = null;
/**
* Place the UI in a given dom node
* @param node the node on which to append the UI
* @param beforeNode the dom before which to append the UI
*/
this.place = function place(node, beforeNode) {
_stateMachine.event("place", this, node, beforeNode);
};
/**
* Renders the template to dom nodes and applies the plugins on it
* It requires the template to be set first
*/
this.render = function render() {
_stateMachine.event("render", this);
};
/**
* Set the UI's template from a DOM element
* @param {HTMLElement|SVGElement} dom the dom element that'll become the template of the UI
* @returns true if dom is an HTMLElement|SVGElement
*/
this.setTemplateFromDom = function setTemplateFromDom(dom) {
if (DomUtils.isAcceptedType(dom)) {
this.template = dom;
return true;
} else {
return false;
}
};
/**
* Transforms dom nodes into a UI.
* It basically does a setTemplateFromDOM, then a place
* It's a helper function
* @param {HTMLElement|SVGElement} node the dom to transform to a UI
* @returns true if dom is an HTMLElement|SVGElement
*/
this.alive = function alive(dom) {
if (DomUtils.isAcceptedType(dom)) {
this.setTemplateFromDom(dom);
this.place(dom.parentNode, dom.nextElementSibling);
return true;
} else {
return false;
}
};
/**
* Get the current dom node where the UI is placed.
* for debugging purpose
* @private
* @return {HTMLElement} node the dom where the UI is placed.
*/
this.getCurrentPlace = function(){
return _currentPlace;
};
};
"use strict";
return function OObjectConstructor(otherStore) {
/**
* This function creates the dom of the UI from its template
* It then queries the dom for data- attributes
* It can't be executed if the template is not set
* @private
*/
var render = function render(UI) {
// The place where the template will be created
// is either the currentPlace where the node is placed
// or a temporary div
var baseNode = _currentPlace || document.createElement("div");
// If the template is set
if (UI.template) {
// In this function, the thisObject is the UI's prototype
// UI is the UI that has OObject as prototype
if (typeof UI.template == "string") {
// Let the browser do the parsing, can't be faster & easier.
baseNode.innerHTML = UI.template.trim();
} else if (DomUtils.isAcceptedType(UI.template)) {
// If it's already an HTML element
baseNode.appendChild(UI.template);
}
// The UI must be placed in a unique dom node
// If not, there can't be multiple UIs placed in the same parentNode
// as it wouldn't be possible to know which node would belong to which UI
// This is probably a DOM limitation.
if (baseNode.childNodes.length > 1) {
throw new Error("UI.template should have only one parent node");
} else {
UI.dom = baseNode.childNodes[0];
}
UI.plugins.apply(UI.dom);
} else {
// An explicit message I hope
throw new Error("UI.template must be set prior to render");
}
},
/**
* This function appends the dom tree to the given dom node.
* This dom node should be somewhere in the dom of the application
* @private
*/
place = function place(UI, DOMplace, beforeNode) {
if (DOMplace) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
if (beforeNode) {
DOMplace.insertBefore(UI.dom, beforeNode);
} else {
DOMplace.appendChild(UI.dom);
}
// Also save the new place, so next renderings
// will be made inside it
_currentPlace = DOMplace;
}
},
/**
* Does rendering & placing in one function
* @private
*/
renderNPlace = function renderNPlace(UI, dom) {
render(UI);
place.apply(null, Tools.toArray(arguments));
},
/**
* This stores the current place
* If this is set, this is the place where new templates
* will be appended
* @private
*/
_currentPlace = null,
/**
* The UI's stateMachine.
* Much better than if(stuff) do(stuff) else if (!stuff and stuff but not stouff) do (otherstuff)
* Please open an issue if you want to propose a better one
* @private
*/
_stateMachine = new StateMachine("Init", {
"Init": [["render", render, this, "Rendered"],
["place", renderNPlace, this, "Rendered"]],
"Rendered": [["place", place, this],
["render", render, this]]
});
/**
* The UI's Store
* It has set/get/del/has/watch/unwatch methods
* @see Emily's doc for more info on how it works.
*/
this.model = otherStore instanceof Store ? otherStore : new Store();
/**
* The module that will manage the plugins for this UI
* @see Olives/Plugins' doc for more info on how it works.
*/
this.plugins = new Plugins();
/**
* Describes the template, can either be like "&lt;p&gt;&lt;/p&gt;" or HTMLElements
* @type string or HTMLElement|SVGElement
*/
this.template = null;
/**
* This will hold the dom nodes built from the template.
*/
this.dom = null;
/**
* Place the UI in a given dom node
* @param node the node on which to append the UI
* @param beforeNode the dom before which to append the UI
*/
this.place = function place(node, beforeNode) {
_stateMachine.event("place", this, node, beforeNode);
};
/**
* Renders the template to dom nodes and applies the plugins on it
* It requires the template to be set first
*/
this.render = function render() {
_stateMachine.event("render", this);
};
/**
* Set the UI's template from a DOM element
* @param {HTMLElement|SVGElement} dom the dom element that'll become the template of the UI
* @returns true if dom is an HTMLElement|SVGElement
*/
this.setTemplateFromDom = function setTemplateFromDom(dom) {
if (DomUtils.isAcceptedType(dom)) {
this.template = dom;
return true;
} else {
return false;
}
};
/**
* Transforms dom nodes into a UI.
* It basically does a setTemplateFromDOM, then a place
* It's a helper function
* @param {HTMLElement|SVGElement} node the dom to transform to a UI
* @returns true if dom is an HTMLElement|SVGElement
*/
this.alive = function alive(dom) {
if (DomUtils.isAcceptedType(dom)) {
this.setTemplateFromDom(dom);
this.place(dom.parentNode, dom.nextElementSibling);
return true;
} else {
return false;
}
};
/**
* Get the current dom node where the UI is placed.
* for debugging purpose
* @private
* @return {HTMLElement} node the dom where the UI is placed.
*/
this.getCurrentPlace = function(){
return _currentPlace;
};
};
});
......@@ -12,77 +12,79 @@ define(["OObject", "Tools"],
*/
function PlacePlugin(OObject, Tools) {
/**
* Intilialize a Place.plugin with a list of OObjects
* @param {Object} $uis a list of OObjects such as:
* {
* "header": new OObject(),
* "list": new OObject()
* }
* @Constructor
*/
return function PlacePluginConstructor($uis) {
"use strict";
/**
* The list of uis currently set in this place plugin
* @private
*/
var _uis = {};
/**
* Intilialize a Place.plugin with a list of OObjects
* @param {Object} $uis a list of OObjects such as:
* {
* "header": new OObject(),
* "list": new OObject()
* }
* @Constructor
*/
return function PlacePluginConstructor($uis) {
/**
* Attach an OObject to this DOM element
* @param {HTML|SVGElement} node the dom node where to attach the OObject
* @param {String} the name of the OObject to attach
* @throws {NoSuchOObject} an error if there's no OObject for the given name
*/
this.place = function place(node, name) {
if (_uis[name] instanceof OObject) {
_uis[name].place(node);
} else {
throw new Error(name + " is not an OObject UI in place:"+name);
}
};
/**
* The list of uis currently set in this place plugin
* @private
*/
var _uis = {};
/**
* Add an OObject that can be attached to a dom element
* @param {String} the name of the OObject to add to the list
* @param {OObject} ui the OObject to add the list
* @returns {Boolean} true if the OObject was added
*/
this.set = function set(name, ui) {
if (typeof name == "string" && ui instanceof OObject) {
_uis[name] = ui;
return true;
} else {
return false;
}
};
/**
* Attach an OObject to this DOM element
* @param {HTML|SVGElement} node the dom node where to attach the OObject
* @param {String} the name of the OObject to attach
* @throws {NoSuchOObject} an error if there's no OObject for the given name
*/
this.place = function place(node, name) {
if (_uis[name] instanceof OObject) {
_uis[name].place(node);
} else {
throw new Error(name + " is not an OObject UI in place:"+name);
}
};
/**
* Add multiple dom elements at once
* @param {Object} $uis a list of OObjects such as:
* {
* "header": new OObject(),
* "list": new OObject()
* }
*/
this.setAll = function setAll(uis) {
Tools.loop(uis, function (ui, name) {
this.set(name, ui);
}, this);
};
/**
* Add an OObject that can be attached to a dom element
* @param {String} the name of the OObject to add to the list
* @param {OObject} ui the OObject to add the list
* @returns {Boolean} true if the OObject was added
*/
this.set = function set(name, ui) {
if (typeof name == "string" && ui instanceof OObject) {
_uis[name] = ui;
return true;
} else {
return false;
}
};
/**
* Returns an OObject from the list given its name
* @param {String} the name of the OObject to get
* @returns {OObject} OObject for the given name
*/
this.get = function get(name) {
return _uis[name];
};
/**
* Add multiple dom elements at once
* @param {Object} $uis a list of OObjects such as:
* {
* "header": new OObject(),
* "list": new OObject()
* }
*/
this.setAll = function setAll(uis) {
Tools.loop(uis, function (ui, name) {
this.set(name, ui);
}, this);
};
this.setAll($uis);
/**
* Returns an OObject from the list given its name
* @param {String} the name of the OObject to get
* @returns {OObject} OObject for the given name
*/
this.get = function get(name) {
return _uis[name];
};
};
this.setAll($uis);
};
});
......@@ -17,6 +17,8 @@ define(["Tools", "DomUtils"],
*/
function Plugins(Tools, DomUtils) {
"use strict";
return function PluginsConstructor($plugins) {
/**
......
......@@ -12,6 +12,8 @@ define(["Observable", "Tools"],
*/
function SocketIOTransport(Observable, Tools) {
"use strict";
/**
* Defines the SocketIOTransport
* @private
......@@ -47,7 +49,7 @@ function SocketIOTransport(Observable, Tools) {
*/
this.getSocket = function getSocket() {
return _socket;
},
};
/**
* Subscribe to a socket event
......@@ -56,7 +58,7 @@ function SocketIOTransport(Observable, Tools) {
*/
this.on = function on(event, func) {
return _socket.on(event, func);
},
};
/**
* Subscribe to a socket event but disconnect as soon as it fires.
......@@ -95,15 +97,17 @@ function SocketIOTransport(Observable, Tools) {
* @param {Object} scope the scope in which to execute the callback
*/
this.request = function request(channel, data, func, scope) {
if (typeof channel == "string"
&& typeof data != "undefined") {
if (typeof channel == "string" &&
typeof data != "undefined") {
var reqData = {
eventId: Date.now() + Math.floor(Math.random()*1e6),
data: data
},
boundCallback = function () {
func && func.apply(scope || null, arguments);
if (func) {
func.apply(scope || null, arguments);
}
};
this.once(reqData.eventId, boundCallback);
......@@ -125,9 +129,9 @@ function SocketIOTransport(Observable, Tools) {
* @returns
*/
this.listen = function listen(channel, data, func, scope) {
if (typeof channel == "string"
&& typeof data != "undefined"
&& typeof func == "function") {
if (typeof channel == "string" &&
typeof data != "undefined" &&
typeof func == "function") {
var reqData = {
eventId: Date.now() + Math.floor(Math.random()*1e6),
......@@ -135,7 +139,9 @@ function SocketIOTransport(Observable, Tools) {
keepAlive: true
},
boundCallback = function () {
func && func.apply(scope || null, arguments);
if (func) {
func.apply(scope || null, arguments);
}
},
that = this;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment