1 // ==========================================================================
  2 // Project:   The M-Project - Mobile HTML5 Application Framework
  3 // Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
  4 //            (c) 2011 panacoda GmbH. All rights reserved.
  5 // Creator:   Dominik
  6 // Date:      27.10.2010
  7 // License:   Dual licensed under the MIT or GPL Version 2 licenses.
  8 //            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
  9 //            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
 10 // ==========================================================================
 11 
 12 m_require('core/utility/logger.js');
 13 
 14 /**
 15  * @class
 16  *
 17  * Object for dispatching all incoming events.
 18  *
 19  * @extends M.Object
 20  */
 21 M.EventDispatcher = M.Object.extend(
 22 /** @scope M.EventDispatcher.prototype */ {
 23 
 24     /**
 25      * The type of this object.
 26      *
 27      * @type String
 28      */
 29     type: 'M.EventDispatcher',
 30 
 31     /**
 32      * Saves the latest on click event to make sure that there are no multiple events
 33      * fired for one click.
 34      *
 35      * @type {Object}
 36      */
 37     lastEvent: {},
 38 
 39     /**
 40      * This method is used to register events and link them to a corresponding action.
 41      * 
 42      * @param {String|Object} eventSource The view's id or a DOM object.
 43      * @param {Object} events The events to be registered for the given view or DOM object.
 44      */
 45     registerEvents: function(eventSource, events, recommendedEvents, sourceType) {
 46         if(!events || typeof(events) !== 'object') {
 47             M.Logger.log('No events passed for \'' + eventSource + '\'!', M.WARN);
 48             return;
 49         }
 50 
 51         eventSource = this.getEventSource(eventSource);
 52         if(!this.checkEventSource(eventSource)) {
 53             return;
 54         }
 55 
 56         _.each(events, function(handler, type) {
 57             M.EventDispatcher.registerEvent(type, eventSource, handler, recommendedEvents, sourceType, YES);
 58         });
 59     },
 60 
 61     /**
 62      * This method is used to register a certain event for a certain view or DOM object
 63      * and link them to a corresponding action.
 64      *
 65      * @param {String} type The type of the event.
 66      * @param {String|Object} eventSource The view's id, the view object or a DOM object.
 67      * @param {Object} handler The handler for the event.
 68      * @param {Object} recommendedEvents The recommended events for this event source.
 69      * @param {Object} sourceType The type of the event source.
 70      * @param {Boolean} isInternalCall The flag to determine whether this is an internal call or not.
 71      */
 72     registerEvent: function(type, eventSource, handler, recommendedEvents, sourceType, isInternalCall) {
 73         if(!isInternalCall) {
 74             if(!handler || typeof(handler) !== 'object') {
 75                 M.Logger.log('No event passed!', M.WARN);
 76                 return;
 77             }
 78 
 79             eventSource = this.getEventSource(eventSource);
 80             if(!this.checkEventSource(eventSource)) {
 81                 return;
 82             }
 83         }
 84 
 85         if(!(recommendedEvents && _.indexOf(recommendedEvents, type) > -1)) {
 86             if(sourceType && typeof(sourceType) === 'string') {
 87                 M.Logger.log('Event type \'' + type + '\' not recommended for ' + sourceType + '!', M.WARN);
 88             } else {
 89                 M.Logger.log('Event type \'' + type + '\' not recommended!', M.WARN);
 90             }
 91         }
 92 
 93         if(!this.checkHandler(handler, type)) {
 94             return;
 95         }
 96 
 97         /* switch enter event to keyup with keycode 13 */
 98         if(type === 'enter') {
 99             eventSource.bind('keyup', function(event) {
100                 if(event.which === 13) {
101                     $(this).trigger('enter');
102                 }
103             });
104         }
105 
106         var that = this;
107         var view = M.ViewManager.getViewById(eventSource.attr('id'));
108         if(!view || (view.type !== 'M.TextFieldView' && view.type !== 'M.SearchBarView')) {
109             eventSource.unbind(type);
110         }
111         eventSource.bind(type, function(event) {
112 
113             /* discard false twice-fired events in some special cases */
114             if(eventSource.attr('id') && M.ViewManager.getViewById(eventSource.attr('id')).type === 'M.DashboardItemView') {
115                 if(that.lastEvent.tap && that.lastEvent.tap.view === 'M.DashboardItemView' && that.lastEvent.tap.x === event.clientX && that.lastEvent.tap.y === event.clientY) {
116                     return;
117                 } else if(that.lastEvent.taphold && that.lastEvent.taphold.view === 'M.DashboardItemView' && that.lastEvent.taphold.x === event.clientX && that.lastEvent.taphold.y === event.clientY) {
118                     return;
119                 }
120             }
121 
122             /* no propagation (except some specials) */
123             var killEvent = YES;
124             if(eventSource.attr('id')) {
125                 var view = M.ViewManager.getViewById(eventSource.attr('id'));
126                 if(view.type === 'M.SearchBarView') {
127                     killEvent = NO;
128                 } else if((type === 'click' || type === 'tap') && view.type === 'M.ButtonView' && view.parentView && view.parentView.type === 'M.ToggleView' && view.parentView.toggleOnClick) {
129                     killEvent = NO;
130                 }
131             }
132             if(killEvent) {
133                 event.preventDefault();
134                 event.stopPropagation();
135             }
136 
137             /* store event in lastEvent property for capturing false twice-fired events */
138             if(M.ViewManager.getViewById(eventSource.attr('id'))) {
139                 that.lastEvent[type] = {
140                     view: M.ViewManager.getViewById(eventSource.attr('id')).type,
141                     date: +new Date(),
142                     x: event.clientX,
143                     y: event.clientY
144                 }
145             }
146 
147             /* event logger, uncomment for development mode */
148             //M.Logger.log('Event \'' + event.type + '\' did happen for id \'' + event.currentTarget.id + '\'', M.INFO);
149 
150             if(handler.nextEvent) {
151                 that.bindToCaller(handler.target, handler.action, [event.currentTarget.id ? event.currentTarget.id : event.currentTarget, event, handler.nextEvent])();
152             } else {
153                 that.bindToCaller(handler.target, handler.action, [event.currentTarget.id ? event.currentTarget.id : event.currentTarget, event])();
154             }
155         });
156     },
157 
158     /**
159      * This method can be used to unregister events.
160      *
161      * @param {String|Object} eventSource The view's id, the view object or a DOM object.
162      */
163     unregisterEvents: function(eventSource) {
164         eventSource = this.getEventSource(eventSource);
165         if(!this.checkEventSource(eventSource)) {
166             return;
167         }
168         eventSource.unbind();
169     },
170 
171     /**
172      * This method can be used to unregister events.
173      *
174      * @param {String} type The type of the event.
175      * @param {String|Object} eventSource The view's id, the view object or a DOM object.
176      */
177     unregisterEvent: function(type, eventSource) {
178         eventSource = this.getEventSource(eventSource);
179         if(!this.checkEventSource(eventSource)) {
180             return;
181         }
182         eventSource.unbind(type);
183     },
184 
185     /**
186      * This method is used to explicitly call an event handler. We mainly use this for
187      * combining internal and external events.
188      *
189      * @param {Object} handler The handler for the event.
190      * @param {Object} event The original DOM event.
191      * @param {Boolean} passEvent Determines whether or not to pass the event and its target as the first parameters for the handler call.
192      * @param {Array} parameters The (additional) parameters for the handler call.
193      */
194     callHandler: function(handler, event, passEvent, parameters) {
195         if(!this.checkHandler(handler, (event && event.type ? event.type : 'undefined'))) {
196             return;
197         }
198 
199         if(!passEvent) {
200             this.bindToCaller(handler.target, handler.action, parameters)();
201         } else {
202             this.bindToCaller(handler.target, handler.action, [event.currentTarget.id ? event.currentTarget.id : event.currentTarget, event])();
203         }
204     },
205 
206     /**
207      * This method is used to check the handler. It tests if target and action are
208      * specified correctly.
209      *
210      * @param {Object} handler The handler for the event.
211      * @param {String} type The type of the event.
212      * @return {Boolean} Specifies whether or not the check was successful.
213      */
214     checkHandler: function(handler, type) {
215         if(typeof(handler.action) === 'string') {
216             if(handler.target) {
217                 if(handler.target[handler.action] && typeof(handler.target[handler.action]) === 'function') {
218                     handler.action = handler.target[handler.action];
219                     return YES;
220                 } else {
221                     M.Logger.log('No action \'' + handler.action + '\' found for given target and the event type \'' + type + '\'!', M.WARN);
222                     return NO;
223                 }
224             } else {
225                 M.Logger.log('No valid target passed for action \'' + handler.action + '\' and the event type \'' + type + '\'!', M.WARN);
226                 return NO;
227             }
228         } else if(typeof(handler.action) !== 'function') {
229             M.Logger.log('No valid action passed for the event type \'' + type + '\'!', M.WARN);
230             return NO;
231         }
232 
233         return YES;
234     },
235 
236     /**
237      * This method is used to get the event source as a DOM object.
238      *
239      * @param {Object|String} eventSource The event source.
240      * @return {Object} The event source as dom object.
241      */
242     getEventSource: function(eventSource) {
243         if(typeof(eventSource) === 'string') {
244             eventSource = $('#' + eventSource + ':first');
245         } else {
246             eventSource = $(eventSource);
247         }
248         
249         return eventSource;
250     },
251 
252     /**
253      * This method is used to check the event source. It tests if it is correctly
254      * specified.
255      *
256      * @param {Object} eventSource The event source.
257      * @return {Boolean} Specifies whether or not the check was successful.
258      */
259     checkEventSource: function(eventSource) {
260         if(!eventSource) {
261             M.Logger.log('The event source is invalid!', M.WARN);
262             return NO;
263         }
264         
265         return YES;
266     }
267 
268 });