API Docs for: 1.0.0
Show:

File: src/gallery-multiobject/js/MultiObject.js

"use strict";

/**
 * @module gallery-multiobject
 */

/**********************************************************************
 * <p>MultiObject exposes exactly the same API as each individual object,
 * both functions and events, and the state of all the objects is kept in
 * sync.  The objects must maintain all state via
 * Y.Attribute.<p>
 *
 * <p>MultiObject is similar to Y.ArrayList, except:</p>
 * <ul>
 * <li>All objects must be of the same type, since MultiObject is supposed
 *		to behave exactly like any single object.</li>
 * <li>MultiObject automatically delegates all methods.</li>
 * <li>By default, MultiObject returns the result from the first object
 *		in the list, not an array of results.</li>
 * <li>MultiObject propagates all events.</li>
 * </ul>
 *
 * <p>Internally, MultiObject delegates all methods by name, so it supports
 * Y.Do.before, Y.Do.after, etc.</p>
 *
 * <p>To avoid shadowing potential function names, we inherit from
 * Y.EventTarget and use multi_ as the prefix for our own functions.</p>
 * 
 * @main gallery-multiobject
 * @class MultiObject
 * @extends EventTarget
 * @constructor
 * @param items {Array} initial set of items
 * @param config {Object} configuration
 * @param config.return_all_results=false {Boolean}
 * If this is true, then all delegated functions
 * will return an array of results instead of the result from the
 * primary item.  Note that functions which return `undefined` or
 * the item itself always cause the MultiObject to be returned,
 * to support chaining.
 * @param config.primary_item_index=0 {Number}
 * When `return_all_results=false`, this is the index of
 * the item which generates the return result for all delegated
 * functions.
 */
function MultiObject(
	/* array */		items,
	/* object */	config)
{
	this.items = Y.Array(items);

	var item = this.items[0];
	for (var f in item)
	{
		if (Y.Lang.isFunction(item[f]) && !this[f])
		{
			this[f] = delegate.call(this, f);
		}
	}

	this.args_adjuster = {};

	config                  = config || {};
	this.primary_item_index = config.primary_item_index || 0;
	this.return_all_results = config.return_all_results;

	Y.Array.each(this.items, installItem, this);

	config.prefix = this.items[0].name;
	MultiObject.superclass.constructor.call(this, config);
}

function delegate(
	/* string */	f)
{
	return function()
	{
		var args    = arguments;
		var results = [];

		Y.Array.each(this.items, function(item, index)
		{
			var args1 = args;
			if (this.args_adjuster[f])
			{
				args1 = this.args_adjuster[f].call(item, index, Y.Array(args, 0, true));
			}

			var result = item[f].apply(item, args1);

			if (!Y.Lang.isUndefined(result) && result !== item)
			{
				results.push(result);
			}
		},
		this);

		if (results.length === 0)
		{
			return this;	// chainable
		}
		else if (this.return_all_results)
		{
			return results;
		}
		else
		{
			return results[ this.primary_item_index ];
		}
	};
}

function distributeValue(e, index, key)
{
	Y.Array.each(this.items, function(item, i)
	{
		if (i !== index)
		{
			item.set(key, e.newVal);
		}
	});
}

function installItem(
	/* object */	item,
	/* int */		index)
{
	if (!item)
	{
		return;
	}

	Y.Object.each(item.getAttrs(), function(value, key)
	{
		item.after(key+'Change', distributeValue, this, index, key);
	},
	this);

	item.addTarget(this);
}

function uninstallItem(
	/* object */	item,
	/* int */		index)
{
	if (!item)
	{
		return;
	}

	Y.Object.each(item.getAttrs(), function(value, key)
	{
		item.detach(key+'Change', distributeValue, this);
	},
	this);

	item.removeTarget(this);
}

Y.extend(MultiObject, Y.EventTarget,
{
	/**
	 * Destroys the MultiObject, but not the individual objects.
	 * <code>destroy()</code> is, of course, delegated.
	 * 
	 * @method multi_destroy
	 */
	multi_destroy: function()
	{
		Y.Array.each(this.items, uninstallItem, this);
	},

	/**
	 * @method multi_get_primary_item_index
	 * @return the index of the primary item
	 */
	multi_get_primary_item_index: function()
	{
		return this.primary_item_index;
	},

	/**
	 * @method multi_set_primary_item_index
	 * @param index {int} the index of the primary item
	 */
	multi_set_primary_item_index: function(
		/* int */	index)
	{
		this.primary_item_index = index;
	},

	/**
	 * @method multi_get_return_all_results
	 * @return true if all results will be returned by delegated functions
	 */
	multi_get_return_all_results: function()
	{
		return this.return_all_results;
	},

	/**
	 * @method multi_set_return_all_results
	 * @param all {boolean} true if delegated functions should return all results
	 */
	multi_set_return_all_results: function(
		/* bool */	all)
	{
		this.return_all_results = all;
	},

	/**
	 * Return an array of all the individual results from calling the
	 * specified function.  This is only useful if return_all_results=false.
	 *
	 * @method multi_get_all
	 * @param f {String} name of the function to invoke
	 * @param arg* {mixed} 0..n arguments to pass to the function
	 * @return {Array} results from delegating the named function
	 */
	multi_get_all: function(
		/* string */ f)
	{
		var args = Y.Array(arguments, 0, true);
		args.shift();

		var all = this.return_all_results;
		this.return_all_results = true;
		var results = this[f].apply(this, args);
		this.return_all_results = all;

		return results;
	}

//	push, pop, shift, unshift, add, remove (index or object), splice
//	item, indexOf, size
});

Y.MultiObject = MultiObject;