61ff238a2e362f16bb1336d76ded3f5b16a66d76.svn-base 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. /*
  2. * Ext JS Library 2.0 Copyright(c) 2006-2007, Ext JS, LLC. licensing@extjs.com
  3. *
  4. * http://extjs.com/license
  5. */
  6. /**
  7. * @class Ext.util.Observable Abstract base class that provides a common
  8. * interface for publishing events. Subclasses are expected to to have a
  9. * property "events" with all the events defined.<br>
  10. * For example:
  11. *
  12. * <pre><code>
  13. * Employee = function(name) {
  14. * this.name = name;
  15. * this.addEvents({
  16. * &quot;fired&quot; : true,
  17. * &quot;quit&quot; : true
  18. * });
  19. * }
  20. * Ext.extend(Employee, Ext.util.Observable);
  21. * </code></pre>
  22. */
  23. Ext.util.Observable = function() {
  24. /**
  25. * @cfg {Object} listeners A config object containing one or more event
  26. * handlers to be added to this object during initialization. This
  27. * should be a valid listeners config object as specified in the
  28. * {@link #addListener} example for attaching multiple handlers at
  29. * once.
  30. */
  31. if (this.listeners) {
  32. this.on(this.listeners);
  33. delete this.listeners;
  34. }
  35. };
  36. Ext.util.Observable.prototype = {
  37. /**
  38. * Fires the specified event with the passed parameters (minus the event
  39. * name).
  40. *
  41. * @param {String}
  42. * eventName
  43. * @param {Object...}
  44. * args Variable number of parameters are passed to handlers
  45. * @return {Boolean} returns false if any of the handlers return false
  46. * otherwise it returns true
  47. */
  48. fireEvent : function() {
  49. if (this.eventsSuspended !== true) {
  50. var ce = this.events[arguments[0].toLowerCase()];
  51. if (typeof ce == "object") {
  52. return ce.fire.apply(ce, Array.prototype.slice.call(arguments,
  53. 1));
  54. }
  55. }
  56. return true;
  57. },
  58. // private
  59. filterOptRe : /^(?:scope|delay|buffer|single)$/,
  60. /**
  61. * Appends an event handler to this component
  62. *
  63. * @param {String}
  64. * eventName The type of event to listen for
  65. * @param {Function}
  66. * handler The method the event invokes
  67. * @param {Object}
  68. * scope (optional) The scope in which to execute the handler
  69. * function. The handler function's "this" context.
  70. * @param {Object}
  71. * options (optional) An object containing handler configuration
  72. * properties. This may contain any of the following properties:
  73. * <ul>
  74. * <li><b>scope</b> : Object
  75. * <p class="sub-desc">
  76. * The scope in which to execute the handler function. The
  77. * handler function's "this" context.
  78. * </p>
  79. * </li>
  80. * <li><b>delay</b> : Number
  81. * <p class="sub-desc">
  82. * The number of milliseconds to delay the invocation of the
  83. * handler after the event fires.
  84. * </p>
  85. * </li>
  86. * <li><b>single</b> : Boolean
  87. * <p class="sub-desc">
  88. * True to add a handler to handle just the next firing of the
  89. * event, and then remove itself.
  90. * </p>
  91. * </li>
  92. * <li>buffer {Number} Causes the handler to be scheduled to run
  93. * in an {@link Ext.util.DelayedTask} delayed by the specified
  94. * number of milliseconds. If the event fires again within that
  95. * time, the original handler is <em>not</em> invoked, but the
  96. * new handler is scheduled in its place.</li>
  97. * </ul>
  98. * <br>
  99. * <p>
  100. * <b>Combining Options</b><br>
  101. * Using the options argument, it is possible to combine
  102. * different types of listeners:<br>
  103. * <br>
  104. * A normalized, delayed, one-time listener that auto stops the
  105. * event and passes a custom argument (forumId)
  106. *
  107. * <pre><code>
  108. * el.on('click', this.onClick, this, {
  109. * single : true,
  110. * delay : 100,
  111. * forumId : 4
  112. * });
  113. * </code></pre>
  114. *
  115. * <p>
  116. * <b>Attaching multiple handlers in 1 call</b><br>
  117. * The method also allows for a single argument to be passed
  118. * which is a config object containing properties which specify
  119. * multiple handlers.
  120. * <p>
  121. *
  122. * <pre><code>
  123. * foo.on({
  124. * 'click' : {
  125. * fn : this.onClick,
  126. * scope : this,
  127. * delay : 100
  128. * },
  129. * 'mouseover' : {
  130. * fn : this.onMouseOver,
  131. * scope : this
  132. * },
  133. * 'mouseout' : {
  134. * fn : this.onMouseOut,
  135. * scope : this
  136. * }
  137. * });
  138. * </code></pre>
  139. *
  140. * <p>
  141. * Or a shorthand syntax:<br>
  142. *
  143. * <pre><code>
  144. * foo.on({
  145. * 'click' : this.onClick,
  146. * 'mouseover' : this.onMouseOver,
  147. * 'mouseout' : this.onMouseOut,
  148. * scope : this
  149. * });
  150. * </code></pre>
  151. */
  152. addListener : function(eventName, fn, scope, o) {
  153. if (typeof eventName == "object") {
  154. o = eventName;
  155. for (var e in o) {
  156. if (this.filterOptRe.test(e)) {
  157. continue;
  158. }
  159. if (typeof o[e] == "function") {
  160. // shared options
  161. this.addListener(e, o[e], o.scope, o);
  162. } else {
  163. // individual options
  164. this.addListener(e, o[e].fn, o[e].scope, o[e]);
  165. }
  166. }
  167. return;
  168. }
  169. o = (!o || typeof o == "boolean") ? {} : o;
  170. eventName = eventName.toLowerCase();
  171. var ce = this.events[eventName] || true;
  172. if (typeof ce == "boolean") {
  173. ce = new Ext.util.Event(this, eventName);
  174. this.events[eventName] = ce;
  175. }
  176. ce.addListener(fn, scope, o);
  177. },
  178. /**
  179. * Removes a listener
  180. *
  181. * @param {String}
  182. * eventName The type of event to listen for
  183. * @param {Function}
  184. * handler The handler to remove
  185. * @param {Object}
  186. * scope (optional) The scope (this object) for the handler
  187. */
  188. removeListener : function(eventName, fn, scope) {
  189. var ce = this.events[eventName.toLowerCase()];
  190. if (typeof ce == "object") {
  191. ce.removeListener(fn, scope);
  192. }
  193. },
  194. /**
  195. * Removes all listeners for this object
  196. */
  197. purgeListeners : function() {
  198. for (var evt in this.events) {
  199. if (typeof this.events[evt] == "object") {
  200. this.events[evt].clearListeners();
  201. }
  202. }
  203. },
  204. relayEvents : function(o, events) {
  205. var createHandler = function(ename) {
  206. return function() {
  207. return this.fireEvent.apply(this, Ext.combine(ename,
  208. Array.prototype.slice.call(arguments, 0)));
  209. };
  210. };
  211. for (var i = 0, len = events.length; i < len; i++) {
  212. var ename = events[i];
  213. if (!this.events[ename]) {
  214. this.events[ename] = true;
  215. };
  216. o.on(ename, createHandler(ename), this);
  217. }
  218. },
  219. /**
  220. * Used to define events on this Observable
  221. *
  222. * @param {Object}
  223. * object The object with the events defined
  224. */
  225. addEvents : function(o) {
  226. if (!this.events) {
  227. this.events = {};
  228. }
  229. if (typeof o == 'string') {
  230. for (var i = 0, a = arguments, v; v = a[i]; i++) {
  231. if (!this.events[a[i]]) {
  232. o[a[i]] = true;
  233. }
  234. }
  235. } else {
  236. Ext.applyIf(this.events, o);
  237. }
  238. },
  239. /**
  240. * Checks to see if this object has any listeners for a specified event
  241. *
  242. * @param {String}
  243. * eventName The name of the event to check for
  244. * @return {Boolean} True if the event is being listened for, else false
  245. */
  246. hasListener : function(eventName) {
  247. var e = this.events[eventName];
  248. return typeof e == "object" && e.listeners.length > 0;
  249. },
  250. /**
  251. * Suspend the firing of all events. (see {@link #resumeEvents})
  252. */
  253. suspendEvents : function() {
  254. this.eventsSuspended = true;
  255. },
  256. /**
  257. * Resume firing events. (see {@link #suspendEvents})
  258. */
  259. resumeEvents : function() {
  260. this.eventsSuspended = false;
  261. },
  262. // these are considered experimental
  263. // allows for easier interceptor and sequences, including cancelling and
  264. // overwriting the return value of the call
  265. // private
  266. getMethodEvent : function(method) {
  267. if (!this.methodEvents) {
  268. this.methodEvents = {};
  269. }
  270. var e = this.methodEvents[method];
  271. if (!e) {
  272. e = {};
  273. this.methodEvents[method] = e;
  274. e.originalFn = this[method];
  275. e.methodName = method;
  276. e.before = [];
  277. e.after = [];
  278. var returnValue, v, cancel;
  279. var obj = this;
  280. var makeCall = function(fn, scope, args) {
  281. if ((v = fn.apply(scope || obj, args)) !== undefined) {
  282. if (typeof v === 'object') {
  283. if (v.returnValue !== undefined) {
  284. returnValue = v.returnValue;
  285. } else {
  286. returnValue = v;
  287. }
  288. if (v.cancel === true) {
  289. cancel = true;
  290. }
  291. } else if (v === false) {
  292. cancel = true;
  293. } else {
  294. returnValue = v;
  295. }
  296. }
  297. }
  298. this[method] = function() {
  299. returnValue = v = undefined;
  300. cancel = false;
  301. var args = Array.prototype.slice.call(arguments, 0);
  302. for (var i = 0, len = e.before.length; i < len; i++) {
  303. makeCall(e.before[i].fn, e.before[i].scope, args);
  304. if (cancel) {
  305. return returnValue;
  306. }
  307. }
  308. if ((v = e.originalFn.apply(obj, args)) !== undefined) {
  309. returnValue = v;
  310. }
  311. for (var i = 0, len = e.after.length; i < len; i++) {
  312. makeCall(e.after[i].fn, e.after[i].scope, args);
  313. if (cancel) {
  314. return returnValue;
  315. }
  316. }
  317. return returnValue;
  318. };
  319. }
  320. return e;
  321. },
  322. // adds an "interceptor" called before the original method
  323. beforeMethod : function(method, fn, scope) {
  324. var e = this.getMethodEvent(method);
  325. e.before.push({
  326. fn : fn,
  327. scope : scope
  328. });
  329. },
  330. // adds a "sequence" called after the original method
  331. afterMethod : function(method, fn, scope) {
  332. var e = this.getMethodEvent(method);
  333. e.after.push({
  334. fn : fn,
  335. scope : scope
  336. });
  337. },
  338. removeMethodListener : function(method, fn, scope) {
  339. var e = this.getMethodEvent(method);
  340. for (var i = 0, len = e.before.length; i < len; i++) {
  341. if (e.before[i].fn == fn && e.before[i].scope == scope) {
  342. e.before.splice(i, 1);
  343. return;
  344. }
  345. }
  346. for (var i = 0, len = e.after.length; i < len; i++) {
  347. if (e.after[i].fn == fn && e.after[i].scope == scope) {
  348. e.after.splice(i, 1);
  349. return;
  350. }
  351. }
  352. }
  353. };
  354. /**
  355. * Appends an event handler to this element (shorthand for addListener)
  356. *
  357. * @param {String}
  358. * eventName The type of event to listen for
  359. * @param {Function}
  360. * handler The method the event invokes
  361. * @param {Object}
  362. * scope (optional) The scope in which to execute the handler
  363. * function. The handler function's "this" context.
  364. * @param {Object}
  365. * options (optional)
  366. * @method
  367. */
  368. Ext.util.Observable.prototype.on = Ext.util.Observable.prototype.addListener;
  369. /**
  370. * Removes a listener (shorthand for removeListener)
  371. *
  372. * @param {String}
  373. * eventName The type of event to listen for
  374. * @param {Function}
  375. * handler The handler to remove
  376. * @param {Object}
  377. * scope (optional) The scope (this object) for the handler
  378. * @method
  379. */
  380. Ext.util.Observable.prototype.un = Ext.util.Observable.prototype.removeListener;
  381. /**
  382. * Starts capture on the specified Observable. All events will be passed to the
  383. * supplied function with the event name + standard signature of the event
  384. * <b>before</b> the event is fired. If the supplied function returns false,
  385. * the event will not fire.
  386. *
  387. * @param {Observable}
  388. * o The Observable to capture
  389. * @param {Function}
  390. * fn The function to call
  391. * @param {Object}
  392. * scope (optional) The scope (this object) for the fn
  393. * @static
  394. */
  395. Ext.util.Observable.capture = function(o, fn, scope) {
  396. o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
  397. };
  398. /**
  399. * Removes <b>all</b> added captures from the Observable.
  400. *
  401. * @param {Observable}
  402. * o The Observable to release
  403. * @static
  404. */
  405. Ext.util.Observable.releaseCapture = function(o) {
  406. o.fireEvent = Ext.util.Observable.prototype.fireEvent;
  407. };
  408. (function() {
  409. var createBuffered = function(h, o, scope) {
  410. var task = new Ext.util.DelayedTask();
  411. return function() {
  412. task.delay(o.buffer, h, scope, Array.prototype.slice.call(
  413. arguments, 0));
  414. };
  415. };
  416. var createSingle = function(h, e, fn, scope) {
  417. return function() {
  418. e.removeListener(fn, scope);
  419. return h.apply(scope, arguments);
  420. };
  421. };
  422. var createDelayed = function(h, o, scope) {
  423. return function() {
  424. var args = Array.prototype.slice.call(arguments, 0);
  425. setTimeout(function() {
  426. h.apply(scope, args);
  427. }, o.delay || 10);
  428. };
  429. };
  430. Ext.util.Event = function(obj, name) {
  431. this.name = name;
  432. this.obj = obj;
  433. this.listeners = [];
  434. };
  435. Ext.util.Event.prototype = {
  436. addListener : function(fn, scope, options) {
  437. scope = scope || this.obj;
  438. if (!this.isListening(fn, scope)) {
  439. var l = this.createListener(fn, scope, options);
  440. if (!this.firing) {
  441. this.listeners.push(l);
  442. } else { // if we are currently firing this event, don't
  443. // disturb the listener loop
  444. this.listeners = this.listeners.slice(0);
  445. this.listeners.push(l);
  446. }
  447. }
  448. },
  449. createListener : function(fn, scope, o) {
  450. o = o || {};
  451. scope = scope || this.obj;
  452. var l = {
  453. fn : fn,
  454. scope : scope,
  455. options : o
  456. };
  457. var h = fn;
  458. if (o.delay) {
  459. h = createDelayed(h, o, scope);
  460. }
  461. if (o.single) {
  462. h = createSingle(h, this, fn, scope);
  463. }
  464. if (o.buffer) {
  465. h = createBuffered(h, o, scope);
  466. }
  467. l.fireFn = h;
  468. return l;
  469. },
  470. findListener : function(fn, scope) {
  471. scope = scope || this.obj;
  472. var ls = this.listeners;
  473. for (var i = 0, len = ls.length; i < len; i++) {
  474. var l = ls[i];
  475. if (l.fn == fn && l.scope == scope) {
  476. return i;
  477. }
  478. }
  479. return -1;
  480. },
  481. isListening : function(fn, scope) {
  482. return this.findListener(fn, scope) != -1;
  483. },
  484. removeListener : function(fn, scope) {
  485. var index;
  486. if ((index = this.findListener(fn, scope)) != -1) {
  487. if (!this.firing) {
  488. this.listeners.splice(index, 1);
  489. } else {
  490. this.listeners = this.listeners.slice(0);
  491. this.listeners.splice(index, 1);
  492. }
  493. return true;
  494. }
  495. return false;
  496. },
  497. clearListeners : function() {
  498. this.listeners = [];
  499. },
  500. fire : function() {
  501. var ls = this.listeners, scope, len = ls.length;
  502. if (len > 0) {
  503. this.firing = true;
  504. var args = Array.prototype.slice.call(arguments, 0);
  505. for (var i = 0; i < len; i++) {
  506. var l = ls[i];
  507. if (l.fireFn
  508. .apply(l.scope || this.obj || window, arguments) === false) {
  509. this.firing = false;
  510. return false;
  511. }
  512. }
  513. this.firing = false;
  514. }
  515. return true;
  516. }
  517. };
  518. })();