Mangler.registerType() - DarthJDG/Mangler.js GitHub Wiki

Register handler functions for typed object instances.

Mangler.registerType(type, options)
Parameter Type Default Description
type Instance
Function
An object instance or its constructor to register.
options Object Handler functions to register.

Returns

true if successful. This function always succeeds, invalid handlers are ignored.

Options

Property Type Default Description
$constructor Function The constructor function for the object. Optional, unless clone is set to 'constructor'.
clone Function
String
true
Optional. A function that clones the passed object of the specified type and returns the clone. Parameters: function(object). Can have the value of true if object implements a standard .clone() method. Can have the value of 'contructor' if the object has a copy-constructor.
each Function
String
true
Optional. A function that iterates through the items of the passed object of the specified type and calls a callback function. Parameters: function(object, callback). Callback parameters: function(key, value). Implementation must stop the iteration if the callback function returns false. Can have the value of true if object implements a standard .each() method. Can have the value of 'array' if object is array-like.
get Function
String
true
Optional. A function that gets and returns an item from the passed object with a specified index or key. Parameters: function(object, key). Can have the value of true if object implements a standard .get() method. Can have the value of 'array' if the object is array-like.

By default Mangler.js can only recognise primitive JavaScript object types. While it is enough to handle object literals and JSON data, it's quite limiting when it comes to typed objects. By registering handler functions for other object types, you can teach Mangler.js what to do when it encounters certain object instances.

If you're only using JSON data, simple arrays, object literals, mangler objects and Date objects, you don't need to do anything as Mangler.js has built-in support for them.

If you're using other native object types or typed arrays, see the Mangler-natives module for support.

To enable Manger.js to process your own objects, read on.

This method will replace all handler bindings for the specified type. To add/remove features of an existing registration, use Mangler.mergeType().

Clone

When cloning an object, Mangler.js processes it recursively and creates a deep copy of all sub-objects found. When it encounters an object it doesn't recognise, it will pass the original object reference to the cloned object:

// Define a simple object type
function MyObject(param) {
	this.data = param;
}

// Create an object
a = {
	id: 1,
	my: new MyObject('Foo')
}

// Clone the object and change properties of the clone
b = Mangler.clone(a);
b.id = 2;
b.my.data = 'Bar';

// Output id and data of both objects
console.log(a.id, a.my.data);
console.log(b.id, b.my.data);

/*
	Output:

	1 "Bar"
	2 "Bar"
*/

In the above example, object a was mostly cloned into b, but the instance of MyObject was not recognised, so the object reference was passed directly to the clone. Therefore both a.my and b.my points to the same object.

To teach Mangler.js how to clone MyObject, register a handler for it:

Mangler.registerType(MyObject, {
	clone: function(obj) {
		var data = Mangler.clone(obj.data);
		return new MyObject(data);
	}
});

If you register the MyObject type before cloning a in the above example, b will be a perfect clone, and changes to b.my.data won't change a.my.data. You can use Mangler.clone() inside the handler as well to clone anything contained in your object, which would be necessary if you'd pass objects to data, not just simple strings as in the example.

If the object has a standard .clone() method, the option can be set to true. In this case it is expected that object.clone() returns a deep copy of itself.

If the object has a copy-constructor, the option can be set to the string 'constructor', and the constructor function itself has to be passed in the $constructor parameter. In this case it is expected that new $constructor(object) returns a deep copy of the object.

Each

You can register an each handler to enable Mangler.each() to iterate through object instances. It's up to you which part of your object you want to expose through each, just make sure to call the callback function once for each visible item, and stop the iteration if callback returns false.

Let's create a simple object store which stores items in groups, and add some items to it:

function Store() {
	this.groups = {};
}

Store.prototype.addGroup = function(groupName) {
	this.groups[groupName] = [];
}

Store.prototype.addItem = function(groupName, item) {
	var g = this.groups[groupName];
	if(g) g.push(item);
}

// Create instance
var st = new Store();

st.addGroup('foo');
st.addGroup('bar');

st.addItem('foo', 'A');
st.addItem('foo', 'B');
st.addItem('bar', 'C');
st.addItem('bar', 'D');

