Created
February 19, 2011 19:45
-
-
Save ghinch/835307 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var REGISTER_COMPONENT = 'registerStateComponent'; | |
Y.StateManager = Y.Base.create('state-manager', Y.Base, [], { | |
_afterAttrChange : function (e, args) { | |
if (e.fromManager) { | |
return; | |
} | |
this._stateQueue.pause(); | |
try { | |
this._stateQueue.add({ | |
context : this, | |
args : { | |
attr : args.mapTo, | |
val : e.newVal, | |
component : args.component, | |
silent : e.silent | |
}, | |
fn : function (o) { | |
this.set('state.' + o.attr, o.val, { | |
fromManager : true, | |
component : o.component, | |
silent : o.silent | |
}); | |
} | |
}); | |
} finally { | |
this._stateQueue.run(); | |
} | |
}, | |
_register : function (e) { | |
var keys = [], | |
curState = this.get('state'), | |
newState = Y.merge(curState, e.state, true); | |
Y.Object.each(e.map, function (val, key) { | |
this._handles.add(Y.after(e.name + ':' + key + 'Change', this._afterAttrChange, this, { | |
mapTo : val, | |
component : e.name | |
})); | |
}, this); | |
this.set('state', newState, {component : e.name, register : true}); | |
}, | |
_monitorStateChangeAttach : function (e) { | |
var subscriber = e.args[2], // The component that subscribed | |
state = this.get('state'); | |
if (subscriber.handleNewState) { | |
subscriber.handleNewState.call(subscriber, state, null, true); | |
} | |
}, | |
_onStateChange : function (e) { | |
if (Y.Compare(e.prevVal, e.newVal)) { | |
e.halt(); | |
return false; | |
} | |
}, | |
initializer : function () { | |
var stateChange = Y.publish(this.name + ':' + 'stateChange'); | |
stateChange.monitor('attach', this._monitorStateChangeAttach, this); | |
this._handles = new Y.ArrayList(); | |
this._handles.add(Y.after(REGISTER_COMPONENT, this._register, this)); | |
this.on('stateChange', this._onStateChange); | |
this._stateQueue = new Y.AsyncQueue(); | |
this._stateQueue.defaults.timeout = 10; | |
}, | |
destructor : function () { | |
this._handles.each(function (h) { | |
h.detach(); | |
}); | |
} | |
}, { | |
ATTRS : { | |
state : { | |
broadcast : 1, | |
getter : function (val) { | |
return Y.clone(val, true); | |
}, | |
valueFn : function () { | |
return {}; | |
} | |
} | |
} | |
}); | |
// Global register state event. | |
Y.publish(REGISTER_COMPONENT); | |
/** | |
* RegisterState Mixin | |
* | |
* This class is mixed in to Components, registering them as an subscriber | |
* of the state manager. | |
* | |
* The Component itself knows which of its | |
* attributes are stateful, and declares this in a 'stateMap' attribute. | |
* This attribute should have as a value an object of key-value pairs, | |
* mapping local attributes to state attributes. | |
* | |
*/ | |
function RegisterState () { | |
// Call _registerStateAfterInit before 'render' | |
// | |
// @TODO Does a better, more general way to invoke this method exist? | |
// This is attached to render as there is currently no evident way to | |
// insert before the "initialize" phase in components that extend Y.Base. | |
// An render fn (empty) must be added and called if not extending Y.Widget | |
this._registerStateHandle = Y.before(this._registerStateAfterInit, this, 'render', this); | |
} | |
RegisterState.prototype = { | |
/** | |
* Perform Registration of Component after initializer | |
* | |
* Note: This must be invoked manually when no render event fires on | |
* the Component. | |
*/ | |
_registerStateAfterInit : function () { | |
if (this._registerStateHandle) { | |
this._registerStateHandle.detach(); | |
this._registerStateHandle = null; | |
} | |
var attrs = this.getAttrs(), | |
stateMap = this.get('stateMap'), | |
state = {}; | |
Y.after("state-manager:stateChange", function (e) { | |
if (e.component != this.name && !e.silent) { | |
this.handleNewState(e.newVal, e.component, e.register, e.fromManager); | |
} | |
}, this); | |
if (stateMap) { | |
Y.Object.each(attrs, function (val, key) { | |
if (key in stateMap) { | |
this.modifyAttr(key, { | |
broadcast : 1 | |
}); | |
state[stateMap[key]] = this.get(key); | |
this.on(key + 'Change', function (e) { | |
if (Y.Compare(e.newVal, e.prevVal)) { | |
e.halt(); | |
return false; | |
} | |
}); | |
} | |
}, this); | |
} | |
// Fire global register state event | |
Y.fire(REGISTER_COMPONENT, { | |
name : this.name, | |
state : state, | |
map : stateMap | |
}); | |
}, | |
/** | |
* Default handleNewState method. | |
* | |
* Updates attributes mapped in stateMap with the new state. | |
* | |
* @param Object state The new state object. | |
* @param String src The source Component. | |
*/ | |
handleNewState : function (state, src, initial, fromManager) { | |
if (src == this.name) { | |
return; | |
} | |
var map = this.get('stateMap'); | |
if (map) { | |
Y.Object.each(map, function (stateProp, targetKey) { | |
if (stateProp in state) { | |
var val = state[stateProp], | |
current = this.get(targetKey); | |
if (!initial && Y.Compare(val, current)) { | |
return; | |
} | |
this.set(targetKey, val, {src : src, initial : initial, fromManager : fromManager}); | |
} | |
}, this); | |
} | |
} | |
} | |
Y.RegisterState = RegisterState; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment