/* * jquery.subscribe.1.1 * * Implementation of publish/subcription framework for jQuery * Requires use of jQuery. Tested with jQuery 1.3 and above * * * Copyright (c) 2008 Eric Chijioke (obinna a-t g mail dot c o m) * * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * * Release Notes: * * version 1.1: * * Fixed unexpected behavior which can occur when a script in a embedded page (page loaded in div,tab etc.) subscribes a handler for a topic using * the jQuery subscribe ($.subscribe) or a no-id element but this subscribe plugin is not reloaded within that embedded page (for example, when * script is included in containing page) . In this case, if the embedded page is reloaded without reloading the entire page (and plugin), the * subscription could be made multiple times for the topic, which will call the handler multiple times each time the topic is published. * Code has been added to prevent this when the subscription is made using the non-element subscribe ($.subscribe()), which assures that only one * subscription is made for a topic for a given window/frame. To prevent this from happening for an element subscription ($elem.subscribe()), make * sure that the element has an id attribute. */ (function($){ _subscribe_topics = {}; _subscribe_handlers = {}; _subscribe_getDocumentWindow = function(document){ return document.parentWindow || document.defaultView; }; $.fn.extend({ /** * Creates a new topic without any subscribers. * Not usually used explicitly */ createTopic : function(topic) { if(topic && !_subscribe_topics[topic]) { _subscribe_topics[topic] = {}; _subscribe_topics[topic].objects = {}; _subscribe_topics[topic].objects['__noId__'] = []; } return this; }, /** * Destroy an existing topic and unsubscribe all subscribers */ destroyTopic : function(topic) { if(topic && _subscribe_topics[topic]) { for(i in _subscribe_topics[topic].objects) { var object = _subscribe_topics[topic].objects[i]; if($.isArray(object)) { // handle '__noId__' elements if(object.length > 0) { for(j in object) { object[j].unbind(topic); } } } else { object.unbind(topic,data); } } } delete _subscribe_topics[topic]; return this; }, /** * Subscribes an object to particular topic with a handler. * When the topic is published, this handler will be executed. * * Parameters: * -topic- is the string name of the topic * -handler- is a handler function and is of the form function(event, data), in which the 'this' refers to the element itself. * handler can be a function or can be a string referring to a function previously registered using the $.subscribeHandler() function * Note: returning 'false' from the handler will prevent subsequent handlers from being executed on this element during * this call. * -data- (optional) is additional data that is passed to the event handler as event.data when the topic is published * * Note: Unexpected behavior can occur when a script in a embedded page (page loaded in div,tab etc.) subscribes a handler for a topic using * the jQuery subscribe ($.subscribe) or a no-id element but this subscribe plugin is not reloaded within that embedded page (for example, when * script is included in containing page) . In this case, if the embedded page is reloaded without reloading the entire page (and plugin), the * subscription could be made multiple times for the topic, which will call the handler multiple times each time the topic is published. * Code has been added to prevent this when the subscription is made using the non-element subscribe ($.subscribe()), which assures that only one * subscription is made for a topic for a given window/frame. To prevent this from happening for an element subscription ($elem.subscribe()), make * sure that the element has an id attribute. */ subscribe : function(topic, handler, data) { if(this[0] && topic && handler) { this.createTopic(topic); if(this.attr('id')) { _subscribe_topics[topic].objects[this.attr('id')] = this; } else { //do not subscribe the same window/frame document multiple times, this causes unexpected behavior of executing embedded scripts multiple times var noIdObjects = _subscribe_topics[topic].objects['__noId__']; if(this[0].nodeType == 9) { //if document is being bound (the case for non-element jQuery subscribing ($.subscribe) for ( var index in noIdObjects) { var noIdObject = noIdObjects[index]; if(noIdObject[0].nodeType == 9 && _subscribe_getDocumentWindow(this[0]).frameElement == _subscribe_getDocumentWindow(noIdObject[0]).frameElement ) { return this; } } } var exists = false; for(var i = 0; i < noIdObjects.length; i++){ if(noIdObjects[i] == this){ exists = true; break; } } if(!exists) { _subscribe_topics[topic].objects['__noId__'].push(this); } } if(typeof(handler) == 'function') { this.bind(topic, data, handler); } else if(typeof(handler) == 'string' && typeof(_subscribe_handlers[handler]) == 'function') { this.bind(topic, data, _subscribe_handlers[handler]); } } return this; }, /** * Remove a subscription of an element to a topic. * This will unbind stop all handlers from executing on this element when the topic * is published */ unsubscribe : function(topic) { if(topic) { if(_subscribe_topics[topic]) { if(this.attr('id')) { var object = _subscribe_topics[topic].objects[this.attr('id')]; if(object) { delete _subscribe_topics[topic].objects[this.attr('id')]; } } else { var noIdObjects = _subscribe_topics[topic].objects['__noId__']; for(var i = 0; i < noIdObjects.length; i++){ if(noIdObjects[i] == this){ subscribe_topics[topic].objects['__noId__'].splice(index,1); break; } } } } this.unbind(topic); } return this; }, /** * Publishes a topic (triggers handlers on all topic subscribers) * This ends up calling any subscribed handlers which are functions of the form function (event, data) * where: event - is a standard jQuery event object * data - is the data parameter that was passed to this publish() method * event.data - is the data parameter passed to the subscribe() function when this published topic was subscribed to * event.target - is the dom element that subscribed to the event (or the document element if $.subscribe() was used) * * Parameters: * -topic- is the string name of the topic * -data- (optional) is additional data that is passed to the event handler 'data' parameter when the topic is published * handler can be a function or can be a string referring to a function previously registered using the $.subscribeHandler() function * -originalEvent- (optional) may be passed in a reference to an event which triggered this publishing. This will be passed as the * 'originalEvent' field of the triggered event which will allow for controlling the propagation of higher level events * from within the topic handler. In other words, this allows one to cancel execution of all subsequent handlers on the originalEvent * for this element by return 'false' from a handler that is subscribed to the topic published here. This can be especially useful * in conjunction with publishOnEvent(), where a topic is published when an event executes (such as a click) and we want our * handler logic prevent additional topics from being published (For example if our topic displays a 'delete confirm' dialog on click and * the user cancels, we may want to prevent subsequent topics bound to the original click event from being published). */ publish : function(topic, data, originalEvent) { if(topic) { this.createTopic(topic); //if an orginal event exists, need to modify the event object to prevent execution of all //other handlers if the result of the handler is false (which calls stopPropagation()) var subscriberStopPropagation = function(){ this.isImmediatePropagationStopped = function(){ return true; }; (new $.Event).stopPropagation(); if(this.originalEvent) { this.originalEvent.isImmediatePropagationStopped = function(){ return true; }; this.originalEvent.stopPropagation = subscriberStopPropagation; } } var event = jQuery.Event(topic); $.extend(event,{originalEvent: originalEvent, stopPropagation: subscriberStopPropagation}); for(i in _subscribe_topics[topic].objects) { var object = _subscribe_topics[topic].objects[i]; if($.isArray(object)) { // handle '__noId__' elements (if any) if(object.length > 0) { for(j in object) { object[j].trigger( event,data); } } } else { object.trigger( event,data); } } } return this; }, /** * Binds an objects event handler to a publish call * * Upon the event triggering, this ends up calling any subscribed handlers which are functions of the form function (event, data) * where: event- is a standard jQuery event object * event.data- is the data parameter passed to the subscribe() function when this published topic was subscribed to * data- is the data parameter that was passed to this publishOnEvent() method * Parameters: * -event- is the string name of the event upon which to publish the topic * -topic- is the string name of the topic to publish when the event occurs * -data- (optional) is additional data which will be passed in to the publish() method ant hen available as the second ('data') * parameter to the topic handler */ publishOnEvent : function(event, topic, data) { if(event && topic) { this.createTopic(topic); this.bind(event, data, function (e) { $(this).publish(topic, e.data, e); }); } return this; } }); /** * Make publish(), createTopic() and destroyTopic() callable without an element context * Often don't need a context to subscribe, publish, create or destroy a topic. * We will call from the document context */ $.extend({ /** * Subscribe an event handler to a topic without an element context * * Note: Caution about subscribing using same document to topic multiple time (maybe by loading subscribe script multiple times) * */ subscribe : function(topic, handler, data) { return $(window).subscribe(topic, handler, data); }, /** * Unsubscribe an event handler for a topic without an element context * */ unsubscribe : function(topic, handler, data) { return $(window).unsubscribe(topic, handler, data); }, /** * Register a handler function which can then be referenced by name when calling subscribe() */ subscribeHandler: function(name, handler) { if(name && handler && typeof(handler) == "function") { _subscribe_handlers[name] = handler; } return $(window); }, publish: function(topic, data) { return $(window).publish(topic,data); }, createTopic: function(topic) { return $(window).createTopic(topic); }, destroyTopic: function(topic) { return $(window).destroyTopic(topic); } }); })(jQuery);