You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

EventsListener.java 28KB

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