mf Object Methods Part 1 {mf.object}

This is a series of posts that explains some of the fundamental rules for developing complex JavaScript applications and the mf framework.

It has been a few weeks since I wrote a post on mf but this has most been due to the time it took to complete all of the object methods available.  Object methods along with array methods are some of the most important methods that any framework can have and as such taking some extra time is required. At present there are 28 methods written and fully tested. Given this number it would obviously be far to much for one single blog post. As a result I will be writing several blog posts each covering several methods at a time. In this post I will discuss the following methods:

  • mf.object.each
  • mf.object.deepEach
  • mf.object.clense
  • mf.object.typeMap
  • mf.object.containsKeys
  • mf.object.containsVals

mf.object.each (object, function, scope)

The each method is used to enumerate through each attribute of an object at a shallow level under the optionally passed scope. On each attribute of the object the provided function is called under the provided scope and passed the name of the attribute as a string, the value of attribute along with the original object passed. For example:

var myObject = {a:1,b:2,c:['a','b','c'],d:{aa:1,bb:2}};
var testFunction = function(key,val,o){
     console.write(key,typeof val, val, o)
};
mf.object.each(myObject,testFunction)

Result:
a, number, 1, {a:1,b:2,c:['a','b','c'],d:{aa:1,bb:2}}
b, number, 2, {a:1,b:2,c:['a','b','c'],d:{aa:1,bb:2}}
c, array, ['a','b','c'], {a:1,b:2,c:['a','b','c'],d:{aa:1,bb:2}}
d, object, {aa:1,bb:2}, {a:1,b:2,c:['a','b','c'],d:{aa:1,bb:2}}

As you can see it is fairly simple in its behaviour. If you wanted to break out of the enumeration of the passed object all you need to is return false from your test function. This function is non destructive in nature. It should be remember however that the method does NOT test for has hasOwnProperty. This is a deliberate decision on my part to allow for the enumeration of prototyped methods and also remove any condition of enumeration for improved performance.

One of the problems however with the object.each method can be seen in the last result of the example which returns d which is an object itself. Basically the each method does not do ‘deep’ enumeration of the passed object. Certainly you could do this with an test and iterative call within your passed function, such as:

var testFunction = function(key,val,o){
  if(mf.isObject(val)){
     mf.object.each(o[key],testFunction)
  }else{
     // do something
  }
}

However this would get very tiresome having to write this in your code all of the time and two points here. In many circumstances you need to do deep object enumeration and secondly, the point of a framework is to streamline your code. The each method returns the passed object in order to allow for chaining.

mf.object.deepEach(object,function,scope)

The deepEach method works in the exact same fashion as the each method where returning false will exit the enumeration. However it enumerates the object deeply. Given our example above running the same testFunction on myObject with the deepEach method would result in the following output:

Result:
a, number, 1, {a:1,b:2,c:['a','b','c'],d:{aa:1,bb:2}}
b, number, 2, {a:1,b:2,c:['a','b','c'],d:{aa:1,bb:2}}
c, array, ['a','b','c'], {a:1,b:2,c:['a','b','c'],d:{aa:1,bb:2}}
d, object, {aa:1,bb:2}, {a:1,b:2,c:['a','b','c'],d:{aa:1,bb:2}}
aa, number, 1, {aa:1,bb:2}
bb, number, 2, {aa:1,bb:2}

As you can see the call would result in the attributes on the d object being passed as well to the function as well as the original d object itself. Note however, when the provided function is passed the attributes of the d object that the passed object changes to d rather than the original root object.  This allows you do things like o[key] in your function and be guaranteed that the key exists within the passed objects scope.

When working with the deepEach method the passed function is also passed two extra parameters, namespace as string and depth as a number. To understand how these can be used lets look at the following example:

var testFunction = function(key,val,o,ns,depth){
	console.log(key,typeof val, val, o, ns, depth)
}
var myObject = {
a:1,
b:{
	aa:{
		aaa:1,
		bbb:2,
		ccc:{
			aaaa:'a',
			bbbb:'b'
		}
	}
}}
mf.object.deepEach(myObject,testFunction)

