Skip to content

Instantly share code, notes, and snippets.

@ghinch
Created February 19, 2011 19:45
Show Gist options
  • Save ghinch/835307 to your computer and use it in GitHub Desktop.
Save ghinch/835307 to your computer and use it in GitHub Desktop.
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