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.

JsUtils.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. /*
  2. * Copyright 2011, The gwtquery team.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.google.gwt.query.client.js;
  17. import static com.google.gwt.query.client.GQuery.browser;
  18. import com.google.gwt.core.client.GWT;
  19. import com.google.gwt.core.client.JavaScriptObject;
  20. import com.google.gwt.core.client.JsArray;
  21. import com.google.gwt.core.client.JsArrayMixed;
  22. import com.google.gwt.core.client.JsonUtils;
  23. import com.google.gwt.dom.client.Document;
  24. import com.google.gwt.dom.client.Element;
  25. import com.google.gwt.dom.client.Node;
  26. import com.google.gwt.dom.client.NodeList;
  27. import com.google.gwt.query.client.Function;
  28. import com.google.gwt.query.client.GQuery;
  29. import com.google.gwt.query.client.Properties;
  30. import com.google.gwt.user.client.Command;
  31. import com.google.gwt.user.client.DOM;
  32. /**
  33. * A bunch of utility methods for GQuery.
  34. */
  35. public class JsUtils {
  36. /**
  37. * A Function which wraps a javascript function.
  38. */
  39. public static class JsFunction extends Function implements Command {
  40. private JavaScriptObject jso = null;
  41. public JsFunction(JavaScriptObject f) {
  42. if (JsUtils.isFunction(f)) {
  43. jso = f;
  44. }
  45. }
  46. public boolean equals(Object obj) {
  47. return jso.equals(obj);
  48. }
  49. public int hashCode() {
  50. return jso.hashCode();
  51. }
  52. private native Object exec(JavaScriptObject f, Object data) /*-{
  53. return @com.google.gwt.query.client.js.JsCache::gwtBox(*)([ f(data) ]);
  54. }-*/;
  55. public void f() {
  56. if (jso != null) {
  57. setArguments(exec(jso, arguments(0)));
  58. }
  59. }
  60. public void execute() {
  61. f();
  62. }
  63. }
  64. /**
  65. * Wraps a GQuery function into a native javascript one so as we can
  66. * export Java methods without using JSNI.
  67. */
  68. public static native JavaScriptObject wrapFunction(Function f) /*-{
  69. return function(r) {
  70. var o = @java.util.ArrayList::new()();
  71. for (i in arguments) {
  72. r = @com.google.gwt.query.client.js.JsCache::gwtBox(*)([arguments[i]]);
  73. o.@java.util.ArrayList::add(Ljava/lang/Object;)(r);
  74. }
  75. o = o.@java.util.ArrayList::toArray()();
  76. f.@com.google.gwt.query.client.Function::setArguments([Ljava/lang/Object;)(o);
  77. return f.@com.google.gwt.query.client.Function::fe([Ljava/lang/Object;)(o);
  78. }
  79. }-*/;
  80. /**
  81. * Default JsUtils implementation.
  82. */
  83. public static class JsUtilsImpl {
  84. public Properties parseJSON(String json) {
  85. return JsonUtils.safeEval(json);
  86. }
  87. public native String JSON2String(JavaScriptObject o) /*-{
  88. return $wnd.JSON.stringify(o);
  89. }-*/;
  90. public native Element parseXML(String xml) /*-{
  91. return new DOMParser().parseFromString(xml, "text/xml").documentElement;
  92. }-*/;
  93. public String text(Element e) {
  94. return e.getInnerText();
  95. }
  96. public JsArray<Element> unique(JsArray<Element> a) {
  97. JsArray<Element> ret = JavaScriptObject.createArray().cast();
  98. JsCache cache = JsCache.create();
  99. for (int i = 0; i < a.length(); i++) {
  100. Element e = a.get(i);
  101. int id = e.hashCode();
  102. if (!cache.exists(id)) {
  103. cache.putNumber(id, 1);
  104. ret.push(e);
  105. }
  106. }
  107. return ret;
  108. }
  109. public native String XML2String(JavaScriptObject o) /*-{
  110. return (new XMLSerializer()).serializeToString(o);
  111. }-*/;
  112. }
  113. /**
  114. * IE JsUtils implemetation.
  115. */
  116. public static class JsUtilsImplIE6 extends JsUtilsImpl {
  117. @Override
  118. public Properties parseJSON(String json) {
  119. return JsonUtils.unsafeEval(json);
  120. }
  121. @Override
  122. public String JSON2String(JavaScriptObject js) {
  123. // This is a very basic implementation for IE6/IE7 of JSON.stringify
  124. // If many people demand a better one we could consider to use json2.js
  125. // @see https://github.com/douglascrockford/JSON-js/blob/master/json2.js
  126. Properties prop = js.cast();
  127. String ret = "";
  128. for (String k : prop.keys()) {
  129. String ky = k.matches("\\d+") ? k : "\"" + k + "\"";
  130. JsCache o = prop.getArray(k).cast();
  131. if (o != null) {
  132. ret += ky + ":[";
  133. for (int i = 0, l = o.length(); i < l; i++) {
  134. Properties p = o.<JsCache> cast().getJavaScriptObject(i);
  135. if (p != null) {
  136. ret += p.toJsonString() + ",";
  137. } else {
  138. ret += "\"" + o.getString(i) + "\",";
  139. }
  140. }
  141. ret += "],";
  142. } else {
  143. Properties p = prop.getJavaScriptObject(k);
  144. if (p != null) {
  145. ret += ky + ":" + p.toJsonString() + ",";
  146. } else {
  147. ret += ky + ":\"" + prop.getStr(k) + "\",";
  148. }
  149. }
  150. }
  151. return "{" + ret.replaceAll(",\\s*([\\]}]|$)", "$1")
  152. .replaceAll("([:,\\[])\"(-?[\\d\\.]+|null|false|true)\"", "$1$2")
  153. + "}";
  154. }
  155. @Override
  156. public native Element parseXML(String xml) /*-{
  157. var d = new ActiveXObject("Microsoft.XmlDom");
  158. d.loadXML(xml);
  159. return d.documentElement;
  160. }-*/;
  161. @Override
  162. public String text(Element e) {
  163. return isXML(e) ? xmlText(e) : super.text(e);
  164. }
  165. @Override
  166. public JsArray<Element> unique(JsArray<Element> a) {
  167. // in IE6 XML elements does not support adding hashId to the object
  168. if (browser.ie6 && isXML(a.get(0))) {
  169. return a;
  170. }
  171. return super.unique(a);
  172. }
  173. @Override
  174. public native String XML2String(JavaScriptObject o) /*-{
  175. return o.xml;
  176. }-*/;
  177. private native String xmlText(Element e) /*-{
  178. return e.text;
  179. }-*/;
  180. }
  181. private static JsUtilsImpl utilsImpl = GWT.create(JsUtilsImpl.class);
  182. /**
  183. * Returns a property present in a javascript object.
  184. */
  185. public static <T> T prop(JavaScriptObject o, Object id, Class<? extends T> type) {
  186. return o == null ? null : o.<JsCache> cast().get(id, type);
  187. }
  188. /**
  189. * Returns a property present in a javascript object.
  190. */
  191. public static <T> T prop(JavaScriptObject o, Object id) {
  192. return o == null ? null : o.<JsCache> cast().<T> get(id);
  193. }
  194. /**
  195. * Set a property to a javascript object.
  196. */
  197. public static void prop(JavaScriptObject o, Object id, Object val) {
  198. if (o != null) {
  199. o.<JsCache> cast().put(id, val);
  200. }
  201. }
  202. /**
  203. * Camelize style property names.
  204. * for instance: font-name -> fontName
  205. */
  206. public static native String camelize(String s) /*-{
  207. return s.replace(/\-(\w)/g, function(all, letter) {
  208. return letter.toUpperCase();
  209. });
  210. }-*/;
  211. /**
  212. * Merge the oldNodes list into the newNodes one. If oldNodes is null, a new
  213. * list will be created and returned. If oldNodes is not null, a new list will
  214. * be created depending on the create flag.
  215. */
  216. public static NodeList<Element> copyNodeList(NodeList<Element> oldNodes,
  217. NodeList<Element> newNodes, boolean create) {
  218. NodeList<Element> ret = oldNodes == null || create ? JsNodeArray.create()
  219. : oldNodes;
  220. JsCache idlist = JsCache.create();
  221. for (int i = 0; oldNodes != null && i < oldNodes.getLength(); i++) {
  222. Element e = oldNodes.getItem(i);
  223. idlist.put(e.hashCode(), 1);
  224. if (create) {
  225. ret.<JsNodeArray> cast().addNode(e, i);
  226. }
  227. }
  228. for (int i = 0, l = newNodes.getLength(), j = ret.getLength(); i < l; i++) {
  229. Element e = newNodes.getItem(i);
  230. if (!idlist.exists(e.hashCode())) {
  231. ret.<JsNodeArray> cast().addNode(newNodes.getItem(i), j++);
  232. }
  233. }
  234. return ret;
  235. }
  236. /**
  237. * Use the method in the gquery class.
  238. * $(elem).cur(prop, force);
  239. */
  240. @Deprecated
  241. public static double cur(Element elem, String prop, boolean force) {
  242. return GQuery.$(elem).cur(prop, force);
  243. }
  244. /**
  245. * Compare two numbers using javascript equality.
  246. */
  247. public static native boolean eq(double s1, double s2) /*-{
  248. return s1 == s2;
  249. }-*/;
  250. /**
  251. * Compare two objects using javascript equality.
  252. */
  253. public static native boolean eq(Object s1, Object s2) /*-{
  254. return s1 == s2;
  255. }-*/;
  256. /**
  257. * Returns the owner document element of an element.
  258. */
  259. public static Document getOwnerDocument(Node n) {
  260. return n == null || !isElement(n) ? null : n.getNodeType() == Node.DOCUMENT_NODE
  261. ? n.<Document> cast() : n.getOwnerDocument();
  262. }
  263. /**
  264. * Check if an object has a property with <code>name</code> defined.
  265. * It supports dots in the name meaning checking nested properties.
  266. *
  267. * Example:
  268. * <pre>
  269. * // Check whether a browser supports touch events
  270. * hasProperty(window, "ontouchstart");
  271. * </pre>
  272. */
  273. public static native boolean hasProperty(JavaScriptObject o, String name)/*-{
  274. var p = name.split('.');
  275. for (var i in p) {
  276. if (!(o && p[i] in o)) return false;
  277. o = o[p[i]];
  278. }
  279. return true;
  280. }-*/;
  281. /**
  282. * Check whether an element has an attribute, this is here since GWT Element.getAttribute
  283. * implementation returns an empty string instead of null when the attribute is not
  284. * present.
  285. */
  286. public static native boolean hasAttribute(Element o, String name) /*-{
  287. return !!(o && o.getAttribute(name));
  288. }-*/;
  289. /**
  290. * Hyphenize style property names.
  291. * for instance: fontName -> font-name
  292. */
  293. public static native String hyphenize(String name) /*-{
  294. return name.replace(/([A-Z])/g, "-$1").toLowerCase();
  295. }-*/;
  296. /**
  297. * Check is a javascript object can be used as an array.
  298. */
  299. public static native boolean isArray(JavaScriptObject o) /*-{
  300. return Object.prototype.toString.call(o) == '[object Array]'
  301. || typeof o.length == 'number';
  302. }-*/;
  303. /**
  304. * Check is a javascript object is a FormData.
  305. */
  306. public static native boolean isFormData(JavaScriptObject o) /*-{
  307. return Object.prototype.toString.call(o) == '[object FormData]';
  308. }-*/;
  309. /**
  310. * Return whether the event was prevented.
  311. */
  312. public static native boolean isDefaultPrevented(JavaScriptObject e) /*-{
  313. return e.defaultPrevented || e.returnValue === false || e.getPreventDefault
  314. && e.getPreventDefault() ? true : false;
  315. }-*/;
  316. /**
  317. * Return whether a node is detached to the DOM.
  318. *
  319. * Be careful : This method works only on node that should be inserted within the body node.
  320. */
  321. public static boolean isDetached(Node n) {
  322. assert n != null;
  323. if ("html".equalsIgnoreCase(n.getNodeName())) {
  324. return false;
  325. }
  326. return !getOwnerDocument(n).getBody().isOrHasChild(n);
  327. }
  328. /**
  329. * Check is a javascript object can be cast to an Element.
  330. */
  331. public static native boolean isElement(Object o) /*-{
  332. return !!o && 'nodeType' in o && 'nodeName' in o;
  333. }-*/;
  334. /**
  335. * Check is a javascript object can be cast to an Event.
  336. */
  337. public static boolean isEvent(JavaScriptObject o) {
  338. return hasProperty(o, "currentTarget");
  339. }
  340. /**
  341. * Check is a javascript object is a function.
  342. */
  343. public static native boolean isFunction(JavaScriptObject o) /*-{
  344. return Object.prototype.toString.call(o) == '[object Function]';
  345. }-*/;
  346. /**
  347. * Check is a javascript can be cast to a node list.
  348. */
  349. public static native boolean isNodeList(JavaScriptObject o) /*-{
  350. var r = Object.prototype.toString.call(o);
  351. return r == '[object HTMLCollection]' || r == '[object NodeList]'
  352. || (typeof o == 'object' && o.length && o[0].tagName) ? true : false;
  353. }-*/;
  354. /**
  355. * Check is a javascript object is a Window.
  356. */
  357. public static boolean isWindow(JavaScriptObject o) {
  358. return hasProperty(o, "alert");
  359. }
  360. /**
  361. * Check if an element is a DOM or a XML node.
  362. */
  363. public static boolean isXML(Node o) {
  364. return o == null ? false
  365. : !"HTML".equals(getOwnerDocument(o).getDocumentElement().getNodeName());
  366. }
  367. /**
  368. * Load an external javascript library. The inserted script replaces the
  369. * element with the given id in the document.
  370. *
  371. * @deprecated use {@link com.google.gwt.query.client.plugins.ajax.Ajax#loadScript(String)}
  372. */
  373. @Deprecated
  374. public static void loadScript(String url, String id) {
  375. GQuery gs = GQuery.$(DOM.createElement("script"));
  376. GQuery gp = GQuery.$("#" + id).parent();
  377. if (gp.size() != 1) {
  378. gp = GQuery.$(GQuery.document.getBody());
  379. }
  380. GQuery.$("#" + id).remove();
  381. gp.append(gs.attr("src", url).attr("type", "text/javascript").attr("id", id));
  382. }
  383. /**
  384. * Return the element which is truth in the double scope.
  385. */
  386. public static native double or(double s1, double s2) /*-{
  387. return s1 || s2;
  388. }-*/;
  389. /**
  390. * Return the element which is truth in the javascript scope.
  391. */
  392. public static native <T> T or(T s1, T s2) /*-{
  393. return s1 || s2;
  394. }-*/;
  395. /**
  396. * Parses a json string returning a Object with useful method to get the
  397. * content.
  398. */
  399. public static Properties parseJSON(String json) {
  400. try {
  401. return utilsImpl.parseJSON(json);
  402. } catch (Exception e) {
  403. if (!GWT.isProdMode()) {
  404. System.err.println("Error while parsing json: " + e.getMessage() + ".\n" + json);
  405. }
  406. return Properties.create();
  407. }
  408. }
  409. /**
  410. * Parses a xml string and return the xml document element which can then be
  411. * passed to GQuery to create a typical GQuery object that can be traversed
  412. * and manipulated.
  413. */
  414. public static Element parseXML(String xml) {
  415. return utilsImpl.parseXML(xml);
  416. }
  417. public static String text(Element e) {
  418. return utilsImpl.text(e);
  419. }
  420. /**
  421. * Utility method to cast objects in production.
  422. * Useful for casting native implementations to interfaces like JsInterop
  423. */
  424. public static native <T> T cast(Object o) /*-{
  425. return o;
  426. }-*/;
  427. /**
  428. * Utility method to cast objects to array of string in production.
  429. */
  430. public static native String[] castArrayString(Object a) /*-{
  431. return a
  432. }-*/;
  433. /**
  434. * Call any arbitrary function present in a Javascript object.
  435. * It checks the existence of the function and object hierarchy before executing it.
  436. * It's very useful in order to avoid writing jsni blocks for very simple snippets.
  437. *
  438. * Note that GWT 3.0 jsinterop will come with a method similar, so we might deprecate
  439. * this in the future.
  440. *
  441. * Example
  442. * <pre>
  443. * // Create a svg node in our document.
  444. * Element svg = jsni(document, "createElementNS", "http://www.w3.org/2000/svg", "svg");
  445. * // Append it to the dom
  446. * $(svg).appendTo(document);
  447. * // show the svg element in the debug console
  448. * jsni("console.log", svg);
  449. * </pre>
  450. *
  451. * @param jso the object containing the method to execute
  452. * @param meth the literal name of the function to call, dot separators are allowed.
  453. * @param args an array with the arguments to pass to the function.
  454. * @return the java ready boxed object returned by the jsni method or null, if the
  455. * call return a number we will get a Double, if it returns a boolean we get a java
  456. * Boolean, strings comes as java String, otherwise we get the javascript object.
  457. */
  458. public static <T> T jsni(JavaScriptObject jso, String meth, Object... args) {
  459. return runJavascriptFunction(jso, meth, args);
  460. }
  461. /**
  462. * Run any arbitrary function in javascript scope using the window as the base object.
  463. * It checks the existence of the function and object hierarchy before executing it.
  464. * It's very useful in order to avoid writing jsni blocks for very simple snippets.
  465. *
  466. * Note that GWT 3.0 jsinterop will come with a method similar, so we might deprecate
  467. * this in the future.
  468. *
  469. * Example
  470. * <pre>
  471. * // Create a svg node in our document.
  472. * Element svg = jsni("document.createElementNS", "http://www.w3.org/2000/svg", "svg");
  473. * // Append it to the dom
  474. * $(svg).appendTo(document);
  475. * // show the svg element in the debug console
  476. * jsni("console.log", svg);
  477. * </pre>
  478. *
  479. * @param meth the literal name of the function to call, dot separators are allowed.
  480. * @param args an array with the arguments to pass to the function.
  481. * @return the java ready boxed object returned by the jsni method or null, if the
  482. * call return a number we will get a Double, if it returns a boolean we get a java
  483. * Boolean, strings comes as java String, otherwise we get the javascript object.
  484. */
  485. public static <T> T jsni(String meth, Object... args) {
  486. return runJavascriptFunction(null, meth, args);
  487. }
  488. /**
  489. * Call via jsni any arbitrary function present in a Javascript object.
  490. *
  491. * It's thought for avoiding to create jsni methods to call external functions and
  492. * facilitate the writing of js wrappers.
  493. *
  494. * Example
  495. * <pre>
  496. * // Create a svg node in our document.
  497. * Element svg = runJavascriptFunction(document, "createElementNS", "http://www.w3.org/2000/svg", "svg");
  498. * // Append it to the dom
  499. * $(svg).appendTo(document);
  500. * </pre>
  501. *
  502. * @param o the javascript object where the function is, it it is null we use window.
  503. * @param meth the literal name of the function to call, dot separators are allowed.
  504. * @param args an array with the arguments to pass to the function.
  505. * @return the java ready boxed object returned by the jsni method or null, if the
  506. * call return a number we will get a Double, if it returns a boolean we get a java
  507. * Boolean, strings comes as java String, otherwise we get the javascript object.
  508. *
  509. * @deprecated use jsni instead.
  510. */
  511. public static <T> T runJavascriptFunction(JavaScriptObject o, String meth, Object... args) {
  512. return runJavascriptFunctionImpl(o, meth, JsObjectArray.create().add(args).<JsArrayMixed>cast());
  513. }
  514. private static native <T> T runJavascriptFunctionImpl(JavaScriptObject o, String meth, JsArrayMixed args) /*-{
  515. var f = o || window, p = meth.split('.');
  516. for (var i in p) {
  517. o = f;
  518. f = f[p[i]];
  519. if (!f) return null;
  520. }
  521. return @com.google.gwt.query.client.js.JsUtils::isFunction(*)(f)
  522. && @com.google.gwt.query.client.js.JsCache::gwtBox(*)([f.apply(o, args)]);
  523. }-*/;
  524. /**
  525. * Check if a number is true in the javascript scope.
  526. */
  527. public static native boolean truth(double a) /*-{
  528. return a ? true : false;
  529. }-*/;
  530. /**
  531. * Check if an object is true in the javascript scope.
  532. */
  533. public static native boolean truth(Object a) /*-{
  534. return a ? true : false;
  535. }-*/;
  536. /**
  537. * Remove duplicates from an elements array.
  538. */
  539. public static JsArray<Element> unique(JsArray<Element> a) {
  540. return utilsImpl.unique(a);
  541. }
  542. public static String XML2String(JavaScriptObject js) {
  543. return utilsImpl.XML2String(js);
  544. }
  545. public static String JSON2String(JavaScriptObject js) {
  546. return utilsImpl.JSON2String(js);
  547. }
  548. /**
  549. * Returns a QueryString representation of a JavascriptObject.
  550. *
  551. * TODO: jquery implementation accepts a second parameter (traditional)
  552. */
  553. public static String param(JavaScriptObject js) {
  554. Properties prop = js.cast();
  555. String ret = "";
  556. for (String k : prop.keys()) {
  557. ret += ret.isEmpty() ? "" : "&";
  558. JsCache o = prop.getArray(k).cast();
  559. if (o != null) {
  560. for (int i = 0, l = o.length(); i < l; i++) {
  561. ret += i > 0 ? "&" : "";
  562. Properties p = o.<JsCache> cast().getJavaScriptObject(i);
  563. if (p != null) {
  564. ret += k + "[]=" + p.toJsonString();
  565. } else {
  566. ret += k + "[]=" + o.getString(i);
  567. }
  568. }
  569. } else {
  570. Properties p = prop.getJavaScriptObject(k);
  571. if (p != null) {
  572. ret += k + "=" + p.tostring();
  573. } else {
  574. String v = prop.getStr(k);
  575. if (v != null && !v.isEmpty() && !"null".equalsIgnoreCase(v)) {
  576. ret += k + "=" + v;
  577. }
  578. }
  579. }
  580. }
  581. return ret;
  582. }
  583. }