Calling Mangler.each() on the item without registering its type would just ignore it and the callback would never be called. Here are a few different handlers you could write for this object:

// Expose the groups property directly
exposeHandler = {
	each: function(obj, callback) {
		callback('groups', obj.groups);
	}
};

// Call it once for each group
groupHandler = {
	each: function(obj, callback) {
		Mangler.each(obj.groups, function(groupName, itemsArray) {
			// Pass on callback return value to stop if needed
			return callback(groupName, itemsArray);
		});
	}
};

// All items as a single array
itemHandler = {
	each: function(obj, callback) {
		var ret, i = 0;

		Mangler.each(obj.groups, function(groupName, itemsArray) {
			Mangler.each(itemsArray, function(key, item) {
				// Bubble up return value to stop if needed
				ret = callback(i++, item);
				return ret;
			});
			return ret;
		});
	}
};

And this is what the above handlers would do:

printKeyValue = function(key, value) {
	console.log(key, '=', value);
};

Mangler.registerType(Store, exposeHandler);
Mangler.each(st, printKeyValue);

/*
	Output:

	'groups' = { foo: ['A', 'B'], bar: ['C', 'D'] }
*/

Mangler.registerType(Store, groupHandler);
Mangler.each(st, printKeyValue);

/*
	Output:

	'foo' = ['A', 'B']
	'bar' = ['C', 'D']
*/

Mangler.registerType(Store, itemHandler);
Mangler.each(st, printKeyValue);

/*
	Output:

	0 = 'A'
	1 = 'B'
	2 = 'C'
	3 = 'D'
*/

If the object has a standard .each() method, the option can be set to true. In this case it is expected that object.each(callback) iterated through the object and calls the callback function with the parameters (key, value).

The option can also be 'array' if object is array-like. It must have a length parameter, and its items must be accessible via reading object[index] directly.

Methods affected by an each handler

The primary purpose of writing an each handler is to modify the behaviour of various Mangler.js methods:

Mangler.each()

By default it ignores unrecognised objects and callback is never called. Once registered, the each handler will be invoked, which then calls the callback function as necessary.

.explore() and Mangler.explore()

When a handler is defined, it explores the inside of object instances as well, and includes iterated keys in the path. If the key passed to the each handler's callback is numeric, it will be put in the path as an array index. If the key is a string, it will appear as an object property.

Using the simple object store example above, this is what's processed when Store is not registered:

// Create store instance
st = new Store();

st.addGroup('foo');
st.addGroup('bar');

st.addItem('foo', 'A');
st.addItem('foo', 'B');
st.addItem('bar', 'C');
st.addItem('bar', 'D');

// Create object that contains the store instance
a = {
	hello: 'world',
	store: st,
	test: true
};

// Explore without registering Store
Mangler.explore(a, function(key, value, state) {
	console.log(state.$path);
});

/*
	Output:

	hello
	store
	test
*/

After registering Store:

Mangler.registerType(Store, groupHandler);

Mangler.explore(a, function(key, value, state) {
	console.log(state.$path);
});

/*
	Output:

	hello
	store
	store.foo
	store.foo[0]
	store.foo[1]
	store.bar
	store.bar[0]
	store.bar[1]
	test
*/

.extract()

Similarly to Mangler.explore() above, when a handler is defined it also explores the inside of object instances looking for a match. It includes iterated keys in the path: if the key passed to the each handler's callback is numeric, it will be put in the path as an array index. If the key is a string, it will appear as an object property.

Staying with the above example:

// Create store instance
st = new Store();

st.addGroup('foo');
st.addGroup('bar');

st.addItem('foo', 'A');
st.addItem('foo', 'B');
st.addItem('bar', 'C');
st.addItem('bar', 'D');

// Create object that contains the store instance
a = {
	hello: 'world',
	store: st,
	test: true
};

// Register handler
Mangler.registerType(Store, groupHandler);

// Do an extraction
Mangler(a).extract('filter');

The above will try to match 'filter' against the following paths as it processes the object:

	[0].hello
	[0].store
	[0].store.foo
	[0].store.foo[0]
	[0].store.foo[1]
	[0].store.bar
	[0].store.bar[0]
	[0].store.bar[1]
	[0].test
⚠️ **GitHub.com Fallback** ⚠️