diff options
author | jdramaix <julien.dramaix@gmail.com> | 2013-11-22 11:39:18 +0100 |
---|---|---|
committer | jdramaix <julien.dramaix@gmail.com> | 2013-11-22 11:39:18 +0100 |
commit | 6c9ec1f3609291d27b9c287a3fe788cb8ac71916 (patch) | |
tree | 713084bc189e2916ccef1e248864454b26b1d561 | |
parent | 2e783da49474361b45c7c0bbb952502649f52c83 (diff) | |
parent | 74edc0e4d202ec31bc591f19b63f54523f4ea776 (diff) | |
download | gwtquery-6c9ec1f3609291d27b9c287a3fe788cb8ac71916.tar.gz gwtquery-6c9ec1f3609291d27b9c287a3fe788cb8ac71916.zip |
Merge branch 'master' of github.com:gwtquery/gwtquery into jd_event
6 files changed, 286 insertions, 3 deletions
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml b/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml index 873e09ab..a902fd99 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml +++ b/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml @@ -22,6 +22,11 @@ <when-type-assignable class="com.google.gwt.query.client.Browser"/> </generate-with> + <!-- JSNI Generator --> + <generate-with class="com.google.gwt.query.rebind.JsniBundleGenerator"> + <when-type-assignable class="com.google.gwt.query.client.builders.JsniBundle"/> + </generate-with> + <!-- Json and Xml builders --> <generate-with class="com.google.gwt.query.rebind.JsonBuilderGenerator"> <when-type-assignable class="com.google.gwt.query.client.builders.JsonBuilder"/> diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/QueryMin.gwt.xml b/gwtquery-core/src/main/java/com/google/gwt/query/QueryMin.gwt.xml index 385adde8..c67ec2af 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/QueryMin.gwt.xml +++ b/gwtquery-core/src/main/java/com/google/gwt/query/QueryMin.gwt.xml @@ -42,6 +42,11 @@ </any> </set-property> + <!-- JSNI Generator --> + <generate-with class="com.google.gwt.query.rebind.JsniBundleGenerator"> + <when-type-assignable class="com.google.gwt.query.client.builders.JsniBundle"/> + </generate-with> + <!-- Browser flags --> <generate-with class="com.google.gwt.query.rebind.BrowserGenerator"> <when-type-assignable class="com.google.gwt.query.client.Browser"/> diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsniBundle.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsniBundle.java new file mode 100644 index 00000000..14b3f1f2 --- /dev/null +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsniBundle.java @@ -0,0 +1,125 @@ +/* + * Copyright 2013, The gwtquery team. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.query.client.builders; + +import static java.lang.annotation.ElementType.*; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A tag interface that is used in the generation of jsni bundles. + * + * A jsni-bundle is a class with jsni methods whose content is taken from + * external handwritten javascript files. + * + * The goals of using this technique are: + * - Use pure javascript files so as we can use IDEs for editing, formating etc, + * instead of dealing with code in comment blocks. + * - Facilitate writing and testing javascript in the browser before compiling it. + * - Include third-party javascript libraries without modification of the original source. + * - Not need of adding javascript tags in the html page or module file to include + * third-party javascript. + * - GWT compiler will get rid of any jsni fragment if the application does not use it. + * - Included javascript will take advantage of GWT jsni validators, obfuscators + * and optimizers. + * + * In summary, this mechanism facilitates the creation of GWT libraries wrapping or using + * external javascript code, hence the developer does not have to take care of tags, + * and leaving gwt compiler the decission to include external code when it is actually + * required. + * + * <pre> + public interface HighCharts extends JsniBundle { + @LibrarySource("highcharts.js") + public void initialize(); + + @MethodSource("createChart.js") + public JavaScriptObject createChart(String elementId, JavaScriptObject config); + } + + // Generate the Bundle implementation + HighCharts library = GWT.create(HighCharts.class); + + // Initialize the third-party library + library.initialize(); + + // Create a new chart + JavaScriptObject chart = hc.createChart("container", properties); + + * </pre> + */ +public interface JsniBundle { + + /** + * Annotation to mark inclusion of third-party libraries. + * + * The content is wrapped with an inner javascript function to set + * the context to the document window instead of the iframe where GWT + * code is run. + */ + @Target({METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + public @interface LibrarySource { + /** + * The external file to be included + */ + String value(); + + /** + * Fragment of code to include before the external javascript is + * written. + */ + String prepend() default "(function(window, document, console){\n"; + + /** + * Fragment of code to include after the external javascript has been + * written. + */ + String postpend() default "\n}.apply($wnd, [$wnd, $doc, $wnd.console]));"; + } + + /** + * Annotation to mark inclusion of jsni code writen in external js files. + * + * The content is not wrapped by default, so the developer has the responsibility + * to return the suitable value and to handle correctly parameters. + */ + @Target({METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + public @interface MethodSource { + /** + * The external file to be included + */ + String value(); + + /** + * Fragment of code to include before the external javascript is + * written. + */ + String prepend() default ""; + + /** + * Fragment of code to include after the external javascript has been + * written. + */ + String postpend() default ""; + } +} diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java index 89b1ab57..9790b12f 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java @@ -26,6 +26,7 @@ import com.google.gwt.dom.client.NodeList; import com.google.gwt.query.client.Function; import com.google.gwt.query.client.GQuery; import com.google.gwt.query.client.Properties; +import com.google.gwt.query.client.plugins.ajax.Ajax; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; @@ -371,7 +372,10 @@ public class JsUtils { /** * Load an external javascript library. The inserted script replaces the * element with the given id in the document. + * + * @deprecated use {@link com.google.gwt.query.client.plugins.ajax.Ajax#loadScript(String)} */ + @Deprecated public static void loadScript(String url, String id) { GQuery gs = GQuery.$(DOM.createElement("script")); GQuery gp = GQuery.$("#" + id).parent(); diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/Ajax.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/Ajax.java index d50d7470..3c834330 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/Ajax.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/ajax/Ajax.java @@ -128,7 +128,7 @@ public class Ajax extends GQuery { if ("jsonp".equalsIgnoreCase(dataType)) { ret = new PromiseReqBuilderJSONP(url, null, settings.getTimeout()); - } else if ("script".equalsIgnoreCase(dataType)){ + } else if ("loadscript".equalsIgnoreCase(dataType)){ ret = createPromiseScriptInjector(url); } else { ret = createPromiseRequestBuilder(settings, httpMethod, url, data) @@ -144,6 +144,9 @@ public class Ajax extends GQuery { retData = JsUtils.parseJSON(response.getText()); } else { retData = response.getText(); + if ("script".equalsIgnoreCase(dataType)) { + ScriptInjector.fromString((String)retData).setWindow(window).inject(); + } } } catch (Exception e) { if (GWT.getUncaughtExceptionHandler() != null) { @@ -212,7 +215,8 @@ public class Ajax extends GQuery { private static Promise createPromiseScriptInjector(final String url) { return new PromiseFunction() { public void f(final Deferred dfd) { - ScriptInjector.fromUrl(url).setCallback(new Callback<Void, Exception>() { + ScriptInjector.fromUrl(url).setWindow(window) + .setCallback(new Callback<Void, Exception>() { public void onSuccess(Void result) { dfd.resolve(); } @@ -349,7 +353,7 @@ public class Ajax extends GQuery { } /** - * Load a JavaScript file from the server using a GET HTTP request, then execute it. + * Get a JavaScript file from the server using a GET HTTP request, then execute it. */ public static Promise getScript(String url) { return getScript(url, null); @@ -364,6 +368,22 @@ public class Ajax extends GQuery { ); } + /** + * Load a JavaScript file from any url using the script tag mechanism + */ + public static Promise loadScript(String url) { + return loadScript(url, null); + } + + public static Promise loadScript(final String url, Function success) { + return ajax(createSettings() + .setUrl(url) + .setType("get") + .setDataType("loadscript") + .setSuccess(success) + ); + } + public static Promise post(String url, Properties data, final Function onSuccess) { Settings s = createSettings(); s.setUrl(url); diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsniBundleGenerator.java b/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsniBundleGenerator.java new file mode 100644 index 00000000..27c91d55 --- /dev/null +++ b/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsniBundleGenerator.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013, The gwtquery team. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.query.rebind; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.ByteArrayOutputStream; + +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.query.client.builders.JsniBundle; +import com.google.gwt.query.client.builders.JsniBundle.LibrarySource; +import com.google.gwt.query.client.builders.JsniBundle.MethodSource; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; + +/** + * Generates an implementation of a user-defined interface <code>T</code> that + * extends {@link JsniBundle}. + * + * The generated implementation includes hand-written external js-files into + * jsni methods so as those files can take advantage of gwt compiler optimizations. + * + */ +public class JsniBundleGenerator extends Generator { + + public String generate(TreeLogger logger, GeneratorContext context, String requestedClass) + throws UnableToCompleteException { + + TypeOracle oracle = context.getTypeOracle(); + JClassType clazz = oracle.findType(requestedClass); + + String packageName = clazz.getPackage().getName(); + String className = clazz.getName().replace('.', '_') + "_Impl"; + String fullName = packageName + "." + className; + + PrintWriter pw = context.tryCreate(logger, packageName, className); + + if (pw != null) { + + ClassSourceFileComposerFactory fact = new ClassSourceFileComposerFactory(packageName, className); + fact.addImplementedInterface(requestedClass); + SourceWriter sw = fact.createSourceWriter(context, pw); + + if (sw != null) { + for (JMethod method : clazz.getMethods()) { + LibrarySource librarySource = method.getAnnotation(LibrarySource.class); + String value, prepend, postpend; + if (librarySource != null) { + value = librarySource.value(); + prepend = librarySource.prepend(); + postpend = librarySource.postpend(); + } else { + MethodSource methodSource = method.getAnnotation(MethodSource.class); + if (methodSource != null) { + value = methodSource.value(); + prepend = methodSource.prepend(); + postpend = methodSource.postpend(); + } else { + return null; + } + } + String file = packageName.replace(".", "/") + "/" + value; + try { + InputStream is = this.getClass().getClassLoader().getResourceAsStream(file); + OutputStream os = new ByteArrayOutputStream(); + IOUtils.copy(is, os); + + String jsni = os.toString() + // remove MS <CR> + .replace("\r", "") + // remove 'c' (/* */) style comments blocks + .replaceAll("/\\*(?>(?:(?>[^\\*]+)|\\*(?!/))*)\\*/", "") + // remove 'c++' (//) style comment lines + .replaceAll("(?m)^\\s*//.*$", "") + // remove 'c++' (//) style comments at the end of a code line + .replaceAll("(?m)^(.*)//[^'\"]*?$", "$1") + // remove empty lines + .replaceAll("\n+", "\n"); + ; + + // Using pw instead of sw in order to avoid stack errors because sw.print is a recursive function + // and it fails with very long javascript files. + + // JMethod.toString() prints the java signature of the method, so we just have to replace abstract by native. + pw.println(method.toString().replace("abstract", "native") + "/*-{"); + pw.println(prepend); + pw.println(jsni); + pw.println(postpend); + pw.println("}-*/;"); + } catch (Exception e) { + e.printStackTrace(); + throw new UnableToCompleteException(); + } + } + + sw.commit(logger); + } + } + + return fullName; + } +} |