diff --git a/docs/sudo-x.html b/docs/sudo-x.html deleted file mode 100644 index a4d82dc..0000000 --- a/docs/sudo-x.html +++ /dev/null @@ -1,1452 +0,0 @@ -
sudo-x.js | |
---|---|
(function(window) { | |
Sudo Namespace | var sudo = { |
Namespace for
| delegates: {}, |
The sudo.extensions namespace holds the objects that are stand alone
| extensions: {}, |
getPath- -Extract a value located at
| getPath: function getPath(path, obj) {
- var key, p;
- p = path.split('.');
- for (key; p.length && (key = p.shift());) {
- if(!p.length) {
- return obj[key];
- } else {
- obj = obj[key] || {};
- }
- }
- return obj;
- }, |
inherit- -Inherit the prototype from a parent to a child.
-Set the childs constructor for subclasses of child.
-Subclasses of the library base classes will not
-want to use this function in most use-cases. Why? User Sudo Class Objects
-possess their own constructors and any call back to a
| inherit: function inherit(parent, child) {
- child.prototype = Object.create(parent.prototype);
- child.prototype.constructor = child;
- }, |
makeMeASandwich- -Notice there is no need to extrinsically instruct how to -make the sandwich, just the elegant single command. - -
| makeMeASandwich: function makeMeASandwich() {return 'Okay.';}, |
namespace- -Method for assuring a Namespace is defined. - -
| namespace: function namespace(path) {
- if (!this.getPath(path, window)) {
- this.setPath(path, {}, window);
- }
- }, |
premier- -The premier object takes precedence over all others so define it at the topmost level. - -
| premier: null, |
setPath- -Traverse the keypath and get each object -(or make blank ones) eventually setting the value -at the end of the path - -
| setPath: function setPath(path, value, obj) {
- var p = path.split('.'), key;
- for (key; p.length && (key = p.shift());) {
- if(!p.length) {
- obj[key] = value;
- } else if (obj[key]) {
- obj = obj[key];
- } else {
- obj = obj[key] = {};
- }
- }
- }, |
uid- -Some sudo Objects use a unique integer as a | uid: 0, |
unique- -An integer used as 'tags' by some sudo Objects as well -as a unique string for views when needed - -
| unique: function unique(prefix) {
- return prefix ? prefix + this.uid++ : this.uid++;
- }, |
unsetPath- -Remove a key:value pair from this object's data store
-located at
| unsetPath: function unsetPath(path, obj) {
- var p = path.split('.'), key;
- for (key; p.length && (key = p.shift());) {
- if(!p.length) {
- delete obj[key];
- } else { |
this can fail if a faulty path is passed. -using getPath beforehand can prevent that | obj = obj[key];
- }
- }
- }
-}; |
Base Class Object- -All sudo.js objects inherit base, giving the ability
-to utilize delegation, the
| sudo.Base = function() { |
can delegate | this.delegates = []; |
a beautiful and unique snowflake | this.uid = sudo.unique();
-}; |
addDelegate- -Push an instance of a Class Object into this object's
| sudo.Base.prototype.addDelegate = function addDelegate(del) {
- del.delegator = this;
- this.delegates.push(del);
- if('addedAsDelegate' in del) del.addedAsDelegate(this);
- return this;
-}; |
base- -Lookup the function matching the name passed in and call it with
-any passed in argumets scoped to the calling object.
-This method will avoid the recursive-loop problem by making sure
-that the first match is not the function that called
| sudo.Base.prototype.base = function base() {
- var args = Array.prototype.slice.call(arguments),
- name = args.shift(),
- found = false,
- obj = this,
- curr; |
find method on the prototype, excluding the caller | while(!found) {
- curr = Object.getPrototypeOf(obj);
- if(curr[name] && curr[name] !== this[name]) found = true; |
keep digging | else obj = curr;
- }
- return curr[name].apply(this, args);
-}; |
construct- -A convenience method that alleviates the need to place:
- | sudo.Base.prototype.construct = function construct() {
- Object.getPrototypeOf(this).constructor.apply(this, arguments || []);
-}; |
delegate- -From this object's list of delegates find the object whose
| sudo.Base.prototype.delegate = function delegate(role, meth) {
- var del = this.delegates, i;
- for(i = 0; i < del.length; i++) {
- if(del[i].role === role) {
- if(!meth) return del[i];
- return del[i][meth].bind(del[i]);
- }
- }
-}; |
getDelegate- -Fetch a delegate whose role property matches the passed in argument.
-Uses the
| sudo.Base.prototype.getDelegate = function getDelegate(role) {
- return this.delegate(role);
-}; |
removeDelegate- -From this objects
| sudo.Base.prototype.removeDelegate = function removeDelegate(role) {
- var del = this.delegates, i;
- for(i = 0; i < del.length; i++) {
- if(del[i].role === role) { |
no delegator for you | del[i].delegator = void 0;
- del.splice(i, 1);
- return this;
- }
- }
- return this;
-}; |
| sudo.Base.prototype.role = 'base'; |
Model Class Object- -Model Objects expose methods for setting and getting data, and
-can be observed if implementing the
| sudo.Model = function(data) {
- sudo.Base.call(this);
- this.data = data || {}; |
only models are | this.callbacks = [];
- this.changeRecords = [];
-}; |
Model inherits from sudo.Base
- | sudo.inherit(sudo.Base, sudo.Model); |
get- -Returns the value associated with a key. - -
| sudo.Model.prototype.get = function get(key) {
- return this.data[key];
-}; |
getPath- -Uses the sudo namespace's getpath function operating on the model's -data hash. - -
| sudo.Model.prototype.getPath = function getPath(path) {
- return sudo.getPath(path, this.data);
-}; |
gets- -Assembles and returns an object of key:value pairs for each key -contained in the passed in Array. - -
| sudo.Model.prototype.gets = function gets(ary) {
- var i, obj = {};
- for (i = 0; i < ary.length; i++) {
- obj[ary[i]] = ary[i].indexOf('.') === -1 ? this.data[ary[i]] :
- this.getPath(ary[i]);
- }
- return obj;
-}; |
| sudo.Model.prototype.role = 'model'; |
set- -Set a key:value pair. - -
| sudo.Model.prototype.set = function set(key, value) { |
NOTE: intentional possibilty of setting a falsy value | this.data[key] = value;
- return this;
-}; |
setPath- -Uses the sudo namespace's setpath function operating on the model's -data hash. - -
| sudo.Model.prototype.setPath = function setPath(path, value) {
- sudo.setPath(path, value, this.data);
- return this;
-}; |
sets- -Invokes
| sudo.Model.prototype.sets = function sets(obj) {
- var i, k = Object.keys(obj);
- for(i = 0; i < k.length; i++) {
- k[i].indexOf('.') === -1 ? this.set(k[i], obj[k[i]]) :
- this.setPath(k[i], obj[k[i]]);
- }
- return this;
-}; |
unset- -Remove a key:value pair from this object's data store - -
| sudo.Model.prototype.unset = function unset(key) {
- delete this.data[key];
- return this;
-}; |
unsetPath- -Uses
| sudo.Model.prototype.unsetPath = function unsetPath(path) {
- sudo.unsetPath(path, this.data);
- return this;
-}; |
unsets- -Deletes a number of keys or paths from this object's data store - -
| sudo.Model.prototype.unsets = function unsets(ary) {
- var i;
- for(i = 0; i < ary.length; i++) {
- ary[i].indexOf('.') === -1 ? this.unset(ary[i]) :
- this.unsetPath(ary[i]);
- }
- return this;
-}; |
Container Class Object- -A container is any object that can both contain other objects and -itself be contained - -
| sudo.Container = function() {
- sudo.Base.call(this);
- this.children = [];
- this.childNames = {};
-}; |
Container is a subclass of sudo.Base | sudo.inherit(sudo.Base, sudo.Container); |
addChild- -Adds a View to this container's list of children.
-Also adds an 'index' property and an entry in the childNames hash.
-If
| sudo.Container.prototype.addChild = function addChild(child, name) {
- var c = this.children;
- child.parent = this;
- child.index = c.length;
- if(name) {
- child.name = name;
- this.childNames[name] = child.index;
- }
- c.push(child);
- if('addedToParent' in child) child.addedToParent(this);
- return this;
-}; |
bubble- -By default,
| sudo.Container.prototype.bubble = function bubble() {return this.parent;}; |
getChild- -If a child was added with a name, via
| sudo.Container.prototype.getChild = function getChild(id) {
- return typeof id === 'string' ? this.children[this.childNames[id]] :
- this.children[id];
-}; |
indexChildren- -Method is called with the | sudo.Container.prototype._indexChildren_ = function _indexChildren_(i) {
- var c = this.children, obj = this.childNames, len;
- for (len = c.length; i < len; i++) {
- c[i].index--; |
adjust any entries in childNames | if(c[i].name in obj) obj[c[i].name] = c[i].index;
- }
-}; |
removeChild- -Find the intended child from my list of children and remove it, removing the name reference and re-indexing
-remaining children. This method does not remove the child's DOM.
-Override this method, doing whatever you want to the child's DOM, then call
| sudo.Container.prototype.removeChild = function removeChild(arg) {
- var i, t = typeof arg, c; |
normalize the input | if(t === 'object') c = arg;
- else c = t === 'string' ? this.children[this.childNames[arg]] : this.children[arg];
- i = c.index; |
remove from the children Array | this.children.splice(i, 1); |
remove from the named child hash if present | delete this.childNames[c.name]; |
child is now an | delete c.parent;
- delete c.index;
- delete c.name;
- this._indexChildren_(i);
- return this;
-}; |
removeChildren- -Remove all children, name references and adjust indexes accordingly. -This method calls removeFromParent as each child may have overridden logic there. - -
| sudo.Container.prototype.removeChildren = function removeChildren() {
- while(this.children.length) {
- this.children.shift().removeFromParent();
- }
- return this;
-}; |
removeFromParent- -Remove this object from its parents list of children. -Does not alter the dom - do that yourself by overriding this method -or chaining method calls | sudo.Container.prototype.removeFromParent = function removeFromParent() { |
will error without a parent, but that would be your fault... | this.parent.removeChild(this);
- return this;
-};
-sudo.Container.prototype.role = 'container'; |
send- -The call to the specific method on a (un)specified target happens here.
-If this Object is part of a TODO Currently, only the first target method found is called, then the -bubbling is stopped. Should bubbling continue all the way up the 'chain'? - -
| sudo.Container.prototype.send = function send(/*args*/) {
- var args = Array.prototype.slice.call(arguments),
- d = this.model && this.model.data, meth, targ, fn; |
normalize the input, common use cases first | if(d && 'sendMethod' in d) meth = d.sendMethod;
- else if(typeof args[0] === 'string') meth = args.shift(); |
less common but viable options | if(!meth) { |
passed as a jquery custom data attr bound in events | meth = 'data' in args[0] ? args[0].data.sendMethod : |
passed in a hash from something or not passed at all | args[0].sendMethod || void 0;
- } |
target is either specified or my parent | targ = d && d.sendTarget || this.bubble(); |
obvious chance for errors here, don't be dumb | fn = targ[meth];
- while(!fn && (targ = targ.bubble())) {
- fn = targ[meth];
- } |
sendMethods expect a signature (sender, ...) | if(fn) {
- args.unshift(this);
- fn.apply(targ, args);
- }
- return this;
-}; |
View Class Object | |
Create an instance of a sudo.View object. A view is any object
-that maintains its own The view object uses jquery for dom manipulation
-and event delegation etc... A jquerified
| sudo.View = function(el, data) {
- sudo.Container.call(this); |
allow model instance to be passed in as well | if(data) {
- this.model = data.role === 'model' ? data :
- this.model = new sudo.Model(data);
- }
- this.setEl(el);
- if(this.role === 'view') this.init();
-}; |
View inherits from Container
- | sudo.inherit(sudo.Container, sudo.View); |
becomePremier- -Premier functionality provides hooks for behavioral differentiation -among elements or class objects. - -
| sudo.View.prototype.becomePremier = function becomePremier() {
- var p, f = function() {
- this.isPremier = true;
- sudo.premier = this;
- }.bind(this); |
is there an existing premier that isn't me? | if((p = sudo.premier) && p.uid !== this.uid) { |
ask it to resign and call the cb | p.resignPremier(f);
- } else f(); // no existing premier
- return this;
-}; |
init- -A 'contruction-time' hook to call for further initialization needs in -View objects (and their subclasses). A noop by default child classes should override. | sudo.View.prototype.init = $.noop; |
the el needs to be normalized before use
- | sudo.View.prototype._normalizedEl_ = function _normalizedEl_(el) {
- if(typeof el === 'string') {
- return $(el);
- } else { |
Passed an already | return el.length ? el : $(el);
- }
-}; |
resignPremier- -Resign premier status - -
| sudo.View.prototype.resignPremier = function resignPremier(cb) {
- var p;
- this.isPremier = false; |
only remove the global premier if it is me | if((p = sudo.premier) && p.uid === this.uid) {
- sudo.premier = null;
- } |
fire the cb if passed | if(cb) cb();
- return this;
-}; |
| sudo.View.prototype.role = 'view'; |
setEl- -A view must have an element, set that here.
-Stores a jquerified object as
| sudo.View.prototype.setEl = function setEl(el) {
- var d = this.model && this.model.data, a, t;
- if(!el) { |
normalize any relevant data | t = d ? d.tagName || 'div': 'div';
- this.$el = $(document.createElement(t));
- if(d && (a = d.attributes)) this.$el.attr(a);
- } else {
- this.$el = this._normalizedEl_(el);
- }
- return this;
-}; |
this.$- -Return a single Element matching
| sudo.View.prototype.$ = function(sel) {
- return this.$el.find(sel);
-}; |
ViewController Class Object | |
ViewControllers were designed for Rails projects for 2 specific use-cases: - -
| sudo.ViewController = function(el, data) {
- sudo.View.call(this, el, data); |
map the names of events to methods we expect to proxy to | this.eventMap = {
- 'ajax:before': 'onAjaxBefore',
- 'ajax:beforeSend': 'onAjaxBeforeSend',
- 'ajax:success': 'onAjaxSuccess',
- 'ajax:error': 'onAjaxError',
- 'ajax:complete': 'onAjaxComplete',
- 'ajax:aborted:required': 'onAjaxAbortedRequired',
- 'ajax:aborted:file': 'onAjaxAbortedFile'
- }; |
can be called again if mapping changes... | if(data) {
- this.doMapping();
- if('descriptor' in data) this.instantiateChildren([data.descriptor]);
- else if('descriptors' in data) this.instantiateChildren();
- }
- if(this.role === 'viewController') this.init();
-}; |
ViewController inherits from View.
- | sudo.inherit(sudo.View, sudo.ViewController); |
doMapping- -assign the proxy mapping for events. This can be called at any time -if the listened for events change - -
| sudo.ViewController.prototype.doMapping = function() { |
either a single event or an array of them | var i,
- toMap = this.model.data.ujsEvent || this.model.data.ujsEvents;
- if(toMap) {
- if(typeof toMap === 'string') this._mapEvent_(toMap);
- else {
- for(i = 0; i < toMap.length; i++) {
- this._mapEvent_(toMap[i]);
- }
- }
- }
- return this;
-}; |
handleObserve- -Helper for instantiateChildren
- | sudo.ViewController.prototype._handleObserve_ = function _handleObserve_(obs, c) {
- var obj = obs.object ? this._objectForPath_(obs.object) : this.model;
- obj.observe(c[obs.cb].bind(c));
-}; |
instantiateChildren- -instantiate the children described in the passed in array or the
| sudo.ViewController.prototype.instantiateChildren = function instantiateChildren(ary) {
- var i, j, curr, c, d = ary || this.model.data.descriptors;
- for(i = 0; i < d.length; i++) {
- curr = d[i];
- c = new curr.is_a(curr.el, curr.data);
- this.addChild(c, curr.name); |
handle any observe(s) | if('observe' in curr) {
- this._handleObserve_(curr.observe, c);
- }
- else if('observes' in curr) {
- for(j = 0; j < curr.observes.length; j++) {
- this._handleObserve_(curr.observes[j], c);
- }
- }
- }
- return this;
-}; |
mapEvent- -Maps the ajax:event names to methods
- | sudo.ViewController.prototype._mapEvent_ = function _mapEvent_(name) { |
because the signatures vary we need specific methods | this.$el.on(name, this[this.eventMap[name]].bind(this));
-}; |
objectForPath- -The objects used for callbacks and connections need to be
-looked-up via a key-path like address as they likely will not exist
-when viewController's are instantiated.
- | sudo.ViewController.prototype._objectForPath_ = function _objectForPath_(path) {
- return sudo.getPath(path, window);
-}; |
Virtual methods to override in your child classes for -any events you chose to listen for | sudo.ViewController.prototype.onAjaxAbortedFile = $.noop;
-sudo.ViewController.prototype.onAjaxAbortedRequired = $.noop;
-sudo.ViewController.prototype.onAjaxBefore = $.noop;
-sudo.ViewController.prototype.onAjaxBeforeSend = $.noop;
-sudo.ViewController.prototype.onAjaxComplete = $.noop;
-sudo.ViewController.prototype.onAjaxSuccess = $.noop;
-sudo.ViewController.prototype.onAjaxError = $.noop; |
| sudo.ViewController.prototype.role = 'viewController'; |
Templating | |
Allow the default {{ js code }}, {{= key }}, and {{- escape stuff }} -micro templating delimiters to be overridden if desired - -
| sudo.templateSettings = {
- evaluate: /\{\{([\s\S]+?)\}\}/g,
- interpolate: /\{\{=([\s\S]+?)\}\}/g,
- escape: /\{\{-([\s\S]+?)\}\}/g
-}; |
Certain characters need to be escaped so that they can be put -into a string literal when templating. - -
| sudo.escapes = {};
-(function(s) {
- var e = {
- '\\': '\\',
- "'": "'",
- r: '\r',
- n: '\n',
- t: '\t',
- u2028: '\u2028',
- u2029: '\u2029'
- };
- for (var key in e) s.escapes[e[key]] = key;
-}(sudo)); |
lookup hash for
| sudo.htmlEscapes = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": ''',
- '/': '/'
-}; |
Escapes certain characters for templating - -
| sudo.escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; |
Escape unsafe HTML - -
| sudo.htmlEscaper = /[&<>"'\/]/g; |
Unescapes certain characters for templating - -
| sudo.unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g; |
escape- -Remove unsafe characters from a string - -
| sudo.escape = function(str) {
- return str.replace(sudo.htmlEscaper, function(match) {
- return sudo.htmlEscapes[match];
- });
-}; |
unescape- -Within an interpolation, evaluation, or escaping, -remove HTML escaping that had been previously added. - -
| sudo.unescape = function unescape(str) {
- return str.replace(sudo.unescaper, function(match, escape) {
- return sudo.escapes[escape];
- });
-}; |
template- -JavaScript micro-templating, similar to John Resig's (and it's offspring) implementation.
-sudo templating preserves whitespace, and correctly escapes quotes within interpolated code.
-Unlike others sudo.template requires a scope name (to avoid the use of
| sudo.template = function template(str, data, scope) {
- scope || (scope = 'data');
- var settings = sudo.templateSettings, render, template, |
Compile the template source, taking care to escape characters that -cannot be included in a string literal and then unescape them in code blocks. | source = "_p+='" + str.replace(sudo.escaper, function(match) {
- return '\\' + sudo.escapes[match];
- }).replace(settings.escape, function(match, code) {
- return "'+\n((_t=(" + sudo.unescape(code) + "))==null?'':sudo.escape(_t))+\n'";
- }).replace(settings.interpolate, function(match, code) {
- return "'+\n((_t=(" + sudo.unescape(code) + "))==null?'':_t)+\n'";
- }).replace(settings.evaluate, function(match, code) {
- return "';\n" + sudo.unescape(code) + "\n_p+='";
- }) + "';\n";
- source = "var _t,_p='';" + source + "return _p;\n";
- render = new Function(scope, source);
- if (data) return render(data);
- template = function(data) {
- return render.call(this, data);
- }; |
Provide the compiled function source as a convenience for reflection/compilation | template.source = 'function(' + scope + '){\n' + source + '}';
- return template;
-}; |
DataView Class Object | |
Create an instance of an Object, inheriting from sudo.View that:
-1. Expects to have a template located in its internal data Store accessible via
| sudo.DataView = function(el, data) {
- sudo.View.call(this, el, data); |
implements the listener extension | $.extend(this, sudo.extensions.listener); |
dataview's models are observable, make it so if not already | if(!this.model.observe) $.extend(this.model, sudo.extensions.observable); |
dont autoRender on the setting of events, -add to this to prevent others if needed | this.autoRenderBlacklist = {event: true, events: true}; |
if autorendering, observe your own model -use this ref to unobserve if desired | if(this.model.data.autoRender) this.observer = this.model.observe(this.render.bind(this));
- this.build();
- this.bindEvents();
- if(this.role === 'dataview') this.init();
-}; |
| sudo.inherit(sudo.View, sudo.DataView); |
addedToParent- -Container's will check for the presence of this method and call it if it is present -after adding a child - essentially, this will auto render the dataview when added to a parent | sudo.DataView.prototype.addedToParent = function(parent) {
- return this.render();
-}; |
build- -Construct the innerHTML of the $el here so that the behavior of the
-DataView, that the markup is ready after a subclass calls | sudo.DataView.prototype.build = function build() {
- var t;
- if(!(t = this.model.data.template)) return;
- if(typeof t === 'string') t = sudo.template(t);
- this.$el.html(t(this.model.data));
- this.built = true;
- return this;
-}; |
removeFromParent- -Remove this object from the DOM and its parent's list of children.
-Overrides
| sudo.DataView.prototype.removeFromParent = function removeFromParent() {
- this.parent.removeChild(this);
- this.$el.remove();
- return this;
-}; |
render- -(Re)hydrate the innerHTML of this object via its template and internal data store.
-If a
| sudo.DataView.prototype.render = function render(change) {
- var d; |
return early if a | if(change && this.autoRenderBlacklist[change.name]) return this;
- d = this.model.data; |
has | if(!this.built) this.build(); |
if there is no template by this point you are doing it wrong -erase the flag | else this.built = false;
- if(d.renderTarget) {
- this._normalizedEl_(d.renderTarget)[d.renderMethod || 'append'](this.$el);
- delete d.renderTarget;
- }
- return this;
-}; |
| sudo.DataView.prototype.role = 'dataview'; |
Navigator Class Object | |
Abstracts location and history events, parsing their information into a -normalized object that is then set to an Observable class instance - -
| sudo.Navigator = function(data) {
- this.started = false;
- this.slashStripper = /^\/+|\/+$/g;
- this.leadingStripper = /^[#\/]|\s+$/g;
- this.trailingStripper = /\/$/;
- this.construct(data);
-}; |
Navigator inherits from | sudo.Navigator.prototype = Object.create(sudo.Model.prototype); |
getFragment- -'Fragment' is defined as any URL information after the 'root' path
-including the
| sudo.Navigator.prototype.getFragment = function getFragment(fragment) {
- var root = this.data.root;
- if(!fragment) { |
intentional use of coersion | if (this.isPushState) {
- fragment = window.location.pathname;
- root = root.replace(this.trailingStripper, '');
- if(!fragment.indexOf(root)) fragment = fragment.substr(root.length);
- } else {
- fragment = this.getHash();
- }
- }
- return decodeURIComponent(fragment.replace(this.leadingStripper, ''));
-}; |
getHash- -Check either the passed in fragment, or the full location.href
-for a
| sudo.Navigator.prototype.getHash = function getHash(fragment) {
- fragment || (fragment = window.location.href);
- var match = fragment.match(/#(.*)$/);
- return match ? match[1] : '';
-}; |
getSearch- -Check either the passed in fragment, or the full location.href
-for a
| sudo.Navigator.prototype.getSearch = function getSearch(fragment) {
- fragment || (fragment = window.location.href);
- var match = fragment.match(/\?(.*)$/);
- return match ? match[1] : '';
-}; |
getUrl- -fetch the URL in the form
| sudo.Navigator.prototype.getUrl = function getUrl() { |
note that delegate(role) returns the deleagte | return this.data.root + this.data.fragment;
-}; |
go- -If the passed in 'fragment' is different than the currently stored one, -push a new state entry / hash event and set the data where specified - -
| sudo.Navigator.prototype.go = function go(fragment) {
- if(!this.started) return false;
- if(!this.urlChanged(fragment)) return; |
TODO ever use replaceState? | if(this.isPushState) {
- window.history.pushState({}, document.title, this.getUrl());
- } else if(this.isHashChange) {
- window.location.hash = '#' + this.data.fragment;
- }
- return this.setData();
-}; |
handleChange- -Bound to either the
| sudo.Navigator.prototype.handleChange = function handleChange(e) {
- if(this.urlChanged()) {
- return this.setData();
- }
-}; |
parseQuery- -Parse and return a hash of the key value pairs contained in
-the current
| sudo.Navigator.prototype.parseQuery = function parseQuery() {
- var obj = {}, seg = this.data.query,
- i, s;
- if(seg) {
- seg = seg.split('&');
- for(i = 0; i < seg.length; i++) {
- if(!seg[i]) continue;
- s = seg[i].split('=');
- obj[s[0]] = s[1];
- }
- return obj;
- }
-}; |
setData- -Using the current
| sudo.Navigator.prototype.setData = function setData() {
- var frag = this.data.fragment, |
data is set in a specified model or in self | observable = this.data.observable || this;
- if(this.data.query) { |
we want to set the key minus any search/hash | frag = frag.indexOf('?') !== -1 ? frag.split('?')[0] : frag.split('#')[0];
- }
- observable.set(frag, this.parseQuery());
- return this;
-}; |
start- -Gather the necessary information about the current environment and -bind to either (push|pop)state or hashchange. -Also, if given an imcorrect URL for the current environment (hashchange -vs pushState) normalize it and set accordingly (or don't). - -
| sudo.Navigator.prototype.start = function start() {
- var hasPushState, atRoot, loc, tmp;
- if(this.started) return;
- hasPushState = window.history && window.history.pushState;
- this.started = true; |
setup the initial configuration | this.isHashChange = this.data.useHashChange && 'onhashchange' in window ||
- (!hasPushState && 'onhashchange' in window);
- this.isPushState = !this.isHashChange && !!hasPushState; |
normalize the root to always contain a leading and trailing slash | this.data['root'] = ('/' + this.data['root'] + '/').replace(this.slashStripper, '/'); |
Get a snapshot of the current fragment | this.urlChanged(); |
monitor URL changes via popState or hashchange | if (this.isPushState) {
- $(window).on('popstate', this.handleChange.bind(this));
- } else if (this.isHashChange) {
- $(window).on('hashchange', this.handleChange.bind(this));
- } else return;
- atRoot = window.location.pathname.replace(/[^\/]$/, '$&/') === this.data['root']; |
somehow a URL got here not in my 'format', unless explicitly told not too, correct this | if(!this.data.stay) {
- if(this.isHashChange && !atRoot) {
- window.location.replace(this.data['root'] + window.location.search + '#' +
- this.data.fragment); |
return early as browser will redirect | return true; |
the converse of the above | } else if(this.isPushState && atRoot && window.location.hash) {
- tmp = this.getHash().replace(this.leadingStripper, '');
- window.history.replaceState({}, document.title, this.data['root'] +
- tmp + window.location.search);
- }
- } |
TODO provide option to | return this;
-}; |
urlChanged- -Is a passed in fragment different from the one currently set at
| sudo.Navigator.prototype.urlChanged = function urlChanged(fragment) {
- var current = this.getFragment(fragment); |
nothing has changed | if (current === this.data.fragment) return false;
- this.data.fragment = current;
- this.data.query = this.getSearch(current) || this.getHash(current);
- return true;
-}; |
Observable Extension Object- -Implementaion of the ES6 Harmony Observer pattern.
-Extend a | sudo.extensions.observable = { |
deliver- -Called from deliverChangeRecords when ready to send -changeRecords to observers. - -
| _deliver_: function _deliver_(obj) {
- var i, cb = this.callbacks;
- for(i = 0; i < cb.length; i++) {
- cb[i](obj);
- }
- }, |
deliverChangeRecords- -Iterate through the changeRecords array(emptying it as you go), delivering them to the -observers. You can override this method to change the standard delivery behavior. - -
| deliverChangeRecords: function deliverChangeRecords() {
- var rec, cr = this.changeRecords; |
FIFO | for(rec; cr.length && (rec = cr.shift());) {
- this._deliver_(rec);
- }
- return this;
- }, |
observe- -In a quasi-ES6 Object.observe pattern, calling observe on an
| observe: function observe(fn) { |
this will fail if mixed-in and no | var d = this.callbacks;
- if(d.indexOf(fn) === -1) d.push(fn);
- return fn;
- }, |
observes- -Allow an array of callbacks to be registered as changeRecord recipients - -
| observes: function observes(ary) {
- var i;
- for(i = 0; i < ary.length; i++) {
- this.observe(ary[i]);
- }
- return ary;
- }, |
set- -Overrides sudo.Base.set to check for observers - -
| set: function set(key, value, hold) {
- var obj = {name: key, object: this.data}; |
did this key exist already | if(key in this.data) {
- obj.type = 'updated'; |
then there is an oldValue | obj.oldValue = this.data[key];
- } else obj.type = 'new'; |
now actually set the value | this.data[key] = value;
- this.changeRecords.push(obj); |
call the observers or not | if(hold) return this;
- return this.deliverChangeRecords();
- }, |
setPath- -Overrides sudo.Base.setPath to check for observers.
-Change records originating from a
| setPath: function setPath(path, value, hold) {
- var curr = this.data, obj = {name: path, object: this.data},
- p = path.split('.'), key;
- for (key; p.length && (key = p.shift());) {
- if(!p.length) { |
reached the last refinement, pre-existing? | if (key in curr) {
- obj.type = 'updated';
- obj.oldValue = curr[key];
- } else obj.type = 'new';
- curr[key] = value;
- } else if (curr[key]) {
- curr = curr[key];
- } else {
- curr = curr[key] = {};
- }
- }
- this.changeRecords.push(obj); |
call all observers or not | if(hold) return this;
- return this.deliverChangeRecords();
- }, |
sets- -Overrides Base.sets to hold the call to deliver until -all operations are done - -
| sets: function sets(obj, hold) {
- var i, k = Object.keys(obj);
- for(i = 0; i < k.length; i++) {
- k[i].indexOf('.') === -1 ? this.set(k[i], obj[k[i]], true) :
- this.setPath(k[i], obj[k[i]], true);
- }
- if(hold) return this;
- return this.deliverChangeRecords();
- }, |
unobserve- -Remove a particular callback from this observable - -
| unobserve: function unobserve(fn) {
- var cb = this.callbacks, i = cb.indexOf(fn);
- if(i !== -1) cb.splice(i, 1);
- return this;
- }, |
unobserves- -Allow an array of callbacks to be unregistered as changeRecord recipients - -
| unobserves: function unobserves(ary) {
- var i;
- for(i = 0; i < ary.length; i++) {
- this.unobserve(ary[i]);
- }
- return this;
- }, |
unset- -Overrides sudo.Base.unset to check for observers - -
| unset: function unset(key, hold) {
- var obj = {name: key, object: this.data, type: 'deleted'},
- val = !!this.data[key];
- delete this.data[key]; |
call the observers if there was a val to delete | return this._unset_(obj, val, hold);
- }, |
unset- -Helper for the unset functions - -
| _unset_: function _unset_(o, v, h) {
- if(v) {
- this.changeRecords.push(o);
- if(h) return this;
- return this.deliverChangeRecords();
- }
- return this;
- }, |
setPath- -Overrides sudo.Base.unsetPath to check for observers - -
| unsetPath: function unsetPath(path, hold) {
- var obj = {name: path, object: this.data, type: 'deleted'},
- curr = this.data, p = path.split('.'),
- key, val;
- for (key; p.length && (key = p.shift());) {
- if(!p.length) { |
reached the last refinement | val = !!curr[key];
- delete curr[key];
- } else { |
this can obviously fail, but can be prevented by checking
-with | curr = curr[key];
- }
- }
- return this._unset_(obj, val, hold);
- }, |
unsets- -Override of Base.unsets to hold the call to deliver until done - -
| unsets: function unsets(ary, hold) {
- var i;
- for(i = 0; i < ary.length; i++) {
- ary[i].indexOf('.') === -1 ? this.unset(k[i], true) :
- this.unsetPath(k[i], true);
- }
- if(hold) return this;
- return this.deliverChangeRecords();
- }
-}; |
Bindable Extension Object | |
Bindable methods allow various properties and attributes of -a sudo Class Object to be synchronized with the data contained -in a changeRecord recieved via observe(). - -
| sudo.extensions.bindable = { |
List of attributes - $.attr() to be used. - -
| _attr_: {
- accesskey: true,
- align: true,
- alt: true,
- contenteditable: true,
- draggable: true,
- href: true,
- label: true,
- name: true,
- rel: true,
- src: true,
- tabindex: true,
- title: true
- }, |
Some bindings defer to jQuery.css() to be bound. - -
| _css_: {
- display: true,
- visibility: true
- }, |
handleAttr- -bind the jQuery prop() method to this object, now exposed
-by this name, matching passed
| _handleAttr_: function _handleAttr_(meth) {
- this[meth] = function(obj) {
- if(obj.name === meth) this.$el.attr(meth, obj.object[obj.name]);
- return this;
- };
- return this;
- }, |
handleCss- -bind the jQuery css() method to this object, now exposed
-by this name, matching passed
| _handleCss_: function _handleCss_(meth) {
- this[meth] = function(obj) {
- if(obj.name === meth) this.$el.css(meth, obj.object[obj.name]);
- return this;
- };
- return this;
- }, |
handleData- -bind the jQuery data() method to this object, now exposed
-by this name, matching passed
| _handleData_: function _handleData_(meth) {
- this[meth] = function(obj) {
- if(obj.name === meth) {
- this.$el.data(obj.object[obj.name].key, obj.object[obj.name].value);
- return this;
- }
- };
- return this;
- }, |
handleProp- -bind the jQuery attr() method to this object, now exposed
-by this name, matching passed NOTE: If more than 1 data-* attribute is desired you must
-set those up manually as
| _handleProp_: function _handleProp_(meth) {
- this[meth] = function(obj) {
- if(obj.name === meth) this.$el.prop(meth, obj.object[obj.name]);
- return this;
- };
- return this;
- }, |
handleSpec- -bind the jQuery shorthand methods to this object matching
-passed
| _handleSpec_: function _handleSpec_(meth) {
- this[meth] = function(obj) {
- if(obj.name === meth) this.$el[meth](obj.object[obj.name]);
- return this;
- };
- return this;
- }, |
List of properties - $.prop() to be used. - -
| _prop_: {
- checked: true,
- defaultValue: true,
- disabled: true,
- location: true,
- multiple: true,
- readOnly: true,
- selected: true
- }, |
setBinding- -Given a single explicit binding, create it. Called from -setbindings as a convenience for normalizing the -single vs. multiple bindings scenario - -
| _setBinding_: function _setBinding_(b) {
- if(b in this._spec_) return this[this._spec_[b]](b);
- if(b in this._css_) return this._handleCss_(b);
- if(b in this._attr_) return this._handleAttr_(b);
- if(b in this._prop_) return this._handleProp_(b);
- }, |
setBindings- -Inspect the binding (in the single-bound use case), or the -bindings Array in this Object's data store and -create the bound functions expected. - -
| setBindings: function setBindings() {
- var d = this.model.data, b, i; |
handle the single binding use case | if((b = d.binding)) return this._setBinding_(b);
- if(!(b = d.bindings)) return this;
- for(i = 0; i < b.length; i++) {
- this._setBinding_(b[i]);
- }
- return this;
- }, |
| _spec_: {
- data: '_handleData_',
- html: '_handleSpec_',
- text: '_handleSpec_',
- val: '_handleSpec_'
- }
-}; |
Listener Extension Object | |
Handles event binding/unbinding via an events array in the form:
-events: [{
-name: | sudo.extensions.listener = { |
bindEvents- -Bind the events in the data store to this object's $el - -
| bindEvents: function bindEvents() {
- var e;
- if((e = this.model.data.event || this.model.data.events)) this._handleEvents_(e, 1);
- return this;
- }, |
Use the jQuery | _handleEvents_: function _handleEvents_(e, which) {
- var i;
- if(Array.isArray(e)) {
- for(i = 0; i < e.length; i++) {
- this._handleEvent_(e[i], which);
- }
- } else {
- this._handleEvent_(e, which);
- }
- }, |
helper for binding and unbinding an individual event
- | _handleEvent_: function _handleEvent_(e, which) {
- if(which) {
- this.$el.on(e.name, e.sel, e.data, typeof e.fn === 'string' ? this[e.fn].bind(this) : e.fn);
- } else { |
do not re-bind the fn going to off otherwise the unbind will fail | this.$el.off(e.name, e.sel);
- }
- }, |
rebindEvents- -Convenience method for 'returns' {object} 'this' | rebindEvents: function rebindEvents() {
- return this.unbindEvents().bindEvents();
- }, |
unbindEvents- -Unbind the events in the data store from this object's $el - -
| unbindEvents: function unbindEvents() {
- var e;
- if((e = this.model.data.event || this.model.data.events)) this._handleEvents_(e);
- return this;
- }
-}; |
sudo persistable extension- -A mixin providing restful CRUD operations for a sudo.Model instance. - -create : POST -read : GET -update : PUT or PATCH (configurable) -destroy : DELETE - -Before use be sure to set an Place any other default options in the | sudo.extensions.persistable = { |
create- -Save this model on the server. If a subset of this model's attributes -have not been stated (ajax:{data:{...}}) send all of the model's data. -Anticipate that the server response will send back the -state of the model on the server and set it here (via a success callback). - -
| create: function create(params) {
- return this._sendData_('POST', params);
- }, |
destroy- -Delete this model on the server - -
| destroy: function destroy(params) {
- return this._sendData_('DELETE', params);
- }, |
normalizeParams- -Abstracted logic for preparing the options object. This looks at
-the set Sets defaults: JSON dataType and a success callback that simply
| _normalizeParams_: function _normalizeParams_(meth, opts, params) {
- opts || (opts = $.extend({}, this.data.ajax));
- opts.url || (opts.url = this.url(opts.baseUrl));
- opts.type || (opts.type = meth);
- opts.dataType || (opts.dataType = 'json'); |
the default success callback is to set the data returned from the server
-or just the status as | opts.success || (opts.success = function(data, status, jqXhr) {
- data ? this.sets(data) : this.set('ajaxStatus', status);
- }.bind(this)); |
allow the passed in params to override any set in this model's | return params ? $.extend(opts, params) : opts;
- }, |
read- -Fetch this models state from the server and set it here. The
- Maps to the http GET method. - -
| read: function read(params) {
- return $.ajax(this._normalizeParams_('GET', null, params));
- }, |
save- -Convenience method removing the need to know if a model is new (not yet persisted) -or has been loaded/refreshed from the server. - -
| save: function save(params) {
- return ('id' in this.data) ? this.update(params) : this.create(params);
- }, |
sendData- -The Create, Update and Patch methods all send data to the server, -varying only in their HTTP method. Abstracted logic is here. - -
| _sendData_: function _sendData_(meth, params) {
- opts = $.extend({}, this.data.ajax);
- opts.contentType || (opts.contentType = 'application/json');
- opts.data || (opts.data = this.data); |
non GET requests do not 'processData' | if(!('processData' in opts)) opts.processData = false;
- return $.ajax(this._normalizeParams_(meth, opts, params));
- }, |
update- -If this model has been persisted to/from the server (it has an NOTE: update does not check is this is a new model or not, do that yourself
-or use the
| update: function update(params) {
- return this._sendData_((this.data.ajax.patch || params && params.patch) ?
- 'PATCH' : 'PUT', params);
- }, |
url- -Takes the base url and appends this models id if present -(narmalizing the trailong slash if needed). -Override if you need to change the format of the calculated url. - -
| url: function url(base) { |
could possibly be 0... | if('id' in this.data) {
- return base + (base.charAt(base.length - 1) === '/' ?
- '' : '/') + encodeURIComponent(this.data.id);
- } else return base;
- }
-}; |
Change Delegate | |
Delegates, if present, can override or extend the behavior
-of objects. The change delegate is specifically designed to
-filter change records from an Observable instance and only forward
-the ones matching a given
| sudo.delegates.Change = function(data) {
- this.construct(data);
-}; |
Delegates inherit from Model | sudo.delegates.Change.prototype = Object.create(sudo.Model.prototype); |
addFilter- -Place an entry into this object's hash of filters - -
| sudo.delegates.Change.prototype.addFilter = function addFilter(key, val) {
- this.data.filters[key] = val;
- return this;
-}; |
filter- -Change records are delivered here and filtered, calling any matching -methods specified in `this.get('filters'). - -
| sudo.delegates.Change.prototype.filter = function filter(change) {
- var filters = this.data.filters, name = change.name,
- type = change.type, obj = {}; |
does my delegator care about this? | if(name in filters && filters.hasOwnProperty(name)) { |
assemble the object to return to the method | obj.type = type;
- obj.name = name;
- obj.oldValue = change.oldValue; |
delete operations will not have any value so no need to look | if(type !== 'deleted') {
- obj.value = name.indexOf('.') === -1 ? change.object[change.name] :
- sudo.getPath(name, change.object);
- }
- return this.delegator[filters[name]].call(this.delegator, obj);
- }
-}; |
removeFilter- -Remove an entry from this object's hash of filters - -
| sudo.delegates.Change.prototype.removeFilter = function removeFilter(key) {
- delete this.data.filters[key];
- return this;
-}; |
| sudo.delegates.Change.prototype.role = 'change'; |
Data Delegate | |
Delegates, if present, can extend the behavior -of objects, lessening the need for subclassing. -The data delegate is specifically designed to -filter through an object, looking for specified keys or paths -and returning values for those if found - -
| sudo.delegates.Data = function(data) {
- this.construct(data);
-}; |
inherits from Model | sudo.delegates.Data.prototype = Object.create(sudo.Model.prototype); |
addFilter- -Place an entry into this object's hash of filters - -
| sudo.delegates.Data.prototype.addFilter = function addFilter(key, val) {
- this.data.filters[key] = val;
- return this;
-}; |
filter- -iterates over a given object literal and returns a value (if present) -located at a given key or path - -
| sudo.delegates.Data.prototype.filter = function(obj) {
- var filters = this.data.filters,
- ary = Object.keys(filters), key, i, o, k;
- for(i = 0; i < ary.length; i++) {
- key = ary[i]; |
keys and paths need different handling | if(key.indexOf('.') === -1) {
- if(key in obj) this.delegator[filters[key]].call(
- this.delegator, obj[key]);
- } else { |
the chars after the last refinement are the key we need to check for | k = key.slice(key.lastIndexOf('.') + 1); |
and the ones prior are the object | o = sudo.getPath(key.slice(0, key.lastIndexOf('.')), obj);
- if(o && k in o) this.delegator[filters[key]].call(
- this.delegator, o[k]);
- }
- }
-}; |
removeFilter- -Remove an entry from this object's hash of filters - -
| sudo.delegates.Data.prototype.removeFilter = function removeFilter(key) {
- delete this.data.filters[key];
- return this;
-}; |
| sudo.delegates.Data.prototype.role = 'data';
-
-sudo.version = "0.9.5";
-window.sudo = sudo;
-if(typeof window._ === "undefined") window._ = sudo;
-}).call(this, this);
-
- |