Created
July 30, 2013 08:44
-
-
Save garrus/6111327 to your computer and use it in GitHub Desktop.
pagemanager, eventmanage, datapersister
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
/** | |
* A Renderer is responsible for rendering specified container on the page, maybe | |
* with given jquery template. It should also delegate UI events on the container. | |
* On destroyed, it will clear the container and remove all delegates. | |
* | |
* @param id | |
* @param container | |
* @param jQTemplateId | |
* @param defaultRenderMode | |
* @constructor | |
*/ | |
function Renderer(id, container, jQTemplateId, defaultRenderMode) { | |
this.id = id; | |
this.container = $(container); //TODO validation | |
this.jqTemplateId = jQTemplateId; //TODO validation | |
this.defaultRenderMode = /^redraw|append|prepend$/.test(defaultRenderMode) ? defaultRenderMode : 'redraw'; | |
this._eventHandlers = []; | |
this._destroyCallback = undefined; | |
this._behaviors = {}; | |
} | |
$.extend(Renderer.prototype, { | |
reset: function () { | |
var c = this.container; | |
c.empty(); | |
this._eventHandlers.map(function (index, args) { | |
c.undelegate.apply(c, args); | |
c.delegate.apply(c, args); | |
}); | |
}, | |
toString: function () { | |
return '[Renderer "' + this.id + '"]'; | |
}, | |
render: function (data, renderMode) { | |
var content = ''; | |
if (typeof data == 'object') { | |
if (data instanceof jQuery || data instanceof Element) { | |
content = data; | |
} else { | |
if (this.jqTemplateId) { | |
content = $(this.jqTemplateId).tmp(data); | |
} else { | |
log(this + ' Rendering data requires jqTemplate.', 'warning'); | |
return; | |
} | |
} | |
} else { | |
content = data.toString(); | |
} | |
this._renderInternal(content, renderMode); | |
}, | |
_renderInternal: function (content, renderMode) { | |
renderMode = /^redraw|append|prepend$/.test(renderMode) ? renderMode : this.defaultRenderMode; | |
switch (renderMode) { | |
case 'redraw': | |
this.container.empty().append(content); | |
break; | |
case 'append': | |
this.container.append(content); | |
break; | |
case 'prepend': | |
this.container.prepend(content); | |
break; | |
default: | |
log(this + ' Unexpected executing point: _renderInternal', 'warning'); | |
break; | |
} | |
}, | |
execute: function (behaviorName, data) { | |
var handler = this._behaviors[behaviorName]; | |
if (handler) { | |
log(this + ' Behaving "' + behaviorName + '".', 'debug'); | |
handler.call(this.container, data ? data : undefined); | |
} else { | |
log(this + ' No behavior defined with name "' + behaviorName + '".', 'warning'); | |
} | |
}, | |
/* | |
update: function(selector, callback){ | |
var $target = this.container.find(selector); | |
if ($target.length) { | |
try { | |
callback($target); | |
} catch (e) { | |
dumpError(e, '[Renderer "' + this.id + '"] Error occurred updating "' + $target.selector + '"'); | |
} | |
} else { | |
log('[Renderer "' + this.id + '"] No dom selected with relative selector "' + selector + '".', 'warning'); | |
} | |
}, | |
*/ | |
setBehavior: function (behaviorName, handler) { | |
if (typeof handler == 'function') { | |
log(this + ' Defining behavior with name "' + behaviorName + '".', 'debug'); | |
if (this._behaviors[behaviorName]) { | |
log(this + ' Overriding behavior of name "' + behaviorName + '".', 'warning'); | |
} | |
this._behaviors[behaviorName] = handler; | |
} | |
return this; | |
}, | |
on: function (selector, eventType, callback) { | |
this._eventHandlers.push([selector, eventType, callback]); | |
this.container.delegate(selector, eventType, callback); | |
log(this + ' delegate event "' + eventType + '" for "' + selector + '".', 'debug'); | |
return this; | |
}, | |
destroy: function (callback) { | |
if (callback) { | |
this._destroyCallback = callback; | |
log(this + ' Set destroy callback.', 'debug'); | |
} else { | |
log(this + ' Destroying.', 'debug'); | |
var c = this.container; | |
this._eventHandlers.map(function (args) { | |
c.undelegate.apply(c, args); | |
}); | |
c.empty(); | |
if (typeof this._destroyCallback == 'function') { | |
this._destroyCallback(); | |
} | |
} | |
} | |
}); | |
// this component manages the internal events. other components may register some event listener, or | |
// trigger a certain event here. Once an event is triggered, it will be broadcast to all listeners that | |
// have registered to this kind of event. | |
var EventManager = { | |
eventQueue: [], | |
handlers: {}, | |
timeo: 0, | |
init: function () { | |
this.eventQueue = []; | |
this.handlers = {}; | |
this.timeo = 0; | |
this.run(); | |
}, | |
run: function () { | |
clearTimeout(this.timeo); | |
var ths = this; | |
setTimeout(function () { | |
ths.runInternal(); | |
}, 1); | |
this.timeo = setTimeout(function () { | |
ths.run(); | |
}, 1000); | |
}, | |
runInternal: function () { | |
var queue = this.eventQueue; | |
if (queue.length) { | |
var e = queue[0]; // the first event is the oldest | |
this.eventQueue = queue.slice(1); | |
// trigger the handlers | |
var handlers = this.handlers[e.name]; | |
if (handlers && handlers.length) { | |
handlers.map(function (callback) { | |
try { | |
callback.apply(null, e.data); | |
} catch (error) { | |
dumpError(error); | |
} | |
}); | |
} | |
// if there remains tasks, schedule next running | |
if (this.eventQueue.length) { | |
var ths = this; | |
setTimeout(function () { | |
ths.runInternal(); | |
}, 1); | |
} | |
} | |
}, | |
/** | |
* | |
* @param {string|object} name can be a string or an object map {eventName: callback} | |
* @param {function} [callback] | |
* @returns {EventManager} | |
*/ | |
bind: function (name, callback) { | |
function addCallback(eventType, handler) { | |
var handlers = EventManager.handlers[eventType]; | |
if (!handlers) { | |
handlers = [handler]; | |
} else { | |
handlers.push(handler); | |
} | |
EventManager.handlers[eventType] = handlers; | |
} | |
if (typeof name == 'string' && typeof callback == 'function') { | |
addCallback(name, callback); | |
} else if (typeof name == 'object') { | |
for (var n in name) { | |
if (name.hasOwnProperty(n) && typeof name[n] == 'function') { | |
addCallback(n, name[n]); | |
} | |
} | |
} | |
return this; | |
}, | |
trigger: function (name, data) { | |
log('Event "' + name + '" triggered.', 'debug'); | |
this.eventQueue.push({ | |
name: name, | |
data: data | |
}); | |
this.run(); | |
} | |
}; | |
// this component manages page rendering. other components may create a renderer here, and afterward | |
// request to render some data. PageManager will manage all renderers | |
var PageManager = { | |
_renderers: {}, | |
_groups: {}, | |
init: function () { | |
var renderers = this._renderers; | |
for (var id in renderers) { | |
if (renderers.hasOwnProperty(id)) { | |
renderers[id].destroy(); | |
} | |
} | |
this._renderers = {}; | |
this._groups = {}; | |
}, | |
render: function (id, data, renderMode) { | |
var renderer = this._renderers[id]; | |
if (renderer) { | |
renderer.render(data, renderMode); | |
} else { | |
log('Unknown renderer id "' + id + '". No data is rendered.', 'warning'); | |
} | |
}, | |
execute: function (id, behaviorName, data) { | |
var renderer = this._renderers[id]; | |
if (renderer) { | |
renderer.execute(behaviorName, data); | |
} else { | |
log('Unknown renderer id "' + id + '". No behavior is executed.', 'warning'); | |
} | |
}, | |
createRenderer: function (id, containerSelector, jqTemplateId, defaultRenderMode) { | |
var renderer = new Renderer(id, containerSelector, jqTemplateId, defaultRenderMode); | |
log(renderer + ' created.', 'debug'); | |
if (this._renderers[id]) { | |
log('An old renderer with the same id "' + id + '" already exists, which will be destroyed.', 'warning'); | |
this._renderers[id].destroy(); | |
} | |
var groupInfo = this._parseGroupInfo(id); | |
if (groupInfo) { | |
this._addGroupContainer(groupInfo.name, groupInfo.id, containerSelector); | |
} | |
this._renderers[id] = renderer; | |
return renderer; | |
}, | |
createPopup: function () { | |
//TODO | |
}, | |
createSubContainer: function (parentSelector, tagName, attributes) { | |
var id = 'sc' + Math.random().toString().slice(2, 18); | |
if (typeof(tagName) !== 'string') { | |
tagName = 'div'; | |
} | |
var subContainer = $('<' + tagName + ' id="' + id + '"></' + tagName + '>'); | |
if (typeof attributes == 'object') { | |
for (var attrName in attributes) { | |
if (attributes.hasOwnProperty(attrName)) { | |
subContainer.attr(attrName, attributes[attrName]); | |
} | |
} | |
} | |
subContainer.hide(); | |
$(parentSelector).append(subContainer); | |
return id; | |
}, | |
addDelegate: function (id, selector, eventName, callback) { | |
if (this._renderers[id]) { | |
this._renderers[id].on(selector, eventName, callback); | |
} else { | |
log('Unknown renderer id "' + id + '". No event is delegated.', 'warning'); | |
} | |
}, | |
addBehavior: function (id, behaviorName, handler) { | |
if (this._renderers[id]) { | |
this._renderers[id].setBehavior(behaviorName, handler); | |
} else { | |
log('Unknown renderer id "' + id + '". No behavior is added.', 'warning'); | |
} | |
}, | |
show: function (id) { | |
var groupInfo = this._parseGroupInfo(id); | |
if (groupInfo) { | |
this._groupToggle(groupInfo.name, groupInfo.id, true); | |
} else { | |
this._toggleRenderer(id, true); | |
} | |
}, | |
hide: function (id) { | |
this._toggleRenderer(id, false); | |
}, | |
removeRenderer: function (id) { | |
var renderer = this._renderers[id]; | |
if (renderer) { | |
renderer.destroy(); | |
delete this._renderers[id]; | |
var groupInfo = this._parseGroupInfo(id); | |
if (groupInfo) { | |
this._removeGroupContainer(groupInfo.name, groupInfo.id); | |
} | |
} else { | |
log('Unknown renderer id "' + id + '". No renderer is destroyed.', 'warning'); | |
} | |
}, | |
_parseGroupInfo: function (id) { | |
var offset = id.indexOf('.'); | |
if (offset != -1) { | |
return { | |
name: id.slice(0, offset), | |
id: id.slice(offset + 1) | |
}; | |
} else { | |
return null; | |
} | |
}, | |
_addGroupContainer: function (groupName, idInGroup, containerSelector) { | |
var group = this._groups[groupName]; | |
if (!group) { | |
this._groups[groupName] = group = {}; | |
} | |
group[idInGroup] = containerSelector; | |
log('Container ' + containerSelector + ' is added to group ' + groupName + ' as ' + idInGroup, 'debug'); | |
}, | |
_removeGroupContainer: function (groupName, idInGroup) { | |
var group = this._groups[groupName]; | |
if (group) { | |
delete group[idInGroup]; | |
} | |
}, | |
_groupToggle: function (groupName, idInGroup) { | |
var group = this._groups[groupName]; | |
if (group) { | |
for (var id in group) { | |
if (group.hasOwnProperty(id)) { | |
this._toggleRenderer(groupName + '.' + id, false); | |
} | |
} | |
this._toggleRenderer(groupName + '.' + idInGroup, true); | |
} else { | |
log('Unknown group name "' + groupName + '". No group is toggled.', 'warning'); | |
} | |
}, | |
_toggleRenderer: function (id, show) { | |
var renderer = this._renderers[id]; | |
if (renderer) { | |
renderer.container.toggle(show); | |
} else { | |
log('Unknown renderer id "' + id + '". No renderer is toggled.', 'warning'); | |
} | |
} | |
}; | |
var DataPersister = function () { | |
this._prefix = null; | |
this._lastRead = {}; | |
}; | |
$.extend(DataPersister, { | |
_instances: {}, | |
load: function (user) { | |
if (this._instances[user]) { | |
return this._instances[user]; | |
} else { | |
return this._instances[user] = new DataPersister(user); | |
} | |
} | |
}); | |
$.extend(DataPersister.prototype, { | |
load: function (user) { | |
if (this._prefix !== user) { | |
this._prefix = user; | |
this._lastRead = {}; | |
} | |
return this; | |
}, | |
_readFromCache: function (key) { | |
return this._lastRead.key == key ? this._lastRead.value : null; | |
}, | |
read: function (key) { | |
var r = this._readFromCache(key); | |
if (r === null) { | |
r = R(this._prefix + key); | |
this._lastRead = {key: key, value: r}; | |
} | |
return r; | |
}, | |
write: function (key, value) { | |
if (this._lastRead.key == key) { | |
this._lastRead.value = value; | |
} | |
S(this._prefix + key, value); | |
}, | |
remove: function (key) { | |
if (this._lastRead.key == key) { | |
this._lastRead = {}; | |
} | |
D(this._prefix + key); | |
}, | |
contains: function (key) { | |
return this.read(key) !== undefined; | |
}, | |
include: function (key, value) { | |
if (this.contains(key)) { | |
var vals = this.read(key); | |
if (typeof vals == 'object' && vals instanceof Array) { | |
if (vals.indexOf(value) == -1) { | |
vals.push(value); | |
this.write(key, vals); | |
} | |
} else { | |
console.error('Persisted data at offset "' + key + '" does not support multiple values.'); | |
} | |
} else { | |
this.write(key, [value]); | |
} | |
}, | |
exclude: function (key, value) { | |
if (this.contains(key)) { | |
var vals = this.read(key); | |
if (typeof vals == 'object' && vals instanceof Array) { | |
var pos = vals.indexOf(value); | |
if (pos !== -1) { | |
vals = vals.slice(0, pos).concat(vals.slice(pos + 1)); | |
this.write(key, vals); | |
} | |
} else { | |
console.error('Persisted data at offset "' + key + '" does not support multiple values.'); | |
} | |
} | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment