Skip to content

Commit

Permalink
control can now have priorities on handlers
Browse files Browse the repository at this point in the history
fixes
  • Loading branch information
rhysbrettbowen committed Mar 23, 2012
1 parent 0270183 commit 11dfe21
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 44 deletions.
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ##

Expand All @@ -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
Expand All @@ -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 ####
Expand Down
93 changes: 72 additions & 21 deletions collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,16 @@ mvc.Collection = function(options) {

/**
* @private
* @type {Array.<mvc.Model>}
* @type {Array.<{model:mvc.Model}>}
*/
this.models_ = [];
/**
* @private
* @type {?function(mvc.Model, mvc.Model):number}
*/
this.comparator_ = defaults['comparator'];

this.modelChangeFns_ = [];
this.modelChange_ = false;
/**
* @private
*/
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
};

/**
Expand All @@ -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;
};
Expand Down Expand Up @@ -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);
Expand All @@ -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;
};

/**
Expand All @@ -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;
};

/**
Expand All @@ -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;
}
};
36 changes: 23 additions & 13 deletions control.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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();
};


/**
Expand All @@ -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);
Expand All @@ -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])
Expand All @@ -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;
};

Expand All @@ -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));
Expand All @@ -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);
};

/**
Expand Down
20 changes: 16 additions & 4 deletions model.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ mvc.Model = function(options) {

this.bound_ = [];
this.boundAll_ = {};
this.onUnload_ = [];

this.handleErr_ = goog.nullFunction;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
};

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
});
};

/**
Expand Down

0 comments on commit 11dfe21

Please sign in to comment.