/** * Observable plugin * * Copyright (c) 2010 Filatov Dmitry (alpha@zforms.ru) * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * * @version 1.0.0 * @requires $.identify * @requires $.inherit */ (function($) { var storageExpando = '__' + (+new Date) + 'storage', getFnId = function(fn, ctx) { return $.identify(fn) + (ctx? $.identify(ctx) : ''); }, Observable = /** @lends $.observable.prototype */{ /** * Builds full event name * @protected * @param {String} e Event type * @returns {String} */ buildEventName : function(e) { return e; }, /** * Adding event handler * @param {String} e Event type * @param {Object} [data] Additional data that the handler gets as e.data * @param {Function} fn Handler * @param {Object} [ctx] Handler context * @returns {$.observable} */ on : function(e, data, fn, ctx, _special) { if(typeof e == 'string') { if($.isFunction(data)) { ctx = fn; fn = data; data = undefined; } var id = getFnId(fn, ctx), storage = this[storageExpando] || (this[storageExpando] = {}), eList = e.split(' '), i = 0, eStorage; while(e = eList[i++]) { e = this.buildEventName(e); eStorage = storage[e] || (storage[e] = { ids : {}, list : {} }); if(!(id in eStorage.ids)) { var list = eStorage.list, item = { fn : fn, data : data, ctx : ctx, special : _special }; if(list.last) { list.last.next = item; item.prev = list.last; } else { list.first = item; } eStorage.ids[id] = list.last = item; } } } else { var _this = this; $.each(e, function(e, fn) { _this.on(e, fn, data, _special); }); } return this; }, onFirst : function(e, data, fn, ctx) { return this.on(e, data, fn, ctx, { one : true }); }, /** * Removing event handler(s) * @param {String} [e] Event type * @param {Function} [fn] Handler * @param {Object} [ctx] Handler context * @returns {$.observable} */ un : function(e, fn, ctx) { if(typeof e == 'string' || typeof e == 'undefined') { var storage = this[storageExpando]; if(storage) { if(e) { // if event type was passed var eList = e.split(' '), i = 0, eStorage; while(e = eList[i++]) { e = this.buildEventName(e); if(eStorage = storage[e]) { if(fn) { // if specific handler was passed var id = getFnId(fn, ctx), ids = eStorage.ids; if(id in ids) { var list = eStorage.list, item = ids[id], prev = item.prev, next = item.next; if(prev) { prev.next = next; } else if(item === list.first) { list.first = next; } if(next) { next.prev = prev; } else if(item === list.last) { list.last = prev; } delete ids[id]; } } else { delete this[storageExpando][e]; } } } } else { delete this[storageExpando]; } } } else { var _this = this; $.each(e, function(e, fn) { _this.un(e, fn, ctx); }); } return this; }, /** * Fires event handlers * @param {String|$.Event} e Event * @param {Object} [data] Additional data * @returns {$.observable} */ trigger : function(e, data) { var _this = this, storage = _this[storageExpando], rawType; typeof e === 'string'? e = $.Event(_this.buildEventName(rawType = e)) : e.type = _this.buildEventName(rawType = e.type); e.target || (e.target = _this); if(storage && (storage = storage[e.type])) { var item = storage.list.first, ret; while(item) { e.data = item.data; ret = item.fn.call(item.ctx || _this, e, data); if(typeof ret !== 'undefined') { e.result = ret; if(ret === false) { e.preventDefault(); e.stopPropagation(); } } item.special && item.special.one && _this.un(rawType, item.fn, item.ctx); item = item.next; } } return this; } }; $.observable = $.inherit(Observable, Observable); })(jQuery);