Observable.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /**
  2. * Observable.js
  3. *
  4. * Copyright, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. */
  10. /**
  11. * This mixin will add event binding logic to classes.
  12. *
  13. * @mixin tinymce.util.Observable
  14. */
  15. define("tinymce/util/Observable", [
  16. "tinymce/util/Tools"
  17. ], function(Tools) {
  18. var bindingsName = "__bindings";
  19. var nativeEvents = Tools.makeMap(
  20. "focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange" +
  21. " mouseout mouseenter mouseleave keydown keypress keyup contextmenu dragstart dragend dragover draggesture dragdrop drop drag", ' '
  22. );
  23. function returnFalse() {
  24. return false;
  25. }
  26. function returnTrue() {
  27. return true;
  28. }
  29. return {
  30. /**
  31. * Fires the specified event by name.
  32. *
  33. * @method fire
  34. * @param {String} name Name of the event to fire.
  35. * @param {tinymce.Event/Object?} args Event arguments.
  36. * @param {Boolean?} bubble True/false if the event is to be bubbled.
  37. * @return {tinymce.Event} Event instance passed in converted into tinymce.Event instance.
  38. * @example
  39. * instance.fire('event', {...});
  40. */
  41. fire: function(name, args, bubble) {
  42. var self = this, handlers, i, l, callback, parent;
  43. name = name.toLowerCase();
  44. args = args || {};
  45. args.type = name;
  46. // Prevent all events except the remove event after the editor has been removed
  47. if (self.removed && name !== "remove") {
  48. return;
  49. }
  50. // Setup target is there isn't one
  51. if (!args.target) {
  52. args.target = self;
  53. }
  54. // Add event delegation methods if they are missing
  55. if (!args.preventDefault) {
  56. // Add preventDefault method
  57. args.preventDefault = function() {
  58. args.isDefaultPrevented = returnTrue;
  59. };
  60. // Add stopPropagation
  61. args.stopPropagation = function() {
  62. args.isPropagationStopped = returnTrue;
  63. };
  64. // Add stopImmediatePropagation
  65. args.stopImmediatePropagation = function() {
  66. args.isImmediatePropagationStopped = returnTrue;
  67. };
  68. // Add event delegation states
  69. args.isDefaultPrevented = returnFalse;
  70. args.isPropagationStopped = returnFalse;
  71. args.isImmediatePropagationStopped = returnFalse;
  72. }
  73. //console.log(name, args);
  74. if (self[bindingsName]) {
  75. handlers = self[bindingsName][name];
  76. if (handlers) {
  77. for (i = 0, l = handlers.length; i < l; i++) {
  78. handlers[i] = callback = handlers[i];
  79. // Stop immediate propagation if needed
  80. if (args.isImmediatePropagationStopped()) {
  81. break;
  82. }
  83. // If callback returns false then prevent default and stop all propagation
  84. if (callback.call(self, args) === false) {
  85. args.preventDefault();
  86. return args;
  87. }
  88. }
  89. }
  90. }
  91. // Bubble event up to parents
  92. if (bubble !== false && self.parent) {
  93. parent = self.parent();
  94. while (parent && !args.isPropagationStopped()) {
  95. parent.fire(name, args, false);
  96. parent = parent.parent();
  97. }
  98. }
  99. return args;
  100. },
  101. /**
  102. * Binds an event listener to a specific event by name.
  103. *
  104. * @method on
  105. * @param {String} name Event name or space separated list of events to bind.
  106. * @param {callback} callback Callback to be executed when the event occurs.
  107. * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
  108. * @return {Object} Current class instance.
  109. * @example
  110. * instance.on('event', function(e) {
  111. * // Callback logic
  112. * });
  113. */
  114. on: function(name, callback, prepend) {
  115. var self = this, bindings, handlers, names, i;
  116. if (callback === false) {
  117. callback = function() {
  118. return false;
  119. };
  120. }
  121. if (callback) {
  122. names = name.toLowerCase().split(' ');
  123. i = names.length;
  124. while (i--) {
  125. name = names[i];
  126. bindings = self[bindingsName];
  127. if (!bindings) {
  128. bindings = self[bindingsName] = {};
  129. }
  130. handlers = bindings[name];
  131. if (!handlers) {
  132. handlers = bindings[name] = [];
  133. if (self.bindNative && nativeEvents[name]) {
  134. self.bindNative(name);
  135. }
  136. }
  137. if (prepend) {
  138. handlers.unshift(callback);
  139. } else {
  140. handlers.push(callback);
  141. }
  142. }
  143. }
  144. return self;
  145. },
  146. /**
  147. * Unbinds an event listener to a specific event by name.
  148. *
  149. * @method off
  150. * @param {String?} name Name of the event to unbind.
  151. * @param {callback?} callback Callback to unbind.
  152. * @return {Object} Current class instance.
  153. * @example
  154. * // Unbind specific callback
  155. * instance.off('event', handler);
  156. *
  157. * // Unbind all listeners by name
  158. * instance.off('event');
  159. *
  160. * // Unbind all events
  161. * instance.off();
  162. */
  163. off: function(name, callback) {
  164. var self = this, i, bindings = self[bindingsName], handlers, bindingName, names, hi;
  165. if (bindings) {
  166. if (name) {
  167. names = name.toLowerCase().split(' ');
  168. i = names.length;
  169. while (i--) {
  170. name = names[i];
  171. handlers = bindings[name];
  172. // Unbind all handlers
  173. if (!name) {
  174. for (bindingName in bindings) {
  175. bindings[name].length = 0;
  176. }
  177. return self;
  178. }
  179. if (handlers) {
  180. // Unbind all by name
  181. if (!callback) {
  182. handlers.length = 0;
  183. } else {
  184. // Unbind specific ones
  185. hi = handlers.length;
  186. while (hi--) {
  187. if (handlers[hi] === callback) {
  188. handlers.splice(hi, 1);
  189. }
  190. }
  191. }
  192. if (!handlers.length && self.unbindNative && nativeEvents[name]) {
  193. self.unbindNative(name);
  194. delete bindings[name];
  195. }
  196. }
  197. }
  198. } else {
  199. if (self.unbindNative) {
  200. for (name in bindings) {
  201. self.unbindNative(name);
  202. }
  203. }
  204. self[bindingsName] = [];
  205. }
  206. }
  207. return self;
  208. },
  209. hasEventListeners: function(name) {
  210. var bindings = this[bindingsName];
  211. name = name.toLowerCase();
  212. return !(!bindings || !bindings[name] || bindings[name].length === 0);
  213. }
  214. };
  215. });