Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

control can now have priorities on handlers

fixes
  • Loading branch information...
commit 11dfe2184a6f5adcc1ac181b971fa0f90aad7202 1 parent 0270183
@rhysbrettbowen authored
Showing with 136 additions and 44 deletions.
  1. +25 −6 README.md
  2. +72 −21 collection.js
  3. +23 −13 control.js
  4. +16 −4 model.js
View
31 README.md
@@ -171,21 +171,28 @@ This can be used as a factory and cache for models. Use the get to retrieve mode
## mvc.Control ##
-the closure library already provides goog.ui.Component which is a great controller. If you use Backbone.js you'll probably recognise it as the view. mvc.Control adds in two methods, delegateEvents and getEls. These are convenience functions. The getEls allows you to use simple string selectors to get a handle for the elements under the component and delegateEvents gives an easier interface for listening to events in the component. If you want to use a different class in the library that already extends goog.ui.Component you can stil use these functions by adding them to your classes prototype like so:
+the closure library already provides goog.ui.Component which is a great controller. If you use Backbone.js you'll probably recognise it as the view. mvc.Control adds in two methods, delegateEvents and getEls. These are convenience functions. The getEls allows you to use simple string selectors to get a handle for the elements under the component and delegateEvents gives an easier interface for listening to events in the component. If you want to use a different class in the library that already extends goog.ui.Component you can still use these functions by adding them to your classes prototype like so:
```javascript
goog.require('mvc.Control');
-myClass.prototype.delegateEvents = mvc.Control.prototype.delegateEvents;
-myClass.prototype.getEls = mvc.Control.prototype.getEls;
+// your class and protoype declarations go here
+// you will also want to make sure that you set the model for the component
+// preferably in the constructor with:
+// this.setModel(model);
+
+goog.object.extend(myClass.prototype, mvc.Control.prototype);
```
-There is also a set of functions under mvc.Control.Fn that can be passed to mvc.Model.bind for frequently used setters (changing textContent, value and classes so far - feel free to extend it with more commonly used functions)
+to delegate events use the "on" method which takes the event name, function to run, an optional className or array of classnames to check the event target against, an optional handler for the function and an optional priority. The priority could be important if you want to use Event.stopPropagation as the control will run the functions by their priority and then in the order they are registered, rather than their position in the DOM. This give you fine grain control over when to run things. There is also a click method which puts in the click event name for you, and a once method which will only call the function once.
+
+Both the on and the click method pass back a uid that can be passed to "off" to stop the listener.
## 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.
+The AjaxSync can be used and passed an object of functions or strings (with the strings acting much like the router example below) for each of the four methods create, read, update and del. These will be used for the call.
## mvc.Router ##
@@ -212,10 +219,21 @@ function(id,edit,query,queryVals) {
## mvc.Mediator ##
-mvc.Mediator allows message passing between components. It's a singleton so you get it's reference using
+mvc.Mediator allows message passing between components. It's most useful as a singleton so you could do this:
```javascript
-var mediator = mvc.Mediator.getInstance();
+goog.require('mvc.Mediator');
+
+/**
+ * @constructor
+ * @extends {mvc.Mediator}
+ */
+myapp.Mediator=function(){};
+goog.inherits(myapp.Mediator, mvc.Mediator);
+goog.addSingletonGetter(myapp.Mediator);
+
+// you can then access the mediator anywhere in your app using:
+// mvc.Mediator.getInstance();
```
you can then register your object with the mediator and the messages that you may pass. This allows other modules that are listening for a specific message to run some initiation, or dispose when you unregister. You can listen to messages using the on method and stop using the off method. You can even test to see if anyone is listening for a message using the isListened method
@@ -226,6 +244,7 @@ you can then register your object with the mediator and the messages that you ma
- reworked bind for performance
- schema is now an object
+- mvc.Control events can now be given a priority to order them
- more tests
#### v0.8 ####
View
93 collection.js
@@ -35,7 +35,7 @@ mvc.Collection = function(options) {
/**
* @private
- * @type {Array.<mvc.Model>}
+ * @type {Array.<{model:mvc.Model}>}
*/
this.models_ = [];
/**
@@ -43,7 +43,8 @@ mvc.Collection = function(options) {
* @type {?function(mvc.Model, mvc.Model):number}
*/
this.comparator_ = defaults['comparator'];
-
+ this.modelChangeFns_ = [];
+ this.modelChange_ = false;
/**
* @private
*/
@@ -64,7 +65,8 @@ goog.inherits(mvc.Collection, mvc.Model);
* @return {Array.<Object.<string, *>>|Array.<*>}
*/
mvc.Collection.prototype.pluck = function(key) {
- return goog.array.map(this.models_, function(model) {
+ return goog.array.map(this.models_, function(mod) {
+ var model = mod.model;
if(goog.isString(key))
return model.get(key);
return goog.array.reduce(key, function(map, attr) {
@@ -107,14 +109,16 @@ mvc.Collection.prototype.sort = function(silent) {
if(this.comparator_) {
var comp = this.comparator_;
this.models_.sort(function(a, b) {
- var ret = comp(a, b);
+ var ret = comp(a.model, b.model);
if(ret < 0)
changeOrder = true;
return ret;
});
+ this.modelChange_ = true;
}
- if(!silent && changeOrder)
+ if(!silent && changeOrder) {
this.dispatchEvent(goog.events.EventType.CHANGE);
+ }
};
/**
@@ -132,18 +136,24 @@ mvc.Collection.prototype.add = function(model, ind, silent) {
}, this);
return;
}
- if(!goog.array.contains(this.models_, model)) {
- goog.array.insertAt(this.models_, model, (ind || this.models_.length));
- goog.events.listen(model, goog.events.EventType.CHANGE, this.sort,
- false, this);
- goog.events.listen(model, goog.events.EventType.UNLOAD,
- function(e){
- goog.array.remove(this.models_, e.target);
- this.sort();
- }, false, this);
+ if(!goog.array.find(this.models_, function(mod) {
+ return mod.model == model;
+ })) {
+ this.modelChange_ = true;
+ var changeId = model.bindAll(this.sort, this);
+ var unloadId = model.bindUnload(function(e){
+ this.remove(model);
+ }, this);
+
+ goog.array.insertAt(this.models_, {
+ model: model,
+ unload: unloadId,
+ change: changeId
+ }, (ind || this.models_.length));
+
this.sort(true);
if(!silent)
- this.dispatchEvent(new goog.events.Event(goog.events.EventType.CHANGE, model));
+ this.dispatchEvent(goog.events.EventType.CHANGE);
}
this.length = this.models_.length;
};
@@ -174,7 +184,14 @@ mvc.Collection.prototype.remove = function(model, silent) {
}, this);
return;
}
- if(goog.array.remove(this.models_, model)) {
+ var modelObj = goog.array.find(this.models_, function(mod) {
+ return mod.model == model;
+ });
+ if(modelObj) {
+ this.modelChange_ = true;
+ model.unbind(modelObj.unload);
+ model.unbind(modelObj.change);
+ goog.array.remove(this.models_, modelObj);
this.sort(true);
if(!silent)
this.dispatchEvent(goog.events.EventType.CHANGE);
@@ -201,9 +218,12 @@ mvc.Collection.prototype.getById = function(id) {
* @return {Array.<mvc.Model>}
*/
mvc.Collection.prototype.getModels = function(filter) {
+ var mods = goog.array.map(this.models_, function(mod) {
+ return mod.model;
+ });
if(filter)
- return goog.array.filter(this.models_, /** @type {Function} */(filter));
- return this.models_.slice(0);
+ return goog.array.filter(mods, /** @type {Function} */(filter));
+ return mods;
};
/**
@@ -213,7 +233,7 @@ mvc.Collection.prototype.getModels = function(filter) {
* @return {mvc.Model}
*/
mvc.Collection.prototype.at = function(index) {
- return this.models_[index<0?this.models_.length+index:index];
+ return this.models_[index<0?this.models_.length+index:index].model;
};
/**
@@ -222,7 +242,38 @@ mvc.Collection.prototype.at = function(index) {
* @param {boolean} silent
*/
mvc.Collection.prototype.clear = function(silent) {
- this.models_ = [];
- if(!silent)
+ //this.models_ = [];
+ this.remove(this.getModels(), true);
+ this.modelChange_ = true;
+ if(!silent) {
this.dispatchEvent(goog.events.EventType.CHANGE);
+ }
+};
+
+mvc.Collection.prototype.modelChange = function(fn, opt_handler) {
+ this.modelChangeFns_.push(goog.bind(fn, (opt_handler || this)));
+};
+
+mvc.Collection.prototype.change_ = function(e) {
+ goog.base(this, 'change_', e);
+ if(this.modelChange_) {
+ goog.array.forEach(this.modelChangeFns_, function(fn) {
+ fn(this);
+ }, this);
+ if(this.schema_) {
+ goog.object.forEach(this.schema_, function(val, key) {
+ if(val.models) {
+ goog.array.forEach(this.bound_, function(bind) {
+ if(goog.array.contains(bind.attr, key)) {
+ bind.fn.apply(bind.hn, goog.array.concat(goog.array.map(bind.attr,
+ function(attr) {
+ return this.get(attr);
+ }, this)));
+ }
+ }, this);
+ }
+ }, this);
+ }
+ this.modelChange_ = false;
+ }
};
View
36 control.js
@@ -28,7 +28,6 @@ mvc.Control = function(model) {
};
goog.inherits(mvc.Control, goog.ui.Component);
-
/**
* Functions that can be passed to the mvc.Model.bind
*
@@ -51,12 +50,6 @@ mvc.Control.prototype.remove = function() {
this.dispose();
};
-/**
- * should be overriden. Creates a div for the component
- */
-mvc.Control.prototype.createDom = function() {
- //this.setElementInternal();
-};
/**
@@ -70,6 +63,9 @@ mvc.Control.prototype.handleEvents_ = function(type, e) {
if(!this.eventHolder_.handlers[type])
return;
goog.array.forEach(this.eventHolder_.handlers[type], function(handler) {
+ if(e.propagationStopped_) {
+ return;
+ }
if(!handler.selectors.length ||
goog.array.some(handler.selectors, function(className) {
return goog.dom.classes.has(/** @type {!Node} */(e.target), className);
@@ -90,8 +86,15 @@ mvc.Control.prototype.handleEvents_ = function(type, e) {
* @param {Function} fn
* @param {string|Array.<string>=} className
* @param {*=} opt_handler
+ * @param {number=} opt_priority
*/
-mvc.Control.prototype.on = function(eventName, fn, className, opt_handler) {
+mvc.Control.prototype.on = function(eventName, fn, className, opt_handler, opt_priority) {
+ if(!this.eventHolder_) {
+ this.eventHolder_ = {
+ listeners: {},
+ handlers: {}
+ };
+ }
if(!this.eventHolder_.handlers[eventName])
this.eventHolder_.handlers[eventName] = [];
if(!this.eventHolder_.listeners[eventName])
@@ -104,10 +107,15 @@ mvc.Control.prototype.on = function(eventName, fn, className, opt_handler) {
selectors: (goog.isArray(className)?className:[className]),
fn: fn,
uid: null,
- handler: (opt_handler || this)
+ handler: (opt_handler || this),
+ priority: (opt_priority || 50)
};
obj.uid = goog.getUid(obj);
- this.eventHolder_.handlers[eventName].push(obj);
+ goog.array.insertAt(this.eventHolder_.handlers[eventName], obj, goog.array.findIndexRight(this.eventHolder_.handlers[eventName],
+ function(obj) {
+ return obj.prioty <= (opt_priority || 50);
+ }
+ )+1);
return obj.uid;
};
@@ -118,8 +126,9 @@ mvc.Control.prototype.on = function(eventName, fn, className, opt_handler) {
* @param {Function} fn
* @param {string|Array.<string>=} className
* @param {*=} opt_handler
+ * @param {number=} opt_priority
*/
-mvc.Control.prototype.once = function(eventName, fn, className, opt_handler) {
+mvc.Control.prototype.once = function(eventName, fn, className, opt_handler, opt_priority) {
var uid;
var onceFn = function() {
fn.apply(/** @type {Object} */(opt_handler||this), Array.prototype.slice.call(arguments));
@@ -135,9 +144,10 @@ mvc.Control.prototype.once = function(eventName, fn, className, opt_handler) {
* @param {Function} fn
* @param {string|Array.<string>=} className
* @param {*=} opt_handler
+ * @param {number=} opt_priority
*/
-mvc.Control.prototype.click = function(fn, className, opt_handler) {
- return this.on(goog.events.EventType.CLICK, fn, className, opt_handler);
+mvc.Control.prototype.click = function(fn, className, opt_handler, opt_priority) {
+ return this.on(goog.events.EventType.CLICK, fn, className, opt_handler, opt_priority);
};
/**
View
20 model.js
@@ -67,6 +67,7 @@ mvc.Model = function(options) {
this.bound_ = [];
this.boundAll_ = {};
+ this.onUnload_ = [];
this.handleErr_ = goog.nullFunction;
@@ -360,9 +361,9 @@ mvc.Model.prototype.getChanges = function() {
}
};
- var ret = goog.object.getKeys(goog.object.filter(this.schema_, function(val) {
+ var ret = goog.object.getKeys(goog.object.filter(this.schema_, function(val, key) {
var requires = [];
- getReq(val,requires);
+ getReq(key,requires);
return goog.array.some(requires, function(require) {
return this.attr_[require] !== this.prev_[require];
}, this);
@@ -398,6 +399,9 @@ mvc.Model.prototype.revert = function(silent) {
mvc.Model.prototype.dispose = function() {
this.sync_.del(this);
this.dispatchEvent(goog.events.EventType.UNLOAD);
+ goog.array.forEach(this.onUnload_, function(fn) {
+ fn(this);
+ }, this);
this.disposeInternal();
};
@@ -459,7 +463,12 @@ mvc.Model.prototype.change_ = function(e) {
}, this);
};
-
+mvc.Model.prototype.bindUnload = function(fn, opt_handler) {
+ fn = goog.bind(fn, (opt_handler || this));
+ var uid = goog.getUid(fn);
+ this.onUnload_.push(fn);
+ return uid;
+};
/**
* Allows easy binding of a model's attribute to an element or a function.
@@ -494,7 +503,10 @@ mvc.Model.prototype.bind = function(name, fn, opt_handler) {
mvc.Model.prototype.unbind = function(id) {
return goog.array.removeIf(this.bound_, function(bound) {
return (bound.id == id);
- }) || goog.object.remove(this.boundAll_, id);
+ }) || goog.object.remove(this.boundAll_, id) ||
+ goog.array.removeIf(this.onUnload_, function(fn) {
+ return goog.getUid(fn) == id;
+ });
};
/**
Please sign in to comment.
Something went wrong with that request. Please try again.