Result:
a,number, 1, [object Object], , 0
b,object, [object Object], [object Object], , 0
aa,object, [object Object], [object Object], b, 1
aaa,number, 1, [object Object], b.aa, 2
bbb,number, 2, [object Object], b.aa, 2
ccc,object, [object Object], [object Object], b.aa, 2
aaaa,string, a, [object Object], b.aa.ccc, 3
bbbb,string, b, [object Object], b.aa.ccc, 3

Note the result on our deepEach call  for namespace and depth. When we method is at the root of the passed object no namespace is passed and the depth is zero. When we hit the attribute aa within the object the namespace is passed as the string b and the depth is 1. This allows you to make test conditions within your function when looking for a specific condition/attribute within a specific namespace or depth. Consider for a moment this object:

var myObject = {a:{aa:1,bb:2},b:{aa:1,bb:2}}
var testFunction = function(key,val,o,ns,depth){
    if(ns==='a' and key==='aa'}{
        o[key]=null
    }
}
mf.object.deepEach(myObject,testFunction)

In this example if the deepEach method did not provide the namespace as string there would be no way to differentiate between the aa attribute in a or b which would not be very useful. Like the each method the deepEach method is non destructive and also returns the passed object to allow for chaining.

mf.object.clense(object, value, deep)

This methods resets an objects attribute values to the optionally provided value. You can also specify if the enumeration should be deep on the object. If the value is not provided is not provided it is defaulted to null. If deep is not provided it will be defaulted to false. Given the following:

var myObject = {a:1,b:2,c:{aa:1,bb:1}
mf.object.clense(myObject,null,true)
Result:
{a:null, b:null ,c:{aa:null, bb:null}

If deep had been set to false only the root attributes of and b would have been set to null and aa and bb would still be 1.

mf.object.typeMap (object, deep)

The typeMap method is a convenience function that returns an object describing the types of of objects contained with the object. It can be used to easily find specific types of data based on namespaces within the object. The deep parameter is optional and defaults to true. For Example:

var myObject = {
    a:{
        aa:1,
        bb:2,
        cc:[1,2,4],
        dd:null
    },
    b:[1,2,3,4],
    c: undefined,
    d: function(){return true},
    f: 1,
    g: 'abc'    
};
mf.object.typeMap(myObject,true)

Result:
{
  array : ['a.cc','b'],
  function : ['d'],
  null : ['a.dd'],
  number : ['a.aa','a.bb','f'],
  object : ['a'],
  string : ['g'],
  undefined : ['c']
}

The result of the call always returns the exact same properties but if for example no arrays where present in the object the the array attribute would have an empty array.  This typeMap method can be useful when you need to set specific types of information in the object to a value. For example consider if we had an unknown object but needed to ensure that it did NOT contain any attributes assigned to functions and all number attributes where set to 0. This could easily be done within mf with the following code.

var myMap = mf.object.typeMap(myObject,true);
mf.each(myMap['function'],function(val){
    mf.ns.remove(val,myObject)
});
mf.each(myMap['number'],function(val){
    mf.ns.set(val,myObject,0);
})

You will notice in the above code I have touched on a couple of other methods mf.each, mf.ns.remove and mf.ns.set. I will be discussing these methods in a future post.

mf.object.containsKeys(object, keys, deep)

This method accepts an object and an array of namespace keys, a string of comma delimited keys or a single key and returns true if the key(s) are found within the object. The deep parameter is optional and defaults to false. Any of the following calls would be valid

mf.object.containsKeys(myObject,['a','a.bb','cc']);
mf.object.containsKeys(myObject,'a,a.bb,cc');
mf.object.containsKeys(myObject,'a');

If more than one namespace/key is passed they ALL must be found in order for the method to return true.

mf.object.containsVals(object, vals, deep)

This method is identical to contains keys but the comparison is done against the values rather than the keys and if found returns true. As with the containsKeys method if an array of values are passed all must be found in order for the method to return true.


In my next post I will discuss further methods in the object class of mf. These methods will focus more on arrays objects and provide methods for sorting, normalizing, searching, comparison along with many more very cool methods for manipulated arrays of object.

When I have completed the blog posts on the mf.object methods I will be adding them to the mf API documentation at the top of the blog along with other base methods. Until then enjoy.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s