sudoclass
Version:
Base, Observable and Delegate classes from the sudo.js library
824 lines (814 loc) • 29.1 kB
JavaScript
require('./polyfills');
(function(module) {
var sudo = {
// Namespace for `Delegate` Class Objects used to delegate functionality
// from a `delegator`
//
// `namespace`
delegates: {},
// The sudo.extensions namespace holds the `extensions` which are stand alone `modules` that
// can be `implemented` (mixed-in) to sudo Class Objects
//
// `namespace`
// Method to copy the declared, non-inherited, properties from one object to another.
extend: function extend(/*var_args*/) {
var args = Array.prototype.slice.call(arguments), targ = args.shift(), obj;
function fn(k) {targ[k] = obj[k];}
// iterate over each passed in obj remaining
for(; args.length && (obj = args.shift());) {Object.keys(obj).forEach(fn);}
return targ;
},
extensions: {},
// ###getPath
// Extract a value located at `path` relative to the passed in object
//
// `param` {String} `path`. The key in the form of a dot-delimited path.
// `param` {object} `obj`. An object literal to operate on.
//
// `returns` {*|undefined}. The value at keypath or undefined if not found.
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 `superclass` constructor
// will generally be looking for the library Object's constructor.
//
// `param` {function} `parent`
// `param` {function} `child`
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.
//
// `returns` {string}
makeMeASandwich: function makeMeASandwich() {return 'Okay.';},
// ###setPath
// Traverse the keypath and get each object
// (or make blank ones) eventually setting the value
// at the end of the path
//
// `param` {string} `path`. The path to traverse when setting a value.
// `param` {*} `value`. What to set.
// `param` {Object} `obj`. The object literal to operate on.
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 `tag` for identification.
// (Views for example). This ensures they are indeed unique.
uid: 0,
// ####unique
// An integer used as 'tags' by some sudo Objects as well
// as a unique string for views when needed
//
// `param` {string} prefix. Optional string identifier
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 <path>
//
// `param` {String} `path`
// `param` {Object} `obj` The object to operate on.
unsetPath: function unsetPath(path, obj) {
var p = path.split('.'), key;
for (key; p.length && (key = p.shift());) {
if(!p.length) delete obj[key];
// this can fail if a faulty path is passed.
// using getPath beforehand can prevent that
else obj = obj[key];
}
}
};
// ##Base Class Object
//
// All sudo.js objects inherit base, giving the ability
// to utilize delegation, the `base` function and the
// `construct` convenience method.
//
// `constructor`
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 `_delegates_` list.
//
// `param` {Object} an instance of a sudo.delegates Class Object
// `returns` {Object} `this`
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 `base`.
//
// `params` {*} any other number of arguments to be passed to the looked up method
// along with the initial method name
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:
// `Object.getPrototypeOf(this).consturctor.apply(this, arguments)`
// in every constructor
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 `_role_` matches
// the passed `name` and:
// 1. if `meth` is falsy return the delegate.
// 2 if `meth` is truthy bind its method (to the delegate) and return the method
//
// `param` {String} `role` The role property to match in this object's delegates list
// `param` {String} `meth` Optional method to bind to the action this delegate is being used for
// `returns`
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 `delegate` method in its 'single argument' form, included for
// API consistency
//
// `param` {String} `role`
// 'returns' {Object|undefined}
sudo.Base.prototype.getDelegate = function getDelegate(role) {
return this.delegate(role);
};
// ###removeDelegate
// From this objects `delegates` list remove the object (there should only ever be 1)
// whose role matches the passed in argument
//
// `param` {String} `role`
// `returns` {Object} `this`
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;
};
// `private`
sudo.Base.prototype.role = 'base';
// ##Model Class Object
//
// Model Objects expose methods for setting and getting data, and
// can be observed if implementing the `Observable Extension`
//
// `param` {object} data. An initial state for this model.
//
// `constructor`
sudo.Model = function(data) {
sudo.Base.call(this);
this.data = data || {};
// only models are `observable`
this.callbacks = [];
this.changeRecords = [];
};
// Model inherits from sudo.Base
// `private`
sudo.inherit(sudo.Base, sudo.Model);
// ###get
// Returns the value associated with a key.
//
// `param` {String} `key`. The name of the key
// `returns` {*}. The value associated with the key or false if not found.
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.
//
// `returns` {*|undefined}. The value at keypath or undefined if not found.
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.
//
// `param` {array} `ary`. An array of keys.
// `returns` {object}
sudo.Model.prototype.gets = function gets(ary) {
var obj = {};
ary.forEach(function(str) {
obj[str] = str.indexOf('.') === -1 ? this.get(str) : this.getPath(str);
}.bind(this));
return obj;
};
// `private`
sudo.Model.prototype.role = 'model';
// ###set
// Set a key:value pair.
//
// `param` {String} `key`. The name of the key.
// `param` {*} `value`. The value associated with the key.
// `returns` {Object} `this`
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.
//
// `param` {String} `path`
// `param` {*} `value`
// `returns` {Object} this.
sudo.Model.prototype.setPath = function setPath(path, value) {
sudo.setPath(path, value, this.data);
return this;
};
// ###sets
// Invokes `set()` or `setPath()` for each key value pair in `obj`.
// Any listeners for those keys or paths will be called.
//
// `param` {Object} `obj`. The keys and values to set.
// `returns` {Object} `this`
sudo.Model.prototype.sets = function sets(obj) {
Object.keys(obj).forEach(function(k) {
k.indexOf('.') === -1 ? this.set(k, obj[k]) : this.setPath(k, obj[k]);
}.bind(this));
return this;
};
// ###unset
// Remove a key:value pair from this object's data store
//
// `param` {String} key
// `returns` {Object} `this`
sudo.Model.prototype.unset = function unset(key) {
delete this.data[key];
return this;
};
// ###unsetPath
// Uses `sudo.unsetPath` operating on this models data hash
//
// `param` {String} path
// `returns` {Object} `this`
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
//
// `param` {array} `ary`. An array of keys or paths.
// `returns` {Objaect} `this`
sudo.Model.prototype.unsets = function unsets(ary) {
ary.forEach(function(k) {
k.indexOf('.') === -1 ? this.unset(k) : this.unsetPath(k);
}.bind(this));
return this;
};
// ##Container Class Object
//
// A container is any object that can both contain other objects and
// itself be contained
//
// `constructor`
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 `addedToParent` if found on the child, call it, sending `this` as an argument.
//
// `param` {Object} `child`. View (or View subclass) instance.
// `param` {String} `name`. An optional name for the child that will go in the childNames hash.
// `returns` {Object} `this`
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;
};
// ###addChildren
// Allows for multiple children to be added to this Container by passing
// either an Array or an Object literal.
//
// see `addChild`
//
// `param` {Array|Object} `arg`. An array of children to add or an
// Object literal in the form {name: child}
// `returns` {Object} `this`
sudo.Container.prototype.addChildren = function addChildren(arg) {
// normalize the arg
var keys = Array.isArray(arg) ? undefined : Object.keys(arg),
ary = keys || arg;
ary.forEach(function(c) {
keys ? this.addChild(arg[c], c) : this.addChild(c);
}.bind(this));
return this;
};
// ###bubble
// By default, `bubble` returns the current view's parent (if it has one)
//
// `returns` {Object|undefined}
sudo.Container.prototype.bubble = function bubble() {return this.parent;};
// ###eachChild
// Call a named method and pass any args to each child in a container's
// collection of children
//
// `param` {*} Any number of arguments the first of which must be
// The named method to look for and call. Other args are passed through
// `returns` {object} `this`
sudo.Container.prototype.eachChild = function() {
var args = Array.prototype.slice.call(arguments), meth = args.shift();
this.children.forEach(function(c) {if(meth in c) c[meth].apply(c, args);});
return this;
};
// ###getChild
// If a child was added with a name, via `addChild`,
// that object can be fetched by name. This prevents us from having to reference a
// containers children by index. That is possible however, though not preferred.
//
// `param` {String|Number} `id`. The string `name` or numeric `index` of the child to fetch.
// `returns` {Object|undefined} The found child
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 `index` property of a subview that is being removed.
// Beginning at <i> decrement subview indices.
// `param` {Number} `i`
// `private`
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 `base('removeChild')` to do so.
//
// `param` {String|Number|Object} `arg`. Children will always have an `index` number, and optionally a `name`.
// If passed a string `name` is assumed, so be sure to pass an actual number if expecting to use index.
// An object will be assumed to be an actual sudo Class Object.
// `returns` {Object} `this`
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 `orphan`
delete c.parent;
delete c.index;
delete c.name;
this._indexChildren_(i);
if('removedFromParent' in c) c.removedFromParent(this);
return this;
};
// ###removeChildren
// Remove all children, name references and adjust indexes accordingly.
// This method calls removeFromParent as each child may have overridden logic there.
//
// `returns` {object} `this`
sudo.Container.prototype.removeChildren = function removeChildren() {
Object.keys(this.childNames).forEach(function(n) {
this.removeChild(this.getChild(n));
}.bind(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 `sudo.Container` maintained hierarchy
// the 'target' may be left out, causing the `bubble()` method to be called.
// What this does is allow children of a `sudo.Container` to simply pass
// events upward, delegating the responsibility of deciding what to do to the parent.
//
// `param` {*} Any number of arguments is supported, but the first is the only one searched for info.
// A sendMethod will be located by:
// 1. using the first argument if it is a string
// 2. looking for a `sendMethod` property if it is an object
// In the case a specified target exists at `this.model.get('sendTarget')` it will be used
// Any other args will be passed to the sendMethod after `this`
// `returns` {Object} `this`
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 `bubble-target`
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;
};
// ## Observable Extension Object
// Implementaion of the ES6 Harmony Observer pattern.
// Extend a `sudo.Model` class with this object if
// data-mutation-observation is required
sudo.extensions.observable = {
// ###_deliver_
// Called from deliverChangeRecords when ready to send
// changeRecords to observers.
//
// `private`
_deliver_: function _deliver_(obj) {
this.callbacks.forEach(function(cb) {
cb(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.
//
// `returns` {Object} `this`
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 `observable` and
// passing a callback will cause that callback to be called whenever any
// property on the observable's data store is set, changed or deleted
// via set, unset, setPath or unsetPath with an object containing:
// {
// type: <new, updated, deleted>,
// object: <the object being observed>,
// name: <the key that was modified>,
// oldValue: <if a previous value existed for this key>
// }
// For ease of 'unobserving' the same Function passed in is returned.
//
// `param` {Function} `fn` The callback to be called with changeRecord(s)
// `returns` {Function} the Function passed in as an argument
observe: function observe(fn) {
// this will fail if mixed-in and no `callbacks` created so don't do that.
// Per the spec, do not allow the same callback to be added
var d = this.callbacks;
if(!~d.indexOf(fn)) d.push(fn);
return fn;
},
// ###observes
// Allow an array of callbacks to be registered as changeRecord recipients
//
// `param` {Array} ary
// `returns` {Object} `this`
observes: function observes(ary) {
ary.forEach(function(fn) {this.observe(fn);}.bind(this));
return ary;
},
// ###set
// Overrides sudo.Base.set to check for observers
//
// `param` {String} `key`. The name of the key
// `param` {*} `value`
// `param` {Bool} `hold` Call _deliver_ (falsy) or store the change notification
// to be delivered upon a call to deliverChangeRecords (truthy)
//
// `returns` {Object|*} `this` or calls deliverChangeRecords
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` operation
// send back the passed in `path` as `name` as well as the
// top level object being observed (this observable's data).
// this allows for easy filtering either manually or via a
// `change delegate`
//
// `param` {String} `path`
// `param` {*} `value`
// `param` {Bool} `hold` Call _deliver_ (falsy) or store the change notification
// to be delivered upon a call to deliverChangeRecords (truthy)
// `returns` {Object|*} `this` or calls deliverChangeRecords
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
//
// `returns` {Object|*} `this` or calls deliverChangeRecords
sets: function sets(obj, hold) {
Object.keys(obj).forEach(function(k) {
!~k.indexOf('.') ? this.set(k, obj[k], true) :
this.setPath(k, obj[k], true);
}.bind(this));
if(hold) return this;
return this.deliverChangeRecords();
},
// ###unobserve
// Remove a particular callback from this observable
//
// `param` {Function} the function passed in to `observe`
// `returns` {Object} `this`
unobserve: function unobserve(fn) {
var cb = this.callbacks, i = cb.indexOf(fn);
if(~i) cb.splice(i, 1);
return this;
},
// ###unobserves
// Allow an array of callbacks to be unregistered as changeRecord recipients
//
// `param` {Array} ary
// `returns` {Object} `this`
unobserves: function unobserves(ary) {
ary.forEach(function(fn) {this.unobserve(fn);}.bind(this));
return this;
},
// ###unset
// Overrides sudo.Base.unset to check for observers
//
// `param` {String} `key`. The name of the key
// `param` {Bool} `hold`
//
// `returns` {Object|*} `this` or calls deliverChangeRecords
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
//
// `private`
_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
//
// `param` {String} `path`
// `param` {*} `value`
// `param` {bool} `hold`
//
// `returns` {Object|*} `this` or calls deliverChangeRecords
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 `getPath` first.
curr = curr[key];
}
}
return this._unset_(obj, val, hold);
},
// ###unsets
// Override of Base.unsets to hold the call to _deliver_ until done
//
// `param` ary
// `param` hold
// `returns` {Object|*} `this` or calls deliverChangeRecords
unsets: function unsets(ary, hold) {
ary.forEach(function(k) {
!~k.indexOf('.') ? this.unset(k, true) : this.unsetPath(k, true);
}.bind(this));
if(hold) return this;
return this.deliverChangeRecords();
}
};
//##Filtered Delegate
// The base type for both the Data and Change delegates.
//
// `param` {Object} data
sudo.delegates.Filtered = function(data) {
sudo.Base.call(this);
this.data = data;
};
// The filtered delegate
sudo.inherit(sudo.Base, sudo.delegates.Filtered);
// ###addFilter
// Place an entry into this object's hash of filters
//
// `param` {string} `k`
// `param` {string} `v`
// `returns` {object} this
sudo.delegates.Filtered.prototype.addFilter = function(k, v) {
this.data.filters[k] = v;
return this;
};
// ###removeFilter
// Remove an entry from this object's hash of filters
//
// `param` {string} `k`
// `returns` {object} this
sudo.delegates.Filtered.prototype.removeFilter = function(k) {
delete this.data.filters[k];
return this;
};
// `private`
sudo.delegates.Filtered.prototype.role = 'filtered';
//##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 `filters` criteria (key or path).
// The forwarded messages will be sent to the specified method
// on the delegates `delegator` (bound to the _delegator_ scope)
//
// `param` {Object} data
sudo.delegates.Change = function(data) {
this.construct(data);
};
// Delegates inherit from the Filtered Delegate
sudo.delegates.Change.prototype = Object.create(sudo.delegates.Filtered.prototype);
// ###filter
// Change records are delivered here and filtered, calling any matching
// methods specified in `this.get('filters').
//
// `returns` {Object} a call to the specified _delegator_ method, passing
// a hash containing:
// 1. the `type` of Change
// 2. the `name` of the changed property
// 3. the value located at the key/path
// 4. the `oldValue` of the key if present
sudo.delegates.Change.prototype.filter = function(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('.') ? change.object[change.name] :
sudo.getPath(name, change.object);
}
return this.delegator[filters[name]].call(this.delegator, obj);
}
};
// `private`
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
//
// `param` {Object} data
// `returns` {*} the value found at the specified key/path if found
sudo.delegates.Data = function(data) {
this.construct(data);
};
// inherits from the Filtered Delegate
sudo.delegates.Data.prototype = Object.create(sudo.delegates.Filtered.prototype);
// ###filter
// iterates over a given object literal and returns a value (if present)
// located at a given key or path
//
// `param` {Object} `obj`
sudo.delegates.Data.prototype.filter = function(obj) {
var filters = this.data.filters, sl, o;
Object.keys(filters).forEach(function(k) {
// keys and paths need different handling
if(!~k.indexOf('.')) {
if(k in obj) this.delegator[filters[k]].call(this.delegator, obj[k]);
} else {
// the chars after the last refinement are the key we need to check for
sl = k.slice(k.lastIndexOf('.') + 1);
// and the ones prior are the object
o = sudo.getPath(k.slice(0, k.lastIndexOf('.')), obj);
if(o && sl in o) this.delegator[filters[k]].call(this.delegator, o[sl]);
}
}.bind(this));
};
// `private`
sudo.delegates.Data.prototype.role = 'data';
sudo.version = "0.9.9";
// export the finished object
module.exports = sudo;
}(module));