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 @@ ...@@ -2,8 +2,8 @@
"name": "todomvc-olives", "name": "todomvc-olives",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"olives": "~1.4.0", "olives": "~1.6.0",
"emily": "~1.3.5", "emily": "~1.8.1",
"requirejs": "~2.1.5", "requirejs": "~2.1.5",
"todomvc-common": "~0.3.0" "todomvc-common": "~0.3.0"
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
*/ */
/** /**
* Emily * Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> * Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed * MIT Licensed
*/ */
...@@ -19,6 +19,36 @@ define('Tools',[], ...@@ -19,6 +19,36 @@ define('Tools',[],
*/ */
function Tools(){ function Tools(){
/**
* 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;
if (!array) {
return;
}
array.forEach(function (comparedItem, comparedItemIndex) {
var thisDiff = getDiff(comparedItem, item);
if (thisDiff >= 0 && (typeof diff == "undefined" || thisDiff < diff)) {
diff = thisDiff;
closest = comparedItemIndex;
}
});
return closest;
}
return { return {
/** /**
* For applications that don't run in a browser, window is not the global object. * For applications that don't run in a browser, window is not the global object.
...@@ -261,7 +291,7 @@ function Tools(){ ...@@ -261,7 +291,7 @@ function Tools(){
*/ */
getNestedProperty: function getNestedProperty(object, property) { getNestedProperty: function getNestedProperty(object, property) {
if (object && object instanceof Object) { if (object && object instanceof Object) {
if (typeof property == "string" && property != "") { if (typeof property == "string" && property !== "") {
var split = property.split("."); var split = property.split(".");
return split.reduce(function (obj, prop) { return split.reduce(function (obj, prop) {
return obj && obj[prop]; return obj && obj[prop];
...@@ -286,7 +316,7 @@ function Tools(){ ...@@ -286,7 +316,7 @@ function Tools(){
*/ */
setNestedProperty: function setNestedProperty(object, property, value) { setNestedProperty: function setNestedProperty(object, property, value) {
if (object && object instanceof Object) { if (object && object instanceof Object) {
if (typeof property == "string" && property != "") { if (typeof property == "string" && property !== "") {
var split = property.split("."); var split = property.split(".");
return split.reduce(function (obj, prop, idx) { return split.reduce(function (obj, prop, idx) {
obj[prop] = obj[prop] || {}; obj[prop] = obj[prop] || {};
...@@ -304,6 +334,45 @@ function Tools(){ ...@@ -304,6 +334,45 @@ function Tools(){
} else { } else {
return object; 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;
});
} }
...@@ -315,7 +384,7 @@ function Tools(){ ...@@ -315,7 +384,7 @@ function Tools(){
/** /**
* Emily * Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> * Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed * MIT Licensed
*/ */
...@@ -330,6 +399,8 @@ define('Observable',["Tools"], ...@@ -330,6 +399,8 @@ define('Observable',["Tools"],
*/ */
function Observable(Tools) { function Observable(Tools) {
/** /**
* Defines the Observable * Defines the Observable
* @private * @private
...@@ -363,6 +434,21 @@ function Observable(Tools) { ...@@ -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 * Remove an observer
* @param {Handle} handle returned by the watch method * @param {Handle} handle returned by the watch method
...@@ -398,14 +484,16 @@ function Observable(Tools) { ...@@ -398,14 +484,16 @@ function Observable(Tools) {
if (observers) { if (observers) {
Tools.loop(observers, function (value) { Tools.loop(observers, function (value) {
try { try {
value && value[0].apply(value[1] || null, args); if (value) {
value[0].apply(value[1] || null, args);
}
} catch (err) { } } catch (err) { }
}); });
return true; return true;
} else { } else {
return false; return false;
} }
}, };
/** /**
* Check if topic has the described observer * Check if topic has the described observer
...@@ -444,7 +532,7 @@ function Observable(Tools) { ...@@ -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> * Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed * MIT Licensed
*/ */
...@@ -456,6 +544,8 @@ define('StateMachine',["Tools"], ...@@ -456,6 +544,8 @@ define('StateMachine',["Tools"],
*/ */
function StateMachine(Tools) { function StateMachine(Tools) {
/** /**
* @param initState {String} the initial state * @param initState {String} the initial state
* @param diagram {Object} the diagram that describes the state machine * @param diagram {Object} the diagram that describes the state machine
...@@ -511,7 +601,8 @@ function StateMachine(Tools) { ...@@ -511,7 +601,8 @@ function StateMachine(Tools) {
*/ */
this.add = function add(name) { this.add = function add(name) {
if (!_states[name]) { if (!_states[name]) {
return _states[name] = new Transition(); var transition = _states[name] = new Transition();
return transition;
} else { } else {
return _states[name]; return _states[name];
} }
...@@ -629,8 +720,8 @@ function StateMachine(Tools) { ...@@ -629,8 +720,8 @@ function StateMachine(Tools) {
return false; return false;
} }
if (typeof event == "string" if (typeof event == "string" &&
&& typeof action == "function") { typeof action == "function") {
arr[0] = action; arr[0] = action;
...@@ -680,8 +771,8 @@ function StateMachine(Tools) { ...@@ -680,8 +771,8 @@ function StateMachine(Tools) {
* @private * @private
* @returns false if error, the next state or undefined if success (that sounds weird) * @returns false if error, the next state or undefined if success (that sounds weird)
*/ */
this.event = function event(event) { this.event = function event(newEvent) {
var _transition = _transitions[event]; var _transition = _transitions[newEvent];
if (_transition) { if (_transition) {
_transition[0].apply(_transition[1], Tools.toArray(arguments).slice(1)); _transition[0].apply(_transition[1], Tools.toArray(arguments).slice(1));
return _transition[2]; return _transition[2];
...@@ -689,14 +780,14 @@ function StateMachine(Tools) { ...@@ -689,14 +780,14 @@ function StateMachine(Tools) {
return false; return false;
} }
}; };
}; }
return StateMachineConstructor; return StateMachineConstructor;
}); });
/** /**
* Emily * Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> * Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed * MIT Licensed
*/ */
...@@ -708,6 +799,8 @@ define('Promise',["Observable", "StateMachine"], ...@@ -708,6 +799,8 @@ define('Promise',["Observable", "StateMachine"],
*/ */
function Promise(Observable, StateMachine) { function Promise(Observable, StateMachine) {
return function PromiseConstructor() { return function PromiseConstructor() {
/** /**
...@@ -726,7 +819,7 @@ function Promise(Observable, StateMachine) { ...@@ -726,7 +819,7 @@ function Promise(Observable, StateMachine) {
* The funky observable * The funky observable
* @private * @private
*/ */
_observable = new Observable, _observable = new Observable(),
/** /**
* The state machine States & transitions * The state machine States & transitions
...@@ -822,7 +915,7 @@ function Promise(Observable, StateMachine) { ...@@ -822,7 +915,7 @@ function Promise(Observable, StateMachine) {
* @returns {Promise} the new promise * @returns {Promise} the new promise
*/ */
this.then = function then() { this.then = function then() {
var promise = new PromiseConstructor; var promise = new PromiseConstructor();
// If a fulfillment callback is given // If a fulfillment callback is given
if (arguments[0] instanceof Function) { if (arguments[0] instanceof Function) {
...@@ -903,7 +996,7 @@ function Promise(Observable, StateMachine) { ...@@ -903,7 +996,7 @@ function Promise(Observable, StateMachine) {
promise.reject(err); promise.reject(err);
} }
} };
}; };
/** /**
...@@ -954,7 +1047,7 @@ function Promise(Observable, StateMachine) { ...@@ -954,7 +1047,7 @@ function Promise(Observable, StateMachine) {
return _states; return _states;
}; };
} };
...@@ -962,7 +1055,7 @@ function Promise(Observable, StateMachine) { ...@@ -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> * Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed * MIT Licensed
*/ */
...@@ -975,6 +1068,8 @@ define('Store',["Observable", "Tools"], ...@@ -975,6 +1068,8 @@ define('Store',["Observable", "Tools"],
*/ */
function Store(Observable, Tools) { function Store(Observable, Tools) {
/** /**
* Defines the Store * Defines the Store
* @param {Array/Object} the data to initialize the store with * @param {Array/Object} the data to initialize the store with
...@@ -1000,6 +1095,12 @@ define('Store',["Observable", "Tools"], ...@@ -1000,6 +1095,12 @@ define('Store',["Observable", "Tools"],
*/ */
_valueObservable = new Observable(), _valueObservable = new Observable(),
/**
* Saves the handles for the subscriptions of the computed properties
* @private
*/
_computed = [],
/** /**
* Gets the difference between two objects and notifies them * Gets the difference between two objects and notifies them
* @private * @private
...@@ -1131,9 +1232,12 @@ define('Store',["Observable", "Tools"], ...@@ -1131,9 +1232,12 @@ define('Store',["Observable", "Tools"],
}; };
/** /**
* Alter the data be calling one of it's method * Alter the data by calling one of it's method
* When the modifications are done, it notifies on changes. * 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 * @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 * @returns the result of the method call
*/ */
this.alter = function alter(func) { this.alter = function alter(func) {
...@@ -1142,8 +1246,9 @@ define('Store',["Observable", "Tools"], ...@@ -1142,8 +1246,9 @@ define('Store',["Observable", "Tools"],
if (_data[func]) { if (_data[func]) {
previousData = Tools.clone(_data); previousData = Tools.clone(_data);
apply = _data[func].apply(_data, Array.prototype.slice.call(arguments, 1)); apply = this.proxy.apply(this, arguments);
_notifyDiffs(previousData); _notifyDiffs(previousData);
_storeObservable.notify("altered", _data, previousData);
return apply; return apply;
} else { } else {
return false; return false;
...@@ -1151,9 +1256,20 @@ define('Store',["Observable", "Tools"], ...@@ -1151,9 +1256,20 @@ define('Store',["Observable", "Tools"],
}; };
/** /**
* proxy is an alias for alter * 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 = this.alter; this.proxy = function proxy(func) {
if (_data[func]) {
return _data[func].apply(_data, Array.prototype.slice.call(arguments, 1));
} else {
return false;
}
};
/** /**
* Watch the store's modifications * Watch the store's modifications
...@@ -1234,6 +1350,7 @@ define('Store',["Observable", "Tools"], ...@@ -1234,6 +1350,7 @@ define('Store',["Observable", "Tools"],
var previousData = Tools.clone(_data); var previousData = Tools.clone(_data);
_data = Tools.clone(data) || {}; _data = Tools.clone(data) || {};
_notifyDiffs(previousData); _notifyDiffs(previousData);
_storeObservable.notify("resetted", _data, previousData);
return true; return true;
} else { } else {
return false; return false;
...@@ -1241,6 +1358,65 @@ define('Store',["Observable", "Tools"], ...@@ -1241,6 +1358,65 @@ define('Store',["Observable", "Tools"],
}; };
/**
* 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 * Returns a JSON version of the data
* Use dump if you want all the data as a plain js object * Use dump if you want all the data as a plain js object
...@@ -1261,11 +1437,10 @@ define('Store',["Observable", "Tools"], ...@@ -1261,11 +1437,10 @@ define('Store',["Observable", "Tools"],
}); });
/** /**
* Emily * Emily.js - http://flams.github.com/emily/
* Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> * Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com>
* MIT Licensed * MIT Licensed
*/ */
define('Transport',[], define('Transport',[],
/** /**
* @class * @class
...@@ -1275,6 +1450,8 @@ define('Transport',[], ...@@ -1275,6 +1450,8 @@ define('Transport',[],
*/ */
function Transport() { function Transport() {
/** /**
* Create a Transport * Create a Transport
* @param {Emily Store} [optionanl] $reqHandlers an object containing the request handlers * @param {Emily Store} [optionanl] $reqHandlers an object containing the request handlers
...@@ -1319,11 +1496,13 @@ function Transport() { ...@@ -1319,11 +1496,13 @@ function Transport() {
* @returns * @returns
*/ */
this.request = function request(reqHandler, data, callback, scope) { this.request = function request(reqHandler, data, callback, scope) {
if (_reqHandlers.has(reqHandler) if (_reqHandlers.has(reqHandler) &&
&& typeof data != "undefined") { typeof data != "undefined") {
_reqHandlers.get(reqHandler)(data, function () { _reqHandlers.get(reqHandler)(data, function () {
callback && callback.apply(scope, arguments); if (callback) {
callback.apply(scope, arguments);
}
}); });
return true; return true;
} else { } else {
...@@ -1341,9 +1520,9 @@ function Transport() { ...@@ -1341,9 +1520,9 @@ function Transport() {
* @returns {Function} the abort function to call to stop listening * @returns {Function} the abort function to call to stop listening
*/ */
this.listen = function listen(reqHandler, data, callback, scope) { this.listen = function listen(reqHandler, data, callback, scope) {
if (_reqHandlers.has(reqHandler) if (_reqHandlers.has(reqHandler) &&
&& typeof data != "undefined" typeof data != "undefined" &&
&& typeof callback == "function") { typeof callback == "function") {
var func = function () { var func = function () {
callback.apply(scope, arguments); callback.apply(scope, arguments);
...@@ -1368,3 +1547,242 @@ function Transport() { ...@@ -1368,3 +1547,242 @@ function Transport() {
}; };
}); });
/**
* 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 @@ ...@@ -12,6 +12,8 @@
define('DomUtils',["Tools"], function (Tools) { define('DomUtils',["Tools"], function (Tools) {
return { return {
/** /**
* Returns a NodeList including the given dom node, * Returns a NodeList including the given dom node,
...@@ -127,6 +129,8 @@ define('Bind.plugin',["Store", "Observable", "Tools", "DomUtils"], ...@@ -127,6 +129,8 @@ define('Bind.plugin',["Store", "Observable", "Tools", "DomUtils"],
*/ */
function BindPlugin(Store, Observable, Tools, DomUtils) { function BindPlugin(Store, Observable, Tools, DomUtils) {
return function BindPluginConstructor($model, $bindings) { return function BindPluginConstructor($model, $bindings) {
/** /**
...@@ -146,14 +150,28 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -146,14 +150,28 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* each foreach has its itemRenderer * each foreach has its itemRenderer
* @private * @private
*/ */
_itemRenderers = {}; _itemRenderers = {},
/** /**
* The observers handlers * The observers handlers
* for debugging only
* @private * @private
*/ */
this.observers = {}; _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 * Define the model to watch for
...@@ -247,7 +265,9 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -247,7 +265,9 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
_rootNode = rootNode; _rootNode = rootNode;
renderer = _rootNode.querySelector("*"); renderer = _rootNode.querySelector("*");
this.setRenderer(renderer); this.setRenderer(renderer);
renderer && _rootNode.removeChild(renderer); if (renderer) {
_rootNode.removeChild(renderer);
}
return true; return true;
} else { } else {
return false; return false;
...@@ -287,7 +307,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -287,7 +307,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* The nodes created from the items are stored here * The nodes created from the items are stored here
* @private * @private
*/ */
this.items = new Store([]); this.items = {};
/** /**
* Set the start limit * Set the start limit
...@@ -296,7 +316,8 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -296,7 +316,8 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns the value * @returns the value
*/ */
this.setStart = function setStart(start) { this.setStart = function setStart(start) {
return _start = parseInt(start, 10); _start = parseInt(start, 10);
return _start;
}; };
/** /**
...@@ -315,7 +336,8 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -315,7 +336,8 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns the value * @returns the value
*/ */
this.setNb = function setNb(nb) { this.setNb = function setNb(nb) {
return _nb = nb == "*" ? nb : parseInt(nb, 10); _nb = nb == "*" ? nb : parseInt(nb, 10);
return _nb;
}; };
/** /**
...@@ -337,12 +359,16 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -337,12 +359,16 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
var node, var node,
next; next;
if (typeof id == "number" && !this.items.get(id)) { if (typeof id == "number" && !this.items[id]) {
next = this.getNextItem(id);
node = this.create(id); node = this.create(id);
if (node) { if (node) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this. // IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
next = this.getNextItem(id); if (next) {
next ? _rootNode.insertBefore(node, next) : _rootNode.appendChild(node); _rootNode.insertBefore(node, next);
} else {
_rootNode.appendChild(node);
}
return true; return true;
} else { } else {
return false; return false;
...@@ -359,11 +385,18 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -359,11 +385,18 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns * @returns
*/ */
this.getNextItem = function getNextItem(id) { this.getNextItem = function getNextItem(id) {
return this.items.alter("slice", id+1).filter(function (value) { var keys = Object.keys(this.items).map(function (string) {
if (DomUtils.isAcceptedType(value)) { return Number(string);
return true; }),
closest = Tools.closestGreater(id, keys),
closestId = keys[closest];
// Only return if different
if (closestId != id) {
return this.items[closestId];
} else {
return;
} }
})[0];
}; };
/** /**
...@@ -373,10 +406,11 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -373,10 +406,11 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns * @returns
*/ */
this.removeItem = function removeItem(id) { this.removeItem = function removeItem(id) {
var item = this.items.get(id); var item = this.items[id];
if (item) { if (item) {
_rootNode.removeChild(item); _rootNode.removeChild(item);
this.items.set(id); delete this.items[id];
_removeObserversForId(id);
return true; return true;
} else { } else {
return false; return false;
...@@ -400,7 +434,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -400,7 +434,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
child.setAttribute("data-" + _plugins.name+"_id", id); child.setAttribute("data-" + _plugins.name+"_id", id);
}); });
this.items.set(id, newNode); this.items[id] = newNode;
_plugins.apply(newNode); _plugins.apply(newNode);
return newNode; return newNode;
} }
...@@ -424,8 +458,10 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -424,8 +458,10 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
if (_nb !== null && _start !== null) { if (_nb !== null && _start !== null) {
// Loop through the existing items // Loop through the existing items
this.items.loop(function (value, idx) { Tools.loop(this.items, function (value, idx) {
// If an item is out of the boundary // If an item is out of the boundary
idx = Number(idx);
if (idx < _start || idx >= (_start + _tmpNb) || !_model.has(idx)) { if (idx < _start || idx >= (_start + _tmpNb) || !_model.has(idx)) {
// Mark it // Mark it
marked.push(idx); marked.push(idx);
...@@ -492,10 +528,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -492,10 +528,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
_model.watch("deleted", function (idx) { _model.watch("deleted", function (idx) {
itemRenderer.render(); itemRenderer.render();
// Also remove all observers // Also remove all observers
this.observers[idx] && this.observers[idx].forEach(function (handler) { _removeObserversForId(idx);
_model.unwatchValue(handler);
}, this);
delete this.observers[idx];
},this); },this);
this.setItemRenderer(idItemRenderer, itemRenderer); this.setItemRenderer(idItemRenderer, itemRenderer);
...@@ -647,14 +680,14 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -647,14 +680,14 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
/** /**
* Prevents the submit and set the model with all form's inputs * Prevents the submit and set the model with all form's inputs
* @param {HTMLFormElement} form * @param {HTMLFormElement} DOMfrom
* @returns true if valid form * @returns true if valid form
*/ */
this.form = function form(form) { this.form = function form(DOMform) {
if (form && form.nodeName == "FORM") { if (DOMform && DOMform.nodeName == "FORM") {
var that = this; var that = this;
form.addEventListener("submit", function (event) { DOMform.addEventListener("submit", function (event) {
Tools.toArray(form.querySelectorAll("[name]")).forEach(that.set, that); Tools.toArray(DOMform.querySelectorAll("[name]")).forEach(that.set, that);
event.preventDefault(); event.preventDefault();
}, true); }, true);
return true; return true;
...@@ -754,6 +787,8 @@ define('Event.plugin',["DomUtils"], ...@@ -754,6 +787,8 @@ define('Event.plugin',["DomUtils"],
*/ */
function EventPlugin(Utils) { function EventPlugin(Utils) {
/** /**
* The event plugin constructor. * The event plugin constructor.
* ex: new EventPlugin({method: function(){} ...}, false); * ex: new EventPlugin({method: function(){} ...}, false);
...@@ -890,6 +925,8 @@ define('LocalStore',["Store", "Tools"], ...@@ -890,6 +925,8 @@ define('LocalStore',["Store", "Tools"],
*/ */
function LocalStore(Store, Tools) { function LocalStore(Store, Tools) {
function LocalStoreConstructor() { function LocalStoreConstructor() {
/** /**
...@@ -971,7 +1008,7 @@ function LocalStore(Store, Tools) { ...@@ -971,7 +1008,7 @@ function LocalStore(Store, Tools) {
return function LocalStoreFactory(init) { return function LocalStoreFactory(init) {
LocalStoreConstructor.prototype = new Store(init); LocalStoreConstructor.prototype = new Store(init);
return new LocalStoreConstructor; return new LocalStoreConstructor();
}; };
}); });
...@@ -995,6 +1032,8 @@ define('Plugins',["Tools", "DomUtils"], ...@@ -995,6 +1032,8 @@ define('Plugins',["Tools", "DomUtils"],
*/ */
function Plugins(Tools, DomUtils) { function Plugins(Tools, DomUtils) {
return function PluginsConstructor($plugins) { return function PluginsConstructor($plugins) {
/** /**
...@@ -1147,6 +1186,8 @@ define('OObject',["StateMachine", "Store", "Plugins", "DomUtils", "Tools"], ...@@ -1147,6 +1186,8 @@ define('OObject',["StateMachine", "Store", "Plugins", "DomUtils", "Tools"],
*/ */
function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
return function OObjectConstructor(otherStore) { return function OObjectConstructor(otherStore) {
/** /**
...@@ -1179,7 +1220,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { ...@@ -1179,7 +1220,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
// as it wouldn't be possible to know which node would belong to which UI // as it wouldn't be possible to know which node would belong to which UI
// This is probably a DOM limitation. // This is probably a DOM limitation.
if (baseNode.childNodes.length > 1) { if (baseNode.childNodes.length > 1) {
throw Error("UI.template should have only one parent node"); throw new Error("UI.template should have only one parent node");
} else { } else {
UI.dom = baseNode.childNodes[0]; UI.dom = baseNode.childNodes[0];
} }
...@@ -1188,7 +1229,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { ...@@ -1188,7 +1229,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
} else { } else {
// An explicit message I hope // An explicit message I hope
throw Error("UI.template must be set prior to render"); throw new Error("UI.template must be set prior to render");
} }
}, },
...@@ -1197,13 +1238,17 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { ...@@ -1197,13 +1238,17 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
* This dom node should be somewhere in the dom of the application * This dom node should be somewhere in the dom of the application
* @private * @private
*/ */
place = function place(UI, place, beforeNode) { place = function place(UI, DOMplace, beforeNode) {
if (place) { if (DOMplace) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this. // 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); if (beforeNode) {
DOMplace.insertBefore(UI.dom, beforeNode);
} else {
DOMplace.appendChild(UI.dom);
}
// Also save the new place, so next renderings // Also save the new place, so next renderings
// will be made inside it // will be made inside it
_currentPlace = place; _currentPlace = DOMplace;
} }
}, },
...@@ -1242,7 +1287,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { ...@@ -1242,7 +1287,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
* It has set/get/del/has/watch/unwatch methods * It has set/get/del/has/watch/unwatch methods
* @see Emily's doc for more info on how it works. * @see Emily's doc for more info on how it works.
*/ */
this.model = otherStore instanceof Store ? otherStore : new Store; this.model = otherStore instanceof Store ? otherStore : new Store();
/** /**
* The module that will manage the plugins for this UI * The module that will manage the plugins for this UI
...@@ -1338,6 +1383,8 @@ define('Place.plugin',["OObject", "Tools"], ...@@ -1338,6 +1383,8 @@ define('Place.plugin',["OObject", "Tools"],
*/ */
function PlacePlugin(OObject, Tools) { function PlacePlugin(OObject, Tools) {
/** /**
* Intilialize a Place.plugin with a list of OObjects * Intilialize a Place.plugin with a list of OObjects
* @param {Object} $uis a list of OObjects such as: * @param {Object} $uis a list of OObjects such as:
...@@ -1427,6 +1474,8 @@ define('SocketIOTransport',["Observable", "Tools"], ...@@ -1427,6 +1474,8 @@ define('SocketIOTransport',["Observable", "Tools"],
*/ */
function SocketIOTransport(Observable, Tools) { function SocketIOTransport(Observable, Tools) {
/** /**
* Defines the SocketIOTransport * Defines the SocketIOTransport
* @private * @private
...@@ -1462,7 +1511,7 @@ function SocketIOTransport(Observable, Tools) { ...@@ -1462,7 +1511,7 @@ function SocketIOTransport(Observable, Tools) {
*/ */
this.getSocket = function getSocket() { this.getSocket = function getSocket() {
return _socket; return _socket;
}, };
/** /**
* Subscribe to a socket event * Subscribe to a socket event
...@@ -1471,7 +1520,7 @@ function SocketIOTransport(Observable, Tools) { ...@@ -1471,7 +1520,7 @@ function SocketIOTransport(Observable, Tools) {
*/ */
this.on = function on(event, func) { this.on = function on(event, func) {
return _socket.on(event, func); return _socket.on(event, func);
}, };
/** /**
* Subscribe to a socket event but disconnect as soon as it fires. * Subscribe to a socket event but disconnect as soon as it fires.
...@@ -1510,15 +1559,17 @@ function SocketIOTransport(Observable, Tools) { ...@@ -1510,15 +1559,17 @@ function SocketIOTransport(Observable, Tools) {
* @param {Object} scope the scope in which to execute the callback * @param {Object} scope the scope in which to execute the callback
*/ */
this.request = function request(channel, data, func, scope) { this.request = function request(channel, data, func, scope) {
if (typeof channel == "string" if (typeof channel == "string" &&
&& typeof data != "undefined") { typeof data != "undefined") {
var reqData = { var reqData = {
eventId: Date.now() + Math.floor(Math.random()*1e6), eventId: Date.now() + Math.floor(Math.random()*1e6),
data: data data: data
}, },
boundCallback = function () { boundCallback = function () {
func && func.apply(scope || null, arguments); if (func) {
func.apply(scope || null, arguments);
}
}; };
this.once(reqData.eventId, boundCallback); this.once(reqData.eventId, boundCallback);
...@@ -1540,9 +1591,9 @@ function SocketIOTransport(Observable, Tools) { ...@@ -1540,9 +1591,9 @@ function SocketIOTransport(Observable, Tools) {
* @returns * @returns
*/ */
this.listen = function listen(channel, data, func, scope) { this.listen = function listen(channel, data, func, scope) {
if (typeof channel == "string" if (typeof channel == "string" &&
&& typeof data != "undefined" typeof data != "undefined" &&
&& typeof func == "function") { typeof func == "function") {
var reqData = { var reqData = {
eventId: Date.now() + Math.floor(Math.random()*1e6), eventId: Date.now() + Math.floor(Math.random()*1e6),
...@@ -1550,7 +1601,9 @@ function SocketIOTransport(Observable, Tools) { ...@@ -1550,7 +1601,9 @@ function SocketIOTransport(Observable, Tools) {
keepAlive: true keepAlive: true
}, },
boundCallback = function () { boundCallback = function () {
func && func.apply(scope || null, arguments); if (func) {
func.apply(scope || null, arguments);
}
}, },
that = this; that = this;
...@@ -1573,3 +1626,504 @@ function SocketIOTransport(Observable, Tools) { ...@@ -1573,3 +1626,504 @@ function SocketIOTransport(Observable, Tools) {
this.setSocket($socket); 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,6 +13,8 @@ define(["Store", "Observable", "Tools", "DomUtils"], ...@@ -13,6 +13,8 @@ define(["Store", "Observable", "Tools", "DomUtils"],
*/ */
function BindPlugin(Store, Observable, Tools, DomUtils) { function BindPlugin(Store, Observable, Tools, DomUtils) {
"use strict";
return function BindPluginConstructor($model, $bindings) { return function BindPluginConstructor($model, $bindings) {
/** /**
...@@ -32,14 +34,28 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -32,14 +34,28 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* each foreach has its itemRenderer * each foreach has its itemRenderer
* @private * @private
*/ */
_itemRenderers = {}; _itemRenderers = {},
/** /**
* The observers handlers * The observers handlers
* for debugging only
* @private * @private
*/ */
this.observers = {}; _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 * Define the model to watch for
...@@ -133,7 +149,9 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -133,7 +149,9 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
_rootNode = rootNode; _rootNode = rootNode;
renderer = _rootNode.querySelector("*"); renderer = _rootNode.querySelector("*");
this.setRenderer(renderer); this.setRenderer(renderer);
renderer && _rootNode.removeChild(renderer); if (renderer) {
_rootNode.removeChild(renderer);
}
return true; return true;
} else { } else {
return false; return false;
...@@ -173,7 +191,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -173,7 +191,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* The nodes created from the items are stored here * The nodes created from the items are stored here
* @private * @private
*/ */
this.items = new Store([]); this.items = {};
/** /**
* Set the start limit * Set the start limit
...@@ -182,7 +200,8 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -182,7 +200,8 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns the value * @returns the value
*/ */
this.setStart = function setStart(start) { this.setStart = function setStart(start) {
return _start = parseInt(start, 10); _start = parseInt(start, 10);
return _start;
}; };
/** /**
...@@ -201,7 +220,8 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -201,7 +220,8 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns the value * @returns the value
*/ */
this.setNb = function setNb(nb) { this.setNb = function setNb(nb) {
return _nb = nb == "*" ? nb : parseInt(nb, 10); _nb = nb == "*" ? nb : parseInt(nb, 10);
return _nb;
}; };
/** /**
...@@ -223,12 +243,16 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -223,12 +243,16 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
var node, var node,
next; next;
if (typeof id == "number" && !this.items.get(id)) { if (typeof id == "number" && !this.items[id]) {
next = this.getNextItem(id);
node = this.create(id); node = this.create(id);
if (node) { if (node) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this. // IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this.
next = this.getNextItem(id); if (next) {
next ? _rootNode.insertBefore(node, next) : _rootNode.appendChild(node); _rootNode.insertBefore(node, next);
} else {
_rootNode.appendChild(node);
}
return true; return true;
} else { } else {
return false; return false;
...@@ -245,11 +269,18 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -245,11 +269,18 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns * @returns
*/ */
this.getNextItem = function getNextItem(id) { this.getNextItem = function getNextItem(id) {
return this.items.alter("slice", id+1).filter(function (value) { var keys = Object.keys(this.items).map(function (string) {
if (DomUtils.isAcceptedType(value)) { return Number(string);
return true; }),
closest = Tools.closestGreater(id, keys),
closestId = keys[closest];
// Only return if different
if (closestId != id) {
return this.items[closestId];
} else {
return;
} }
})[0];
}; };
/** /**
...@@ -259,10 +290,11 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -259,10 +290,11 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
* @returns * @returns
*/ */
this.removeItem = function removeItem(id) { this.removeItem = function removeItem(id) {
var item = this.items.get(id); var item = this.items[id];
if (item) { if (item) {
_rootNode.removeChild(item); _rootNode.removeChild(item);
this.items.set(id); delete this.items[id];
_removeObserversForId(id);
return true; return true;
} else { } else {
return false; return false;
...@@ -286,7 +318,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -286,7 +318,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
child.setAttribute("data-" + _plugins.name+"_id", id); child.setAttribute("data-" + _plugins.name+"_id", id);
}); });
this.items.set(id, newNode); this.items[id] = newNode;
_plugins.apply(newNode); _plugins.apply(newNode);
return newNode; return newNode;
} }
...@@ -310,8 +342,10 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -310,8 +342,10 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
if (_nb !== null && _start !== null) { if (_nb !== null && _start !== null) {
// Loop through the existing items // Loop through the existing items
this.items.loop(function (value, idx) { Tools.loop(this.items, function (value, idx) {
// If an item is out of the boundary // If an item is out of the boundary
idx = Number(idx);
if (idx < _start || idx >= (_start + _tmpNb) || !_model.has(idx)) { if (idx < _start || idx >= (_start + _tmpNb) || !_model.has(idx)) {
// Mark it // Mark it
marked.push(idx); marked.push(idx);
...@@ -378,10 +412,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -378,10 +412,7 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
_model.watch("deleted", function (idx) { _model.watch("deleted", function (idx) {
itemRenderer.render(); itemRenderer.render();
// Also remove all observers // Also remove all observers
this.observers[idx] && this.observers[idx].forEach(function (handler) { _removeObserversForId(idx);
_model.unwatchValue(handler);
}, this);
delete this.observers[idx];
},this); },this);
this.setItemRenderer(idItemRenderer, itemRenderer); this.setItemRenderer(idItemRenderer, itemRenderer);
...@@ -533,14 +564,14 @@ function BindPlugin(Store, Observable, Tools, DomUtils) { ...@@ -533,14 +564,14 @@ function BindPlugin(Store, Observable, Tools, DomUtils) {
/** /**
* Prevents the submit and set the model with all form's inputs * Prevents the submit and set the model with all form's inputs
* @param {HTMLFormElement} form * @param {HTMLFormElement} DOMfrom
* @returns true if valid form * @returns true if valid form
*/ */
this.form = function form(form) { this.form = function form(DOMform) {
if (form && form.nodeName == "FORM") { if (DOMform && DOMform.nodeName == "FORM") {
var that = this; var that = this;
form.addEventListener("submit", function (event) { DOMform.addEventListener("submit", function (event) {
Tools.toArray(form.querySelectorAll("[name]")).forEach(that.set, that); Tools.toArray(DOMform.querySelectorAll("[name]")).forEach(that.set, that);
event.preventDefault(); event.preventDefault();
}, true); }, true);
return true; return true;
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
define(["Tools"], function (Tools) { define(["Tools"], function (Tools) {
"use strict";
return { return {
/** /**
* Returns a NodeList including the given dom node, * Returns a NodeList including the given dom node,
......
...@@ -14,6 +14,8 @@ define(["DomUtils"], ...@@ -14,6 +14,8 @@ define(["DomUtils"],
*/ */
function EventPlugin(Utils) { function EventPlugin(Utils) {
"use strict";
/** /**
* The event plugin constructor. * The event plugin constructor.
* ex: new EventPlugin({method: function(){} ...}, false); * ex: new EventPlugin({method: function(){} ...}, false);
......
...@@ -15,6 +15,8 @@ define(["Store", "Tools"], ...@@ -15,6 +15,8 @@ define(["Store", "Tools"],
*/ */
function LocalStore(Store, Tools) { function LocalStore(Store, Tools) {
"use strict";
function LocalStoreConstructor() { function LocalStoreConstructor() {
/** /**
...@@ -96,7 +98,7 @@ function LocalStore(Store, Tools) { ...@@ -96,7 +98,7 @@ function LocalStore(Store, Tools) {
return function LocalStoreFactory(init) { return function LocalStoreFactory(init) {
LocalStoreConstructor.prototype = new Store(init); LocalStoreConstructor.prototype = new Store(init);
return new LocalStoreConstructor; return new LocalStoreConstructor();
}; };
}); });
...@@ -13,6 +13,8 @@ define(["StateMachine", "Store", "Plugins", "DomUtils", "Tools"], ...@@ -13,6 +13,8 @@ define(["StateMachine", "Store", "Plugins", "DomUtils", "Tools"],
*/ */
function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
"use strict";
return function OObjectConstructor(otherStore) { return function OObjectConstructor(otherStore) {
/** /**
...@@ -45,7 +47,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { ...@@ -45,7 +47,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
// as it wouldn't be possible to know which node would belong to which UI // as it wouldn't be possible to know which node would belong to which UI
// This is probably a DOM limitation. // This is probably a DOM limitation.
if (baseNode.childNodes.length > 1) { if (baseNode.childNodes.length > 1) {
throw Error("UI.template should have only one parent node"); throw new Error("UI.template should have only one parent node");
} else { } else {
UI.dom = baseNode.childNodes[0]; UI.dom = baseNode.childNodes[0];
} }
...@@ -54,7 +56,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { ...@@ -54,7 +56,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
} else { } else {
// An explicit message I hope // An explicit message I hope
throw Error("UI.template must be set prior to render"); throw new Error("UI.template must be set prior to render");
} }
}, },
...@@ -63,13 +65,17 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { ...@@ -63,13 +65,17 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
* This dom node should be somewhere in the dom of the application * This dom node should be somewhere in the dom of the application
* @private * @private
*/ */
place = function place(UI, place, beforeNode) { place = function place(UI, DOMplace, beforeNode) {
if (place) { if (DOMplace) {
// IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this. // 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); if (beforeNode) {
DOMplace.insertBefore(UI.dom, beforeNode);
} else {
DOMplace.appendChild(UI.dom);
}
// Also save the new place, so next renderings // Also save the new place, so next renderings
// will be made inside it // will be made inside it
_currentPlace = place; _currentPlace = DOMplace;
} }
}, },
...@@ -108,7 +114,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) { ...@@ -108,7 +114,7 @@ function OObject(StateMachine, Store, Plugins, DomUtils, Tools) {
* It has set/get/del/has/watch/unwatch methods * It has set/get/del/has/watch/unwatch methods
* @see Emily's doc for more info on how it works. * @see Emily's doc for more info on how it works.
*/ */
this.model = otherStore instanceof Store ? otherStore : new Store; this.model = otherStore instanceof Store ? otherStore : new Store();
/** /**
* The module that will manage the plugins for this UI * The module that will manage the plugins for this UI
......
...@@ -12,6 +12,8 @@ define(["OObject", "Tools"], ...@@ -12,6 +12,8 @@ define(["OObject", "Tools"],
*/ */
function PlacePlugin(OObject, Tools) { function PlacePlugin(OObject, Tools) {
"use strict";
/** /**
* Intilialize a Place.plugin with a list of OObjects * Intilialize a Place.plugin with a list of OObjects
* @param {Object} $uis a list of OObjects such as: * @param {Object} $uis a list of OObjects such as:
......
...@@ -17,6 +17,8 @@ define(["Tools", "DomUtils"], ...@@ -17,6 +17,8 @@ define(["Tools", "DomUtils"],
*/ */
function Plugins(Tools, DomUtils) { function Plugins(Tools, DomUtils) {
"use strict";
return function PluginsConstructor($plugins) { return function PluginsConstructor($plugins) {
/** /**
......
...@@ -12,6 +12,8 @@ define(["Observable", "Tools"], ...@@ -12,6 +12,8 @@ define(["Observable", "Tools"],
*/ */
function SocketIOTransport(Observable, Tools) { function SocketIOTransport(Observable, Tools) {
"use strict";
/** /**
* Defines the SocketIOTransport * Defines the SocketIOTransport
* @private * @private
...@@ -47,7 +49,7 @@ function SocketIOTransport(Observable, Tools) { ...@@ -47,7 +49,7 @@ function SocketIOTransport(Observable, Tools) {
*/ */
this.getSocket = function getSocket() { this.getSocket = function getSocket() {
return _socket; return _socket;
}, };
/** /**
* Subscribe to a socket event * Subscribe to a socket event
...@@ -56,7 +58,7 @@ function SocketIOTransport(Observable, Tools) { ...@@ -56,7 +58,7 @@ function SocketIOTransport(Observable, Tools) {
*/ */
this.on = function on(event, func) { this.on = function on(event, func) {
return _socket.on(event, func); return _socket.on(event, func);
}, };
/** /**
* Subscribe to a socket event but disconnect as soon as it fires. * Subscribe to a socket event but disconnect as soon as it fires.
...@@ -95,15 +97,17 @@ function SocketIOTransport(Observable, Tools) { ...@@ -95,15 +97,17 @@ function SocketIOTransport(Observable, Tools) {
* @param {Object} scope the scope in which to execute the callback * @param {Object} scope the scope in which to execute the callback
*/ */
this.request = function request(channel, data, func, scope) { this.request = function request(channel, data, func, scope) {
if (typeof channel == "string" if (typeof channel == "string" &&
&& typeof data != "undefined") { typeof data != "undefined") {
var reqData = { var reqData = {
eventId: Date.now() + Math.floor(Math.random()*1e6), eventId: Date.now() + Math.floor(Math.random()*1e6),
data: data data: data
}, },
boundCallback = function () { boundCallback = function () {
func && func.apply(scope || null, arguments); if (func) {
func.apply(scope || null, arguments);
}
}; };
this.once(reqData.eventId, boundCallback); this.once(reqData.eventId, boundCallback);
...@@ -125,9 +129,9 @@ function SocketIOTransport(Observable, Tools) { ...@@ -125,9 +129,9 @@ function SocketIOTransport(Observable, Tools) {
* @returns * @returns
*/ */
this.listen = function listen(channel, data, func, scope) { this.listen = function listen(channel, data, func, scope) {
if (typeof channel == "string" if (typeof channel == "string" &&
&& typeof data != "undefined" typeof data != "undefined" &&
&& typeof func == "function") { typeof func == "function") {
var reqData = { var reqData = {
eventId: Date.now() + Math.floor(Math.random()*1e6), eventId: Date.now() + Math.floor(Math.random()*1e6),
...@@ -135,7 +139,9 @@ function SocketIOTransport(Observable, Tools) { ...@@ -135,7 +139,9 @@ function SocketIOTransport(Observable, Tools) {
keepAlive: true keepAlive: true
}, },
boundCallback = function () { boundCallback = function () {
func && func.apply(scope || null, arguments); if (func) {
func.apply(scope || null, arguments);
}
}, },
that = this; 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