Permalink
Browse files

- reworked bind for performance

- schema is now an object
- more tests
  • Loading branch information...
rhysbrettbowen committed Mar 22, 2012
1 parent 8854c14 commit 02701839780058642fc60a79b4be7ba99c36cca5
Showing with 1,315 additions and 230 deletions.
  1. +22 −4 README.md
  2. +7 −2 alltests.js
  3. +34 −25 collection.js
  4. +3 −3 collection_test.js
  5. +95 −19 control.js
  6. +40 −8 control_test.js
  7. +20 −1 mediator.js
  8. +11 −0 mediator_test.html
  9. +32 −0 mediator_test.js
  10. +191 −150 model.js
  11. +7 −4 model_test.js
  12. +5 −4 router_test.js
  13. +8 −3 store.js
  14. +11 −0 store_test.html
  15. +34 −0 store_test.js
  16. +795 −7 test_deps.js
View
@@ -111,10 +111,27 @@ There are also other functions that can take parameters such as the boolean "sil
- fetch(callback, silent): updates the model using it's sync
- save: tells the sync to create/update
+You can also set a schema for a model. A schema is an object which has a set of keys for which you want getters and setters. Under the key you can put an object with the keys of get, set and require. The get function works much like the meta function above (in fact the meta function is just a nice way to add things to the schema). The function will accept in the values for any attributes you put in the require array. The set function will take the value and should return the data to get set on the model. You can also throw errors to stop the value saving and fire the models handleErr_ function which you should set. an example of a schema:
-## mvc.model.Schema ##
+```javascript
+{
+ 'firstname': {
+ set: function(name) {
+ if(name.length > 64)
+ throw new Error("name is too long");
+ return goog.string.trim(name);
+ }
+ },
+ 'fullName': {
+ get: function(first, last) {
+ return last + ", " + first;
+ },
+ require: ['firstname','lastname']
+ }
+}
+```
-A schema can be set for a model. The schema takes in an object or map of keys and functions. The functions take in a value, throw an error if the data is invalid and should return the value to be set in the model. In this way the schema can also act as a setter, doing all the formatting and checking. When a schema is passed in to a model, the model will use this to validate any values trying to be set, and won't add in data if a function throws an error. You can also pass in the following strings to check for the type of input: "number", "string", "array"
+the require should be put in for any user defined attributes as the model goes up the require chain to decide when to fire a change event on these attributes.
## mvc.Collection ##
@@ -168,7 +185,7 @@ There is also a set of functions under mvc.Control.Fn that can be passed to mvc.
## mvc.Sync ##
This is an interface that should have a custom implementation. Two simple implementations have been given called mvc.AjaxSync and mvc.LocalSync. The purpose of sync is to be the glue between the model and the dataStore.
-
+
## mvc.Router ##
@@ -208,7 +225,8 @@ you can then register your object with the mediator and the messages that you ma
#### v0.9 ####
- reworked bind for performance
-- mediator now has once method
+- schema is now an object
+- more tests
#### v0.8 ####
View
@@ -13,5 +13,10 @@
// limitations under the License.
var _allTests = [
-"collection_test.html", "control_test.html", "model_test.html", 'router_test.html'
-]
+ "collection_test.html",
+ "control_test.html",
+ "model_test.html",
+ 'router_test.html',
+ 'store_test.html',
+ 'mediator_test.html'
+];
View
@@ -13,13 +13,14 @@ goog.require('goog.events.EventTarget');
/**
- * A collection of models
+ * A collection of models. Extends model so it has it's own values
*
* @constructor
- * @extends mvc.Model
+ * @extends {mvc.Model}
* @param {Object=} options
*/
mvc.Collection = function(options) {
+ options = options||{};
var defaults = {
'comparator': options['comparator']||null,
'modelType': options['modelType']||mvc.Model,
@@ -31,7 +32,7 @@ mvc.Collection = function(options) {
goog.base(this, options);
-
+
/**
* @private
* @type {Array.<mvc.Model>}
@@ -42,28 +43,22 @@ mvc.Collection = function(options) {
* @type {?function(mvc.Model, mvc.Model):number}
*/
this.comparator_ = defaults['comparator'];
-
+
/**
* @private
*/
this.modelType_ = defaults['modelType'];
-
+
goog.array.forEach(defaults['models'], function(model) {
this.add(model, undefined, true);
}, this);
};
goog.inherits(mvc.Collection, mvc.Model);
-
/**
- * @return {Array}
- */
-mvc.Collection.prototype.toArray = function() {
- return this.models_;
-};
-
-/**
- * plucks an attribute from each model and returns as an array
+ * plucks an attribute from each model and returns as an array. If you pass
+ * an array of keys then the array will contain a map of each key and it's
+ * value
*
* @param {string|Array} key
* @return {Array.<Object.<string, *>>|Array.<*>}
@@ -82,12 +77,8 @@ mvc.Collection.prototype.pluck = function(key) {
};
/**
- * @return {Array}
- */
-
-
-/**
- * function to sort models by
+ * function to sort models by. Function should take two models and
+ * return -1, 0 or 1. Also takes whether to fire a change event after sorting
*
* @param {function(mvc.Model, mvc.Model):number} fn
* @param {boolean=} silent
@@ -98,13 +89,17 @@ mvc.Collection.prototype.setComparator = function(fn, silent) {
};
/**
+ * returns the number of models in the collection
+ *
* @return {number}
*/
mvc.Collection.prototype.getLength = function() {
return this.models_.length;
};
/**
+ * tells the collection to sort it's models. This is used internally
+ *
* @param {boolean=} silent
*/
mvc.Collection.prototype.sort = function(silent) {
@@ -123,6 +118,9 @@ mvc.Collection.prototype.sort = function(silent) {
};
/**
+ * accepts a model or array of models and adds them at the end unless an index
+ * to insert is given.
+ *
* @param {mvc.Model|Array.<mvc.Model>} model
* @param {number=} ind
* @param {boolean=} silent
@@ -151,17 +149,21 @@ mvc.Collection.prototype.add = function(model, ind, silent) {
};
/**
- * @param {Object=} attr
+ * add a new model with the given options. The type of model is given by the
+ * modelType of the collection
+ *
+ * @param {Object=} options
* @param {boolean=} silent
*/
-mvc.Collection.prototype.newModel = function(attr, silent) {
- var model = new this.modelType_();
- model.set(attr || null);
+mvc.Collection.prototype.newModel = function(options, silent) {
+ var model = new this.modelType_(options);
this.add(model, 0, silent);
return model;
};
/**
+ * remove the given model from the collection
+ *
* @param {mvc.Model|Array.<mvc.Model>} model
* @param {boolean=} silent
*/
@@ -214,6 +216,13 @@ mvc.Collection.prototype.at = function(index) {
return this.models_[index<0?this.models_.length+index:index];
};
-mvc.Collection.prototype.clear = function() {
+/**
+ * remove all models from the collection
+ *
+ * @param {boolean} silent
+ */
+mvc.Collection.prototype.clear = function(silent) {
this.models_ = [];
+ if(!silent)
+ this.dispatchEvent(goog.events.EventType.CHANGE);
};
View
@@ -14,7 +14,7 @@ var setUp = function() {
};
var testUnsortedCollection = function() {
- var test = new mvc.Collection([model1, model2, model3]);
+ var test = new mvc.Collection({'models':[model1, model2, model3]});
assertEquals('first object should be mock 1', test.at(0), model1);
assertEquals('second object should be mock 2', test.at(1), model2);
assertEquals('third object should be mock 3', test.at(2), model3);
@@ -37,7 +37,7 @@ var testSortedCollection = function() {
var testNewSortedCollection = function() {
var sort = function(a, b) {return a.get('sort')-b.get('sort');};
- var test = new mvc.Collection([model1,model2,model3]);
+ var test = new mvc.Collection({'models':[model1,model2,model3]});
test.setComparator(sort);
assertEquals('first object should be mock 2', test.at(0), model2);
assertEquals('second object should be mock 3', test.at(1), model3);
@@ -48,4 +48,4 @@ var testAsModel = function() {
var test = new mvc.Collection();
test.set('a', 1);
assertEquals('should have attribute a as 1', test.get('a'), 1);
-}
+};
View
@@ -21,6 +21,10 @@ goog.require('goog.ui.Component');
mvc.Control = function(model) {
goog.base(this);
this.setModel(model);
+ this.eventHolder_ = {
+ listeners: {},
+ handlers: {}
+ };
};
goog.inherits(mvc.Control, goog.ui.Component);
@@ -47,34 +51,106 @@ mvc.Control.prototype.remove = function() {
this.dispose();
};
-mvc.Control.prototype.show = function(parent) {
- this.decorate(parent);
- return this;
+/**
+ * should be overriden. Creates a div for the component
+ */
+mvc.Control.prototype.createDom = function() {
+ //this.setElementInternal();
};
-mvc.Control.prototype.init = goog.abstractMethod;
-mvc.Control.prototype.createDom = function() {
- this.setElementInternal(goog.dom.createDom("DIV"));
+/**
+ * Internal use. Handles and delegates events
+ *
+ * @private
+ * @param {string} type
+ * @param {Event} e
+ */
+mvc.Control.prototype.handleEvents_ = function(type, e) {
+ if(!this.eventHolder_.handlers[type])
+ return;
+ goog.array.forEach(this.eventHolder_.handlers[type], function(handler) {
+ if(!handler.selectors.length ||
+ goog.array.some(handler.selectors, function(className) {
+ return goog.dom.classes.has(/** @type {!Node} */(e.target), className);
+ })) {
+ goog.bind(handler.fn, handler.handler)(e);
+ }
+ });
+};
+
+
+/**
+ * delegating events. An event type is needed as well as a handling function.
+ * if a third parameter is passed then elements with that class will be listened
+ * to, otherwise the whole component. Returns a uid that can be used to end
+ * the listener with the off method
+ *
+ * @param {string} eventName
+ * @param {Function} fn
+ * @param {string|Array.<string>=} className
+ * @param {*=} opt_handler
+ */
+mvc.Control.prototype.on = function(eventName, fn, className, opt_handler) {
+ if(!this.eventHolder_.handlers[eventName])
+ this.eventHolder_.handlers[eventName] = [];
+ if(!this.eventHolder_.listeners[eventName])
+ this.eventHolder_.listeners[eventName] = this.getHandler().listen(
+ this.getElement(), eventName,
+ goog.bind(this.handleEvents_, this, eventName));
+ if(!goog.isDef(className))
+ className = [];
+ var obj = {
+ selectors: (goog.isArray(className)?className:[className]),
+ fn: fn,
+ uid: null,
+ handler: (opt_handler || this)
+ };
+ obj.uid = goog.getUid(obj);
+ this.eventHolder_.handlers[eventName].push(obj);
+ return obj.uid;
};
+/**
+ * same as on, but will only fire once
+ *
+ * @param {string} eventName
+ * @param {Function} fn
+ * @param {string|Array.<string>=} className
+ * @param {*=} opt_handler
+ */
+mvc.Control.prototype.once = function(eventName, fn, className, opt_handler) {
+ var uid;
+ var onceFn = function() {
+ fn.apply(/** @type {Object} */(opt_handler||this), Array.prototype.slice.call(arguments));
+ this.off(uid);
+ };
+ uid = this.on(eventName, onceFn, className);
+ return uid;
+};
+
+/**
+ * same as on but assumes the event type is a click
+ *
+ * @param {Function} fn
+ * @param {string|Array.<string>=} className
+ * @param {*=} opt_handler
+ */
+mvc.Control.prototype.click = function(fn, className, opt_handler) {
+ return this.on(goog.events.EventType.CLICK, fn, className, opt_handler);
+};
/**
- * pass an object where the key is "eventType .className" and the value is the
- * event handler function
- * example:
- * var events = {}
- * goog.object.set(events, goog.events.EventType.CLICK+" ."+goog.getCssName('button'), function(e) {alert("hello");});
+ * take off a lister by it's id'
*
- * @param {Object.<string, Function>} events
+ * @param {string} uid
*/
-mvc.Control.prototype.delegateEvents = function(events) {
- goog.object.forEach(events, function(val, key) {
- this.getHandler().listen(this.getElement(), key.replace(/\s.*/,''), function(e) {
- if(goog.dom.classes.has(e.target, key.replace(/.*\./,'')))
- events[key](e);
- }, false, this);
- }, this);
+mvc.Control.prototype.off = function(uid) {
+ goog.object.forEach(this.eventHolder_.handlers, function(val, key) {
+ goog.array.removeIf(val, function(handler) {
+ return handler.uid == uid;
+ });
+ });
};
/**
Oops, something went wrong.

0 comments on commit 0270183

Please sign in to comment.