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 });