API Docs for: 1.0.0
Show:

File: src/gallery-node-event-set/js/node-event-set.js

"use strict";

/**
 * @module gallery-node-event-set
 */

/**********************************************************************
 * Patches Y.Node to provide "set" events for attributes and styles similar
 * to the "change" events provided by `Y.Attribute`.  Simply subscribe to
 * _attr_Set or _style_Set, e.g., valueSet, z-indexSet, or classSet.
 *
 * IMPORTANT: "set" events will ONLY fire if changes are made through
 * `Y.Node`, NOT when directly operating on the DOM element.  Also NOT when a
 * different sandbox operates on a separate `Y.Node` instance for the same
 * element.
 *
 * IMPORTANT: "set" events must be subscribed directly on the `Y.Node`, not
 * via `Y.on(...)`.
 *
 * Note: The valuechange event provided by YUI captures all changes to the
 * element's value attribute, but only when the element has focus.
 *
 * To minimize the performance impact, this module initially overrides only
 * Y.Node.on().  Patches are then applied to the appropriate functions on
 * individual instances when a "set" event is requested.
 *
 * <dl>
 * <dt>set, setAttrs, setAttribute, setStyle, setStyles</dt>
 * <dd>Fires _attr_Set or _style_Set event with prevVal, newVal.</dd>
 * <dt>setData,clearData</dt>
 * <dd>Fires dataSet event with dataKey, prevVal, newVal.</dd>
 * <dt>addClass, removeClass, replaceClass</dt>
 * <dd>Fires classNameSet event with prevVal, newVal -- consistent with set('className', ...).  Also includes addedClass or removedClass, as appropriate.</dd>
 * <dt>setX, setY, setXY</dt>
 * <dd>Fires xySet event with prevVal and newVal defining x, y, or both.</dd>
 *
 * @main gallery-node-event-set
 * @class Node~event-set
 */

function setValue(get, set, attr, val)
{
	var prev_val = get.call(this, attr);
	set.apply(this, Y.Array(arguments, 2));

	// always fire because type conversion is too complicated

	this.fire(attr + 'Set',
	{
		prevVal: prev_val,
		newVal:  val
	});
}

function setValues(get, set, map)
{
	var prev_map = {};
	Y.each(map, function(value, key)
	{
		prev_map[ key ] = get.call(this, key);
	},
	this);

	set.apply(this, Y.Array(arguments, 2));

	// always fire because type conversion is too complicated

	Y.each(map, function(value, key)
	{
		this.fire(key + 'Set',
		{
			prevVal: prev_map[ key ],
			newVal:  value
		});
	},
	this);
}

function patchSet()
{
	this._event_set_patched_set = true;

	this.set          = Y.bind(setValue,  this, this.get, this.set);
	this.setAttrs     = Y.bind(setValues, this, this.get, this.setAttrs);
	this.setAttribute = Y.bind(setValue,  this, this.getAttribute, this.setAttribute);
	this.setStyle     = Y.bind(setValue,  this, this.getStyle, this.setStyle);
	this.setStyles    = Y.bind(setValues, this, this.getStyle, this.setStyles);
}

function patchData()
{
	this._event_set_patched_data = true;

	var orig_setData = this.setData;
	this.setData = function(name, val)
	{
		if (arguments.length > 1)
		{
			var prev_val = this.getData(name);
		}
		else
		{
			var data_map = {};
			Y.each(this.getData(), function(value, key)
			{
				data_map[ key ] = { prevVal: value };
			});
		}

		orig_setData.apply(this, arguments);

		if (arguments.length > 1)
		{
			this.fire('dataSet',
			{
				dataKey: name,
				prevVal: prev_val,
				newVal:  val
			});
		}
		else
		{
			Y.each(name, function(value, key)
			{
				if (data_map[ key ])
				{
					data_map[ key ].newVal = value;
				}
				else
				{
					data_map[ key ] = { newVal: value };
				}
			});

			Y.each(data_map, function(value, key)
			{
				this.fire('dataSet',
				{
					dataKey: key,
					prevVal: value.prevVal,
					newVal:  value.newVal
				});
			},
			this);
		}
	};

	var orig_clearData = this.clearData;
	this.clearData = function(name)
	{
		if (name)
		{
			var prev_val = this.getData(name);
		}
		else
		{
			var prev_map = this.getData();
		}

		orig_clearData.apply(this, arguments);

		if (name)
		{
			this.fire('dataSet',
			{
				dataKey: name,
				prevVal: prev_val
			});
		}
		else
		{
			Y.each(prev_map, function(value, key)
			{
				this.fire('dataSet',
				{
					dataKey: key,
					prevVal: value
				});
			},
			this);
		}
	};
}

