Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. /*
  2. * Copyright 2011, The gwtquery team.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  5. * in compliance with the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License
  10. * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  11. * or implied. See the License for the specific language governing permissions and limitations under
  12. * the License.
  13. */
  14. package com.google.gwt.query.client.plugins.events;
  15. import static com.google.gwt.query.client.GQuery.$;
  16. import com.google.gwt.core.client.Duration;
  17. import com.google.gwt.dom.client.Element;
  18. import com.google.gwt.dom.client.EventTarget;
  19. import com.google.gwt.dom.client.NodeList;
  20. import com.google.gwt.query.client.Function;
  21. import com.google.gwt.query.client.GQuery;
  22. import com.google.gwt.query.client.js.JsCache;
  23. import com.google.gwt.query.client.js.JsMap;
  24. import com.google.gwt.query.client.js.JsNamedArray;
  25. import com.google.gwt.query.client.js.JsObjectArray;
  26. import com.google.gwt.query.client.js.JsUtils;
  27. import com.google.gwt.query.client.plugins.events.SpecialEvent.DefaultSpecialEvent;
  28. import com.google.gwt.user.client.DOM;
  29. import com.google.gwt.user.client.Event;
  30. import com.google.gwt.user.client.EventListener;
  31. import java.util.ArrayList;
  32. import java.util.HashMap;
  33. import java.util.List;
  34. import java.util.Map;
  35. /**
  36. * This class implements an event queue instance for one Element. The queue instance is configured
  37. * as the default event listener in GWT.
  38. *
  39. * The reference to this queue is stored as a unique variable in the element's DOM.
  40. *
  41. * The class takes care of calling the appropriate functions for each browser event and it also
  42. * calls sinkEvents method.
  43. *
  44. */
  45. public class EventsListener implements EventListener {
  46. /**
  47. * Used for simulating mouseenter and mouseleave events.
  48. */
  49. private static class MouseSpecialEvent extends DefaultSpecialEvent {
  50. public MouseSpecialEvent(final String type, String delegateType) {
  51. super(type, delegateType);
  52. handler = new Function() {
  53. public boolean f(Event e, Object... arg) {
  54. EventTarget eventTarget = e.getCurrentEventTarget();
  55. Element target = eventTarget != null ? eventTarget.<Element> cast() : null;
  56. EventTarget relatedEventTarget = e.getRelatedEventTarget();
  57. Element related = relatedEventTarget != null ? relatedEventTarget.<Element> cast() : null;
  58. if (related == null || (related != target && !GQuery.contains(target, related))) {
  59. getInstance(target).dispatchEvent(e, type);
  60. }
  61. return true;
  62. };
  63. };
  64. }
  65. }
  66. /**
  67. * Utility class to split a list of events with or without namespaces.
  68. */
  69. public static class EventName {
  70. public final String nameSpace;
  71. public final String eventName;
  72. public EventName(String n, String e) {
  73. nameSpace = n;
  74. eventName = e;
  75. }
  76. public static List<EventName> split(String events) {
  77. List<EventName> ret = new ArrayList<EventName>();
  78. String[] parts = events.split("[\\s,]+");
  79. for (String event : parts) {
  80. String[] tmp = event.split("\\.", 2);
  81. String eventName = tmp[0];
  82. String nameSpace = tmp.length > 1 ? tmp[1] : null;
  83. ret.add(new EventName(nameSpace, eventName));
  84. }
  85. return ret;
  86. }
  87. }
  88. /**
  89. * The function used per each element event.
  90. */
  91. private static class BindFunction {
  92. Object data;
  93. Function function;
  94. String nameSpace;
  95. int times;
  96. int type;
  97. String eventName;
  98. BindFunction(int type, String eventName, String nameSpace, Function function, Object data,
  99. int times) {
  100. this.times = times;
  101. this.eventName = eventName;
  102. this.type = type;
  103. this.function = function;
  104. this.data = data;
  105. this.nameSpace = nameSpace != null ? nameSpace : "";
  106. }
  107. public boolean fire(Event event, Object[] eventData) {
  108. return fire(event, event.getTypeInt(), event.getType(), eventData);
  109. }
  110. public boolean fire(Event event, int typeInt, String type, Object[] eventData) {
  111. if (times != 0) {
  112. times--;
  113. Object[] arguments;
  114. eventData = eventData != null ? eventData : new Object[0];
  115. // The argument of the function will be first the data attached to the handler then the
  116. // data attached to the event.
  117. if (data != null) {
  118. Object[] handlerData = data.getClass().isArray() ? (Object[]) data : new Object[]{data};
  119. arguments = new Object[handlerData.length + eventData.length];
  120. System.arraycopy(handlerData, 0, arguments, 0, handlerData.length);
  121. System.arraycopy(eventData, 0, arguments, handlerData.length, eventData.length);
  122. } else {
  123. arguments = eventData;
  124. }
  125. // FIXME(manolo): figure out when this is null, and fix or comment it.
  126. if (function != null) {
  127. return function.fe(event, arguments);
  128. }
  129. }
  130. return true;
  131. }
  132. public boolean hasEventType(int etype) {
  133. return type != BITLESS && etype != BITLESS && (type & etype) != 0;
  134. }
  135. public boolean isTypeOf(String eName) {
  136. return eventName != null && eventName.equalsIgnoreCase(eName);
  137. }
  138. /**
  139. * Remove a set of events. The bind function will not be fire anymore for those events
  140. *
  141. * @param eventBits the set of events to unsink
  142. *
  143. */
  144. public int unsink(int eventBits) {
  145. if (eventBits <= 0) {
  146. type = 0;
  147. } else {
  148. type = type & ~eventBits;
  149. }
  150. return type;
  151. }
  152. @Override
  153. public String toString() {
  154. return "bind function for event type " + (eventName != null ? eventName : "" + type);
  155. }
  156. public boolean isEquals(Function f) {
  157. assert f != null : "function f cannot be null";
  158. return f.equals(function);
  159. }
  160. }
  161. /**
  162. * {@link BindFunction} used for live() method.
  163. *
  164. */
  165. private static class LiveBindFunction extends BindFunction {
  166. JsNamedArray<JsObjectArray<BindFunction>> bindFunctionBySelector;
  167. LiveBindFunction(String eventName, String namespace, Object data) {
  168. super(BITLESS, eventName, namespace, null, data, -1);
  169. clean();
  170. }
  171. LiveBindFunction(int type, String namespace, Object data) {
  172. super(type, null, namespace, null, data, -1);
  173. clean();
  174. }
  175. /**
  176. * Add a {@link BindFunction} for a specific css selector.
  177. */
  178. public void addBindFunctionForSelector(String cssSelector, BindFunction f) {
  179. JsObjectArray<BindFunction> bindFunctions = bindFunctionBySelector.get(cssSelector);
  180. if (bindFunctions == null) {
  181. bindFunctions = JsObjectArray.create();
  182. bindFunctionBySelector.put(cssSelector, bindFunctions);
  183. }
  184. bindFunctions.add(f);
  185. }
  186. public void clean() {
  187. bindFunctionBySelector = JsNamedArray.create();
  188. }
  189. @Override
  190. public boolean fire(Event event, int typeInt, String type, Object[] eventData) {
  191. if (isEmpty()) {
  192. return true;
  193. }
  194. // first element where the event was fired
  195. Element eventTarget = getEventTarget(event);
  196. // last element where the event was dispatched on
  197. Element liveContextElement = getCurrentEventTarget(event);
  198. if (eventTarget == null || liveContextElement == null) {
  199. return true;
  200. }
  201. // Compute the live selectors which respond to this event type
  202. List<String> validSelectors = new ArrayList<String>();
  203. for (String cssSelector : bindFunctionBySelector.keys()) {
  204. JsObjectArray<BindFunction> bindFunctions = bindFunctionBySelector.get(cssSelector);
  205. for (int i = 0; bindFunctions != null && i < bindFunctions.length(); i++) {
  206. BindFunction f = bindFunctions.get(i);
  207. if (f.hasEventType(typeInt) || f.isTypeOf(type)) {
  208. validSelectors.add(cssSelector);
  209. break;
  210. }
  211. }
  212. }
  213. // Create a structure of elements which matches the selectors
  214. JsNamedArray<NodeList<Element>> realCurrentTargetBySelector =
  215. $(eventTarget).closest(validSelectors.toArray(new String[0]), liveContextElement);
  216. // nothing matches the selectors
  217. if (realCurrentTargetBySelector.length() == 0) {
  218. return true;
  219. }
  220. Element stopElement = null;
  221. GqEvent gqEvent = GqEvent.create(event);
  222. for (String cssSelector : realCurrentTargetBySelector.keys()) {
  223. JsObjectArray<BindFunction> bindFunctions = bindFunctionBySelector.get(cssSelector);
  224. for (int i = 0; bindFunctions != null && i < bindFunctions.length(); i++) {
  225. BindFunction f = bindFunctions.get(i);
  226. if (f.hasEventType(typeInt) || f.isTypeOf(type)) {
  227. NodeList<Element> n = realCurrentTargetBySelector.get(cssSelector);
  228. for (int j = 0; n != null && j < n.getLength(); j++) {
  229. Element element = n.getItem(j);
  230. // When an event fired in an element stops bubbling we have to fire also all other
  231. // handlers for this element bound to this element
  232. if (stopElement == null || element.equals(stopElement)) {
  233. gqEvent.setCurrentElementTarget(element);
  234. // data
  235. eventData = $(element).data(EVENT_DATA);
  236. if (!f.fire(gqEvent, eventData)) {
  237. stopElement = element;
  238. }
  239. }
  240. }
  241. }
  242. }
  243. }
  244. // trick to reset the right currentTarget on the original event on ie
  245. gqEvent.setCurrentElementTarget(liveContextElement);
  246. return stopElement == null;
  247. }
  248. /**
  249. * Remove the BindFunction associated to this cssSelector.
  250. */
  251. public void removeBindFunctionForSelector(String cssSelector, String nameSpace) {
  252. if (nameSpace == null) {
  253. bindFunctionBySelector.delete(cssSelector);
  254. } else {
  255. JsObjectArray<BindFunction> functions = bindFunctionBySelector.get(cssSelector);
  256. if (functions == null || functions.length() == 0) {
  257. return;
  258. }
  259. JsObjectArray<BindFunction> newFunctions = JsObjectArray.create();
  260. for (int i = 0; i < functions.length(); i++) {
  261. BindFunction f = functions.get(i);
  262. if (nameSpace != null && !nameSpace.equals(f.nameSpace)) {
  263. newFunctions.add(f);
  264. }
  265. }
  266. bindFunctionBySelector.delete(cssSelector);
  267. if (newFunctions.length() > 0) {
  268. bindFunctionBySelector.put(cssSelector, newFunctions);
  269. }
  270. }
  271. }
  272. /**
  273. * Tell if no {@link BindFunction} are linked to this object.
  274. *
  275. * @return
  276. */
  277. public boolean isEmpty() {
  278. return bindFunctionBySelector.length() == 0;
  279. }
  280. @Override
  281. public String toString() {
  282. return "live bind function for selector "
  283. + bindFunctionBySelector.<JsCache> cast().tostring();
  284. }
  285. /**
  286. * Return the element whose the listener fired last. It represent the context element where the
  287. * {@link LiveBindFunction} was binded.
  288. *
  289. */
  290. private Element getCurrentEventTarget(Event e) {
  291. EventTarget currentEventTarget = e.getCurrentEventTarget();
  292. if (!Element.is(currentEventTarget)) {
  293. return null;
  294. }
  295. return Element.as(currentEventTarget);
  296. }
  297. /**
  298. * Return the element that was the actual target of the element.
  299. */
  300. private Element getEventTarget(Event e) {
  301. EventTarget eventTarget = e.getEventTarget();
  302. if (!Element.is(eventTarget)) {
  303. return null;
  304. }
  305. return Element.as(eventTarget);
  306. }
  307. }
  308. public static final String EVENT_DATA = "___event_datas";
  309. public static final int BITLESS = -1;
  310. public static String MOUSEENTER = "mouseenter";
  311. public static String MOUSELEAVE = "mouseleave";
  312. public static Map<String, SpecialEvent> special;
  313. static {
  314. // Register some special events which already exist in jQuery
  315. special = new HashMap<String, SpecialEvent>();
  316. special.put(MOUSEENTER, new MouseSpecialEvent(MOUSEENTER, "mouseover"));
  317. special.put(MOUSELEAVE, new MouseSpecialEvent(MOUSELEAVE, "mouseout"));
  318. }
  319. public static void clean(Element e) {
  320. EventsListener ret = getGQueryEventListener(e);
  321. if (ret != null) {
  322. ret.clean();
  323. }
  324. }
  325. public static EventsListener getInstance(Element e) {
  326. EventsListener ret = getGQueryEventListener(e);
  327. return ret != null ? ret : new EventsListener(e);
  328. }
  329. /**
  330. * We have to set the gQuery event listener to the element again when
  331. * the element is a widget, because when GWT detaches a widget it removes the
  332. * event listener.
  333. */
  334. public static void rebind(Element e) {
  335. EventsListener ret = getGQueryEventListener(e);
  336. if (ret != null) {
  337. DOM.setEventListener((com.google.gwt.user.client.Element) e, ret);
  338. }
  339. }
  340. private static native void cleanGQListeners(Element elem) /*-{
  341. if (elem.__gwtlistener) {
  342. @com.google.gwt.user.client.DOM::setEventListener(*)(elem, elem.__gwtlistener);
  343. }
  344. elem.__gwtlistener = elem.__gqueryevent = elem.__gquery = null;
  345. }-*/;
  346. private static native EventsListener getGQueryEventListener(Element elem) /*-{
  347. return elem.__gqueryevent;
  348. }-*/;
  349. private static native EventListener getGwtEventListener(Element elem) /*-{
  350. return elem.__gwtlistener;
  351. }-*/;
  352. private static native void init(Element elem, EventsListener gqevent) /*-{
  353. elem.__gwtlistener = @com.google.gwt.user.client.DOM::getEventListener(*)(elem);
  354. elem.__gqueryevent = gqevent;
  355. // Someone has reported that in IE the init can be called multiple times
  356. // causing a loop. We need some test to demonstrate this behaviour though.
  357. // Anyway we check this condition to avoid looping
  358. if (elem.__gwtlistener == gqevent) elem.__gwtlistener = null;
  359. }-*/;
  360. private static native void sinkBitlessEvent(Element elem, String name) /*-{
  361. if (!elem.__gquery)
  362. elem.__gquery = [];
  363. if (elem.__gquery[name])
  364. return;
  365. elem.__gquery[name] = true;
  366. var handle = function(event) {
  367. elem.__gqueryevent.@com.google.gwt.query.client.plugins.events.EventsListener::onBrowserEvent(Lcom/google/gwt/user/client/Event;)(event);
  368. };
  369. if (elem.addEventListener)
  370. elem.addEventListener(name, handle, true);
  371. else
  372. elem.attachEvent("on" + name, handle);
  373. }-*/;
  374. int eventBits = 0;
  375. double lastEvnt = 0;
  376. String lastType = "";
  377. private Element element;
  378. private JsObjectArray<BindFunction> elementEvents = JsObjectArray.createArray().cast();
  379. private JsMap<Integer, LiveBindFunction> liveBindFunctionByEventType = JsMap.create();
  380. private JsMap<String, LiveBindFunction> liveBindFunctionByEventName = JsMap.create();
  381. private EventsListener(Element element) {
  382. this.element = element;
  383. init(element, this);
  384. }
  385. public void bind(int eventbits, final Object data, Function... funcs) {
  386. bind(eventbits, null, data, funcs);
  387. }
  388. public void bind(int eventbits, final Object data, final Function function, int times) {
  389. bind(eventbits, null, null, data, function, times);
  390. }
  391. public void bind(int eventbits, String name, final Object data, Function... funcs) {
  392. for (Function function : funcs) {
  393. bind(eventbits, name, null, data, function, -1);
  394. }
  395. }
  396. public void bind(int eventbits, String namespace, Object data, Function function, int times) {
  397. bind(eventbits, namespace, null, data, function, times);
  398. }
  399. public void bind(String events, final Object data, Function... funcs) {
  400. if (funcs.length == 0 || funcs[0] == null) {
  401. unbind(events, null);
  402. }
  403. for (EventName ev : EventName.split(events)) {
  404. SpecialEvent hook = special.get(ev.eventName);
  405. boolean bind = hook == null || hook.setup(element) == false;
  406. for (Function function : funcs) {
  407. int b = Event.getTypeInt(ev.eventName);
  408. if (bind) {
  409. bind(b, ev.nameSpace, ev.eventName, data, function, -1);
  410. }
  411. if (hook != null) {
  412. hook.add(element, ev.eventName, ev.nameSpace, data, function);
  413. }
  414. }
  415. }
  416. }
  417. public void bind(int eventbits, String namespace, String eventName, Object data, Function function, int times) {
  418. sink(eventbits, eventName);
  419. elementEvents.add(new BindFunction(eventbits, eventName, namespace, function, data, times));
  420. }
  421. public void die(String events, String cssSelector) {
  422. for (EventName ev : EventName.split(events)) {
  423. SpecialEvent hook = special.get(ev.eventName);
  424. boolean unbind = hook == null || hook.tearDown(element) == false;
  425. if (unbind) {
  426. die(Event.getTypeInt(ev.eventName), ev.nameSpace, ev.eventName, cssSelector);
  427. }
  428. if (hook != null) {
  429. hook.remove(element, ev.eventName, ev.nameSpace, null);
  430. }
  431. }
  432. }
  433. public void die(int eventbits, String nameSpace, String eventName, String cssSelector) {
  434. if (eventbits <= 0) {
  435. if (eventName != null && eventName.length() > 0) {
  436. LiveBindFunction liveBindFunction = liveBindFunctionByEventName.get(eventName);
  437. maybeRemoveLiveBindFunction(liveBindFunction, cssSelector, BITLESS, eventName, nameSpace);
  438. } else {
  439. // if eventbits == -1 and eventName is null, remove all event handlers for this selector
  440. for (String k : liveBindFunctionByEventType.keys()) {
  441. int bits = Integer.parseInt(k);
  442. LiveBindFunction liveBindFunction = liveBindFunctionByEventType.get(bits);
  443. maybeRemoveLiveBindFunction(liveBindFunction, cssSelector, bits, null, nameSpace);
  444. }
  445. for (String k : liveBindFunctionByEventName.keys()) {
  446. int realKey = Integer.parseInt(k);
  447. LiveBindFunction liveBindFunction = liveBindFunctionByEventName.get(realKey);
  448. if (liveBindFunction != null) {
  449. String eName = liveBindFunction.eventName;
  450. maybeRemoveLiveBindFunction(liveBindFunction, cssSelector, BITLESS, eName, nameSpace);
  451. }
  452. }
  453. }
  454. } else {
  455. LiveBindFunction liveBindFunction = liveBindFunctionByEventType.get(eventbits);
  456. maybeRemoveLiveBindFunction(liveBindFunction, cssSelector, eventbits, null, nameSpace);
  457. }
  458. }
  459. private void maybeRemoveLiveBindFunction(LiveBindFunction liveBindFunction, String cssSelector,
  460. int eventbits, String eventName, String nameSpace) {
  461. if (liveBindFunction != null) {
  462. liveBindFunction.removeBindFunctionForSelector(cssSelector, nameSpace);
  463. if (liveBindFunction.isEmpty()) {
  464. if (eventbits != BITLESS) {
  465. liveBindFunctionByEventType.remove(eventbits);
  466. } else {
  467. liveBindFunctionByEventName.remove(eventName);
  468. }
  469. }
  470. }
  471. }
  472. /**
  473. * Dispatch an event in this element.
  474. */
  475. public void dispatchEvent(Event event) {
  476. dispatchEvent(event, event.getType());
  477. }
  478. /**
  479. * Dispatch an event in this element but changing the type,
  480. * it's useful for special events.
  481. */
  482. public void dispatchEvent(Event event, String eventName) {
  483. int typeInt = Event.getTypeInt(eventName);
  484. Object[] handlerData = $(element).data(EVENT_DATA);
  485. for (int i = 0, l = elementEvents.length(); i < l; i++) {
  486. BindFunction listener = elementEvents.get(i);
  487. String namespace = JsUtils.prop(event, "namespace");
  488. boolean matchEV = listener != null && (listener.hasEventType(typeInt) || listener.isTypeOf(eventName));
  489. boolean matchNS = matchEV && (isNullOrEmpty(namespace) || listener.nameSpace.equals(namespace));
  490. if (matchEV && matchNS) {
  491. if (!listener.fire(event, typeInt, eventName, handlerData)) {
  492. event.stopPropagation();
  493. event.preventDefault();
  494. }
  495. }
  496. }
  497. }
  498. /**
  499. * Return the original gwt EventListener associated with this element, before gquery replaced it
  500. * to introduce its own event handler.
  501. */
  502. public EventListener getOriginalEventListener() {
  503. return getGwtEventListener(element);
  504. }
  505. public void live(String events, String cssSelector, Object data, Function... funcs) {
  506. for (EventName ev : EventName.split(events)) {
  507. SpecialEvent hook = special.get(ev.eventName);
  508. boolean bind = hook == null || hook.setup(element) == false;
  509. for (Function function : funcs) {
  510. int b = Event.getTypeInt(ev.eventName);
  511. if (bind) {
  512. live(b, ev.nameSpace, ev.eventName, cssSelector, data, function);
  513. }
  514. if (hook != null) {
  515. hook.add(element, ev.eventName, ev.nameSpace, data, function);
  516. }
  517. }
  518. }
  519. }
  520. public void live(int eventbits, String nameSpace, String eventName, String cssSelector,
  521. Object data, Function... funcs) {
  522. if (eventbits != BITLESS) {
  523. liveBitEvent(eventbits, nameSpace, cssSelector, data, funcs);
  524. } else {
  525. liveBitlessEvent(eventName, nameSpace, cssSelector, data, funcs);
  526. }
  527. }
  528. private void liveBitlessEvent(String eventName, String nameSpace, String cssSelector,
  529. Object data, Function... funcs) {
  530. LiveBindFunction liveBindFunction = liveBindFunctionByEventName.get(eventName);
  531. if (liveBindFunction == null) {
  532. liveBindFunction = new LiveBindFunction(eventName, "live", data);
  533. sink(BITLESS, eventName);
  534. elementEvents.add(liveBindFunction);
  535. liveBindFunctionByEventName.put(eventName, liveBindFunction);
  536. }
  537. for (Function f : funcs) {
  538. liveBindFunction.addBindFunctionForSelector(cssSelector, new BindFunction(BITLESS, eventName,
  539. nameSpace, f, data, -1));
  540. }
  541. }
  542. private void liveBitEvent(int eventbits, String nameSpace, String cssSelector, Object data,
  543. Function... funcs) {
  544. for (int i = 0; i < 28; i++) {
  545. int event = (int) Math.pow(2, i);
  546. if ((eventbits & event) == event) {
  547. // is a LiveBindFunction already attached for this kind of event
  548. LiveBindFunction liveBindFunction = liveBindFunctionByEventType.get(event);
  549. if (liveBindFunction == null) {
  550. liveBindFunction = new LiveBindFunction(event, "live", data);
  551. sink(eventbits, null);
  552. elementEvents.add(liveBindFunction);
  553. liveBindFunctionByEventType.put(event, liveBindFunction);
  554. }
  555. for (Function f : funcs) {
  556. liveBindFunction.addBindFunctionForSelector(cssSelector, new BindFunction(event,
  557. null, nameSpace, f, data, -1));
  558. }
  559. }
  560. }
  561. }
  562. public void onBrowserEvent(Event event) {
  563. if (JsUtils.isDefaultPrevented(event)) {
  564. return;
  565. }
  566. double now = Duration.currentTimeMillis();
  567. // Workaround for Issue_20
  568. if (lastType.equals(event.getType()) && now - lastEvnt < 10
  569. && "body".equalsIgnoreCase(element.getTagName())) {
  570. return;
  571. }
  572. lastEvnt = now;
  573. lastType = event.getType();
  574. // Execute the original Gwt listener
  575. if (getOriginalEventListener() != null && getOriginalEventListener() != this) {
  576. getOriginalEventListener().onBrowserEvent(event);
  577. }
  578. dispatchEvent(event);
  579. }
  580. public void unbind(int eventbits) {
  581. unbind(eventbits, null, null, null);
  582. }
  583. public void unbind(int eventbits, String namespace, String eventName, Function f) {
  584. JsObjectArray<BindFunction> newList = JsObjectArray.createArray().cast();
  585. for (int i = 0; i < elementEvents.length(); i++) {
  586. BindFunction listener = elementEvents.get(i);
  587. boolean matchNS = isNullOrEmpty(namespace) || listener.nameSpace.equals(namespace);
  588. boolean matchEV = eventbits <= 0 || listener.hasEventType(eventbits);
  589. boolean matchEVN = matchEV || listener.isTypeOf(eventName);
  590. boolean matchFC = f == null || listener.isEquals(f);
  591. if (matchNS && matchEV && matchEVN && matchFC) {
  592. int currentEventbits = listener.unsink(eventbits);
  593. if (currentEventbits == 0) {
  594. // the BindFunction doesn't listen anymore on any events
  595. continue;
  596. }
  597. }
  598. newList.add(listener);
  599. }
  600. elementEvents = newList;
  601. }
  602. /**
  603. * Return true if the element is listening for the
  604. * given eventBit or eventName.
  605. */
  606. public boolean hasHandlers(int eventBits, String eventName) {
  607. return hasHandlers(eventBits, eventName, null);
  608. }
  609. /**
  610. * Return true if the element is listening for the
  611. * given eventBit or eventName and the handler matches.
  612. */
  613. public boolean hasHandlers(int eventBits, String eventName, Function handler) {
  614. for (int i = 0, j = elementEvents.length(); i < j; i++) {
  615. BindFunction function = elementEvents.get(i);
  616. if ((function.hasEventType(eventBits) || function.isTypeOf(eventName))
  617. && (handler == null || function.isEquals(handler))) {
  618. return true;
  619. }
  620. }
  621. return false;
  622. }
  623. private static boolean isNullOrEmpty(String s) {
  624. return s == null || s.isEmpty();
  625. }
  626. public void unbind(String events, Function f) {
  627. for (EventName ev : EventName.split(events)) {
  628. SpecialEvent hook = special.get(ev.eventName);
  629. boolean unbind = hook == null || hook.tearDown(element) == false;
  630. if (unbind) {
  631. unbind(Event.getTypeInt(ev.eventName), ev.nameSpace, ev.eventName, f);
  632. }
  633. if (hook != null) {
  634. hook.remove(element, ev.eventName, ev.nameSpace, f);
  635. }
  636. }
  637. }
  638. public void clean() {
  639. cleanGQListeners(element);
  640. elementEvents = JsObjectArray.createArray().cast();
  641. liveBindFunctionByEventType = JsMap.create();
  642. eventBits = 0;
  643. }
  644. private void sink(int eventbits, String eventName) {
  645. // ensure that the gwtQuery's event listener is set as event listener of the element
  646. DOM.setEventListener((com.google.gwt.user.client.Element) element, this);
  647. if (eventbits != BITLESS) {
  648. eventBits |= eventbits;
  649. if ((eventBits | Event.FOCUSEVENTS) == Event.FOCUSEVENTS
  650. && JsUtils.isElement(element)
  651. && element.getAttribute("tabIndex").length() == 0) {
  652. element.setAttribute("tabIndex", "0");
  653. }
  654. DOM.sinkEvents((com.google.gwt.user.client.Element) element, eventBits
  655. | DOM.getEventsSunk((com.google.gwt.user.client.Element) element));
  656. } else {
  657. sinkBitlessEvent(element, eventName);
  658. }
  659. }
  660. public void cleanEventDelegation() {
  661. for (String k : liveBindFunctionByEventType.keys()) {
  662. LiveBindFunction function = liveBindFunctionByEventType.<JsCache> cast().get(k);
  663. function.clean();
  664. }
  665. }
  666. }