I wrote this block of code as part of a JS utility library I'm working on. I will be thankful if someone could scan through it for eventual bugs or improvements that might be applied to it.
The idea is to create a function that will configure an object to manage its own data collection by attaching .data()
function to it and plugging in a few utility functions to further control objects data:
//
// #idb
//
// enables an object to hold arbitrary data
// plugges in 'data' ( fn ) property to passed object
// handles adding, removing, modifing, replacing, iterating, checking for data
// adds 'idb' identifier to global scope
//
// .data()
// # reads/manipulates objects data:
// - if no parameters are passed returns private data store
// - if one parameter is passed ( and its primitive ) returns it's coresponding value,
// if plain object is provided extends data with it's props
// - if two parameters are passed sets data key to coresponding value,
// if second parameter is not a primitive value,
// tries to extends private data with provided data value( extends plain objs, adds values to arrays )
//
// static fns:
//
// .data.alter()
// # replaces data with provided ( plain obj ) parameter
// # @param1, object, required
// # returns host object
//
// .data.drop()
// # removes data, if parameter is provided deletes it's coresponding data, otherwise empties data store
// # @param1, scalar, optional
// # retutns host object
//
// .data.has()
// # checks if data value coresponding to provided key exists
// # @param1, scalar, required
// # returns boolean
//
// .data.each()
// # iterates data store, sequentialy passes data key and it's data value to provided fn, sets fn's context to host obj
// # @param1, function, required
// # returns host object
//
// .data.index()
// # returns existing data keys as array
// # returns array
//
// use:
//
// var o = idb({});
//
// o.data("prop1", 1);
// o.data("prop2", {p:12});
// o.data("prop3", {p1:1, p2:{}, p3:[]});
// o.data() -> Object, ( data object )
// o.data('prop1'); -> 1
// o.data('prop1', []);
// o.data('prop1'); -> []
// o.data.has('prop4'); -> false
// o.data.alter({p:1,q:2});
// o.data("p"); -> 1
// o.data("prop1"); -> undefined
// o.data.each(
// function ( k, v ) {
// console.log( k, v);
// }
// ); -> p 1
// q 2
// o.data.drop("q");
// o.data(); -> {p:1}
// o.data.drop();
// o.data(); -> {}
//
;
(function(_host_) {
var t = !0,
f = !t,
nl = null,
_dbs = [],
_db, un;
// #helpers
Array.prototype.each = function(fn) {
for (
var i = 0, len = this.length; i < len; i++) {
if (fn.call(this, i, this[i]) === f) break;
}
return this;
};
Array.prototype.has = function(v) {
return this.indexOf(v) !== -1;
};
function isobj(o) {
return o === Object(o);
}
function isfn(o) {
return typeof o === 'function' && (Object.prototype.toString.call(o) === '[object Function]');
}
function isplainobj(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}
function isvalid(arg) {
return arg !== un && arg !== nl && (arg === arg);
}
function isarray(o) {
return isfn(Array.isArray) ? Array.isArray(o) : Object.prototype.toString.call(o) === '[object Array]';
}
function slc(a, i1, i2) {
return Array.prototype.slice.call(a, i1, i2);
}
function overload(o, method_v1, method_v2) {
var origm = o[method_v1];
return o[method_v1] = function() {
var args = slc(arguments);
if (method_v2.length === arguments.length) {
return method_v2.apply(this, args);
} else if (isfn(origm)) {
return origm.apply(this, args);
} else {
return un;
}
};
}
function rig_props(obj, props) {
if (
isobj(obj) && isplainobj(props)) {
each(
props, function(p, v) {
obj[p] = v;
})
}
return obj;
}
function owns(obj, prop) {
return obj.hasOwnProperty(prop);
}
function each(obj, fn) {
for (var p in obj) {
if (owns(obj, p)) {
if (fn.call(obj, p, obj[p]) === f) break;
}
}
return obj;
}
function vacate_obj(obj) {
if (isobj(obj)) {
for (var p in obj) {
try {
if (!owns(Object.prototype, p)) {
delete obj[p];
}
} catch (xc) {}
}
}
return obj;
}
function extend(hostObj, obj) {
each(
obj, function(prop) {
if (owns(hostObj, prop) && isobj(hostObj[prop])) {
if (isobj(this[prop])) {
if (!isarray(this[prop])) {
if (
isplainobj(this[prop]) && isplainobj(hostObj[prop])) {
extend(hostObj[prop], this[prop]);
} else {
hostObj[prop] = this[prop];
}
} else if (isarray(this[prop])) {
if (isarray(hostObj[prop])) {
this[prop].each(
function(k, v) {
hostObj[prop].has(v) || hostObj[prop].push(v);
});
} else {
hostObj[prop] = this[prop];
}
} else {
hostObj[prop] = this[prop];
}
} else {
hostObj[prop] = this[prop];
}
} else {
hostObj[prop] = this[prop];
}
});
return hostObj;
}
//
//
// declare main fn
// accepts any object
_db = function(obj) {
if (
isobj(obj) && !_dbs.has(obj)) {
(function(_d) {
var host = this,
dtfn = "data";
// if no arguments are provided return private data store
// else pass responsibility to overloaded fns
this[dtfn] = function() {
return (arguments.length === 0) ? _d : host[dtfn](arguments[0], arguments[1]);
};
// if one argument is provided deal with reading data value
// or if plain object is provided extend data store with provided obj's props
overload(
this, dtfn, function(dtk) {
return isplainobj(dtk) && (extend(_d, dtk), host) || _d[dtk];
});
// if two arguments are given handle setting a data property
// if value to set and current data value are objs extend current data value
// else set data value to given parameter
overload(
this, dtfn, function(dtk, dtv) {
isplainobj(dtv) && (
isplainobj(_d[dtk]) && (extend(_d[dtk], dtv), t) || (_d[dtk] = dtv), t) || (_d[dtk] = dtv);
return host;
});
// attach utility fns to data method
rig_props(
this[dtfn], {
alter: function(dt) {
return (
isplainobj(dt) && (_d = dt), host);
},
drop: function(dtk) {
isvalid(dtk) && (delete _d[dtk], t) || vacate_obj(_d);
return host;
},
has: function(dtk) {
return isvalid(dtk) ? owns(_d, dtk['valueOf']()) : un;
},
each: function(fn) {
if (isfn(fn)) {
each(
_d, function(p, v) {
return fn.call(host, p, v);
});
}
return host;
},
index: function() {
var k = [];
this.each(
function(p, v) {
k.push(p);
});
return k;
}
});
// remember configured object to avoid configuration rerun
_dbs.push(this);
}).call(obj, {});
} // end if
// return configured object
return obj;
}
// attach 'idb' fn identifier to global scope
_host_.idb = _db;
})(self);
//
//
Looking forward to comments, suggestions for extending the API, etc.