function patchClass()
{
	this._event_set_patched_class = true;

	var orig_addClass = this.addClass;
	this.addClass = function(className)
	{
		if (this.hasClass(className))
		{
			return;
		}

		var prev_class = this.get('className');
		orig_addClass.apply(this, arguments);

		if (!this._event_set_do_not_fire_add_remove_class)
		{
			this.fire('classNameSet',
			{
				prevVal:    prev_class,
				newVal:     this.get('className'),
				addedClass: className
			});
		}
	}

	var orig_removeClass = this.removeClass;
	this.removeClass = function(className)
	{
		if (!this.hasClass(className))
		{
			return;
		}

		var prev_class = this.get('className');
		orig_removeClass.apply(this, arguments);

		if (!this._event_set_do_not_fire_add_remove_class)
		{
			this.fire('classNameSet',
			{
				prevVal:      prev_class,
				newVal:       this.get('className'),
				removedClass: className
			});
		}
	}

	var orig_replaceClass = this.replaceClass;
	this.replaceClass = function(oldC, newC)
	{
		this._event_set_do_not_fire_add_remove_class = true;

		var prev_class = this.get('className'),
			had_class  = this.hasClass(oldC);

		orig_replaceClass.apply(this, arguments);

		var event =
		{
			prevVal:    prev_class,
			newVal:     this.get('className'),
			addedClass: newC
		};

		if (had_class)
		{
			event.removedClass = oldC;
		}
		this.fire('classNameSet', event);

		this._event_set_do_not_fire_add_remove_class = false;
	}
}

function setPos(set, xy)
{
	var prev_pt = this.getXY();
	set.apply(this, Y.Array(arguments, 1));

	this.fire('xySet',
	{
		prevVal: prev_pt,
		newVal:  this.getXY()
	});
}

function patchXY()
{
	this._event_set_patched_xy = true;

	this.setX  = Y.bind(setPos, this, this.setX);
	this.setY  = Y.bind(setPos, this, this.setY);
	this.setXY = Y.bind(setPos, this, this.setXY);
}

var orig_on = Y.Node.prototype.on;
Y.Node.prototype.on = function(type, fn, context)
{
	// manually check characters, because that is faster than a regexp

	if (type.length > 3 && type.charAt &&
		type.charAt(type.length-3) == 'S' &&
		type.charAt(type.length-2) == 'e' &&
		type.charAt(type.length-1) == 't')
	{
		if (type == 'dataSet')
		{
			if (!this._event_set_patched_data)
			{
				patchData.call(this);
			}
		}
		else if (type == 'classNameSet')
		{
			if (!this._event_set_patched_class)	// add/remove/replace class
			{
				patchClass.call(this);
			}

			if (!this._event_set_patched_set)	// set('className', ...)
			{
				patchSet.call(this);
			}
		}
		else if (type == 'xySet')
		{
			if (!this._event_set_patched_xy)
			{
				patchXY.call(this);
			}
		}
		else if (!this._event_set_patched_set)
		{
			patchSet.call(this);
		}

		this.publish(type,
		{
			emitFacade: true
		});
	}

	return orig_on.apply(this, arguments);
};