1 /**
  2  * Copyright (C) 2010-2014 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*global gui, core*/
 26 
 27 /**
 28  * A helper object used to subscribe to events on multiple event sources. Tracking this makes it easier to unsubscribe
 29  * to all events upon destruction.
 30  * 
 31  * @constructor
 32  * @implements {core.Destroyable}
 33  */
 34 core.EventSubscriptions = function () {
 35     "use strict";
 36      var /**@type{!Array.<!{eventSource: !core.EventSource, eventid: !string, callback: !Function}>}*/
 37          subscriptions = [],
 38          /**@type {!core.EventNotifier}*/
 39          frameEventNotifier = new core.EventNotifier(),
 40          /**@type{!Object.<!string,!Array.<!{frameEventId: !string, eventSource: !Object, task: !core.ScheduledTask}>>}*/
 41          frameSubscriptions = {},
 42          /**@type{!number}*/
 43          nextFrameEventId = 0;
 44 
 45     /**
 46      * Subscribe to the specified event on the supplied eventSource
 47      * @param {!core.EventSource} eventSource
 48      * @param {!string} eventid
 49      * @param {!Function} callback
 50      */
 51     function addSubscription(eventSource, eventid, callback) {
 52         eventSource.subscribe(eventid, callback);
 53         subscriptions.push({
 54             eventSource: eventSource,
 55             eventid: eventid,
 56             callback: callback
 57         });
 58     }
 59     this.addSubscription = addSubscription;
 60 
 61     /**
 62      * Register a callback that will be invoked if the supplied event id is triggered at least once before the next
 63      * frame. The callback will only be triggered once per event id when the browser redraws the content.
 64      * The callback takes no arguments.
 65      *
 66      * @param {!core.EventSource} eventSource
 67      * @param {!string} eventid
 68      * @param {!function():undefined} callback Event callback. This callback takes NO arguments
 69      * @return {undefined}
 70      */
 71     this.addFrameSubscription = function (eventSource, eventid, callback) {
 72         var frameSubscription,
 73             frameEventId,
 74             eventFrameSubscriptions,
 75             i;
 76 
 77         if (!frameSubscriptions.hasOwnProperty(eventid)) {
 78             frameSubscriptions[eventid] = [];
 79         }
 80         eventFrameSubscriptions = frameSubscriptions[eventid];
 81 
 82         for (i = 0; i < eventFrameSubscriptions.length; i += 1) {
 83             if (eventFrameSubscriptions[i].eventSource === eventSource) {
 84                 frameSubscription = eventFrameSubscriptions[i];
 85                 break;
 86             }
 87         }
 88 
 89         if (!frameSubscription) {
 90             frameEventId = "s" + nextFrameEventId;
 91             nextFrameEventId += 1;
 92             frameEventNotifier.register(frameEventId);
 93             frameSubscription = {
 94                 // A unique frame event id is necessary in case multiple eventSources identical external event ids
 95                 frameEventId: frameEventId,
 96                 eventSource: eventSource,
 97                 task: core.Task.createRedrawTask(function() {
 98                     frameEventNotifier.emit(frameEventId, undefined);
 99                 })
100             };
101             eventFrameSubscriptions.push(frameSubscription);
102             addSubscription(eventSource, eventid, frameSubscription.task.trigger);
103         }
104 
105         frameEventNotifier.subscribe(frameSubscription.frameEventId, callback);
106     };
107 
108     /**
109      * Unsubscribe all event subscriptions on all eventSources
110      * @return {undefined}
111      */
112     function unsubscribeAll() {
113         var cleanup = [];
114 
115         subscriptions.forEach(function(subscription) {
116             subscription.eventSource.unsubscribe(subscription.eventid, subscription.callback);
117         });
118         subscriptions.length = 0;
119 
120         Object.keys(frameSubscriptions).forEach(function(eventId) {
121             frameSubscriptions[eventId].forEach(function(subscriber) {
122                 cleanup.push(subscriber.task.destroy);
123             });
124             delete frameSubscriptions[eventId];
125         });
126         /*jslint emptyblock:true*/
127         core.Async.destroyAll(cleanup, function() { });
128         /*jslint emptyblock:false*/
129         frameEventNotifier = new core.EventNotifier();
130     }
131     this.unsubscribeAll = unsubscribeAll;
132 
133     /**
134      * Destroy the object.
135      * Do not access any member of this object after this call.
136      * @param {function(!Error=):undefined} callback
137      * @return {undefined}
138      */
139     this.destroy = function(callback) {
140         unsubscribeAll();
141         callback();
142     };
143 };