Revision: 6363 Author: [email protected] Date: Tue Oct 13 13:41:12 2009 Log: UiBinder now understands CssResource's imported scopes, which is more than I can say for myself most of the time.
Reviewed by jgw http://code.google.com/p/google-web-toolkit/source/detail?r=6363 Added: /trunk/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.css /trunk/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.java /trunk/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.ui.xml Deleted: /trunk/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java Modified: /trunk/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java /trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java /trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml /trunk/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java ======================================= --- /dev/null +++ /trunk/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.css Tue Oct 13 13:41:12 2009 @@ -0,0 +1,3 @@ +.body { + background-color: pink; +} ======================================= --- /dev/null +++ /trunk/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.java Tue Oct 13 13:41:12 2009 @@ -0,0 +1,83 @@ +/* + * Copyright 2009 Google Inc. + * + * 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.uibinder.sample.client; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.CssResource.ImportedWithPrefix; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.HasText; +import com.google.gwt.user.client.ui.Widget; + +/** + * Odd widget demonstrates UiBinder's integration with CssResource's obscure but + * crucial imported scopes feature. + */ +public class CssImportScopeSample extends Widget implements HasText { + interface Binder extends UiBinder<DivElement, CssImportScopeSample> { + } + private static final Binder binder = GWT.create(Binder.class); + + interface Bundle extends ClientBundle { + @Source("CssImportScopeSample.css") + InnerStyle innerStyle(); + + @Source("CssImportScopeSample.css") + OuterStyle style(); + } + + @ImportedWithPrefix("inner") + interface InnerStyle extends Style { + } + + @ImportedWithPrefix("outer") + interface OuterStyle extends Style { + } + + interface Style extends CssResource { + String body(); + } + + @UiField(provided = true) + Bundle bundle = GWT.create(Bundle.class); + @UiField + Element inner; + + @UiField + Element outer; + + CssImportScopeSample() { + bundle.style().ensureInjected(); + bundle.innerStyle().ensureInjected(); + setElement(binder.createAndBindUi(this)); + } + + public String getText() { + return outer.getInnerText(); + } + + public void setText(String text) { + outer.setInnerText(text); + } + + public void setWrappedText(String text) { + inner.setInnerText(text); + } +} ======================================= --- /dev/null +++ /trunk/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.ui.xml Tue Oct 13 13:41:12 2009 @@ -0,0 +1,26 @@ +<!-- Copyright 2009 Google Inc. --> +<!-- Licensed under the Apache License, Version 2.0 (the "License"); you --> +<!-- may not use this file except in compliance with the License. You may --> +<!-- 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. License for the specific language governing permissions and --> +<!-- limitations under the License. --> + +<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' > + <ui:with field='bundle' type='com.google.gwt.uibinder.sample.client.CssImportScopeSample.Bundle' /> + + <ui:style import='com.google.gwt.uibinder.sample.client.CssImportScopeSample.OuterStyle + com.google.gwt.uibinder.sample.client.CssImportScopeSample.InnerStyle'> + .outer-body .inner-body { width: 100px; background-color: red; } + </ui:style> + + <div class='{bundle.style.body}'> + <span ui:field='outer'/> + <div ui:field='inner' class='{bundle.innerStyle.body}'>Inner!</div> + </div> +</ui:UiBinder> ======================================= --- /trunk/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java Thu Sep 3 16:03:13 2009 +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2009 Google Inc. - * - * 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.uibinder.client; - -import com.google.gwt.dom.client.StyleInjector; -import com.google.gwt.resources.client.CssResource; - -import java.util.HashSet; -import java.util.Set; - -/** - * Extended by code generated by calls to GWT.create(Class<? extends UiBinder>). - * - * @param <U> The type of the root object of the generated UI, typically a - * subclass of {...@link com.google.gwt.dom.client.Element} or - * {...@link com.google.gwt.user.client.ui.UIObject} - * @param <O> The type of the object that will own the generated UI - */ -public abstract class AbstractUiBinder<U, O> implements UiBinder<U, O> { - private static final Set<Class<? extends CssResource>> injected = - new HashSet<Class<? extends CssResource>>(); - - /** - * Invokes {...@link StyleInjector#injectStylesheet} on the given css's - * {...@link CssResource#getText()}. Injection is performed only once for each - * CssResource subclass received (that is, we key on - * <code>css.getClass()</code>); - * - * @param css the resource to inject - */ - protected static void ensureCssInjected(CssResource css) { - if (!injected.contains(css.getClass())) { - StyleInjector.injectStylesheet(css.getText()); - injected.add(css.getClass()); - } - } -} ======================================= --- /trunk/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java Mon Oct 12 15:15:47 2009 +++ /trunk/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java Tue Oct 13 13:41:12 2009 @@ -21,6 +21,7 @@ import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.DataResource; import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.resources.client.CssResource.Import; import com.google.gwt.resources.client.ImageResource.ImageOptions; import com.google.gwt.resources.client.ImageResource.RepeatStyle; import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle; @@ -28,6 +29,8 @@ import com.google.gwt.uibinder.rebind.model.ImplicitDataResource; import com.google.gwt.uibinder.rebind.model.ImplicitImageResource; +import java.util.Set; + /** * Writes source implementing an {...@link ImplicitClientBundle}. */ @@ -43,6 +46,7 @@ private final JClassType imageOptionType; private final JClassType imageResourceType; private final JClassType repeatStyleType; + private final JClassType importAnnotationType; public BundleWriter(ImplicitClientBundle bundleClass, PrintWriterManager writerManager, TypeOracle oracle, @@ -58,6 +62,7 @@ imageOptionType = oracle.findType(ImageOptions.class.getCanonicalName()); imageResourceType = oracle.findType(ImageResource.class.getCanonicalName()); repeatStyleType = oracle.findType(RepeatStyle.class.getCanonicalName()); + importAnnotationType = oracle.findType(Import.class.getCanonicalName()); } public void write() throws UnableToCompleteException { @@ -77,20 +82,22 @@ } // Imports - writer.write("import %s;", imageResourceType.getQualifiedSourceName()); - writer.write("import %s;", imageOptionType.getQualifiedSourceName()); writer.write("import %s;", clientBundleType.getQualifiedSourceName()); writer.write("import %s;", dataResourceType.getQualifiedSourceName()); + writer.write("import %s;", imageResourceType.getQualifiedSourceName()); + writer.write("import %s;", imageOptionType.getQualifiedSourceName()); + writer.write("import %s;", importAnnotationType.getQualifiedSourceName()); writer.newline(); // Open interface writer.write("public interface %s extends ClientBundle {", bundleClass.getClassName()); writer.indent(); - + // Write css methods for (ImplicitCssResource css : bundleClass.getCssMethods()) { writer.write("@Source(\"%s\")", css.getSource()); + writeCssImports(css); writer.write("%s %s();", css.getClassName(), css.getName()); writer.newline(); } @@ -108,6 +115,26 @@ writer.outdent(); writer.write("}"); } + + private void writeCssImports(ImplicitCssResource css) { + Set<JClassType> importTypes = css.getImports(); + int numImports = importTypes.size(); + if (numImports > 0) { + if (numImports == 1) { + writer.write("@Import(%s.class)", + importTypes.iterator().next().getQualifiedSourceName()); + } else { + StringBuffer b = new StringBuffer(); + for (JClassType importType : importTypes) { + if (b.length() > 0) { + b.append(", "); + } + b.append(importType.getQualifiedSourceName()).append(".class"); + } + writer.write("@Import({%s})", b); + } + } + } private void writeImageMethods() { for (ImplicitImageResource image : bundleClass.getImageMethods()) { ======================================= --- /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java Mon Oct 12 15:15:47 2009 +++ /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java Tue Oct 13 13:41:12 2009 @@ -30,12 +30,43 @@ import com.google.gwt.uibinder.rebind.model.ImplicitImageResource; import com.google.gwt.uibinder.rebind.model.OwnerField; +import java.util.LinkedHashSet; + /** * Parses the root UiBinder element, and kicks of the parsing of the rest of the * document. */ public class UiBinderParser { + private enum Resource { + data { + void create(UiBinderParser parser, XMLElement elem) + throws UnableToCompleteException { + parser.createData(elem); + } + }, + image { + void create(UiBinderParser parser, XMLElement elem) + throws UnableToCompleteException { + parser.createImage(elem); + } + }, + style { + void create(UiBinderParser parser, XMLElement elem) + throws UnableToCompleteException { + parser.createStyle(elem); + } + }, + with { + void create(UiBinderParser parser, XMLElement elem) + throws UnableToCompleteException { + parser.createResource(elem); + } + }; + + abstract void create(UiBinderParser parser, XMLElement elem) + throws UnableToCompleteException; + } private static final String FLIP_RTL_ATTRIBUTE = "flipRtl"; private static final String FIELD_ATTRIBUTE = "field"; private static final String SOURCE_ATTRIBUTE = "src"; @@ -45,6 +76,7 @@ // constructor like this one does, and make this an ElementParser. I want // guice!!! + private static final String IMPORT_ATTRIBUTE = "import"; private final UiBinderWriter writer; private final TypeOracle oracle; private final MessagesWriter messagesWriter; @@ -52,6 +84,7 @@ private final ImplicitClientBundle bundleClass; private final JClassType cssResourceType; private final JClassType imageResourceType; + private final JClassType dataResourceType; public UiBinderParser(UiBinderWriter writer, MessagesWriter messagesWriter, @@ -66,44 +99,12 @@ this.imageResourceType = oracle.findType(ImageResource.class.getCanonicalName()); this.dataResourceType = oracle.findType(DataResource.class.getCanonicalName()); } - - private enum Resource { - data { - void create(UiBinderParser parser, XMLElement elem) - throws UnableToCompleteException { - parser.createData(elem); - } - }, - image { - void create(UiBinderParser parser, XMLElement elem) - throws UnableToCompleteException { - parser.createImage(elem); - } - }, - style { - void create(UiBinderParser parser, XMLElement elem) - throws UnableToCompleteException { - parser.createStyle(elem); - } - }, - with { - void create(UiBinderParser parser, XMLElement elem) - throws UnableToCompleteException { - parser.createResource(elem); - } - }; - - abstract void create(UiBinderParser parser, XMLElement elem) - throws UnableToCompleteException; - } /** * Parses the root UiBinder element, and kicks off the parsing of the rest of * the document. */ public String parse(XMLElement elem) throws UnableToCompleteException { - // TODO(rjrjr) Clearly need to break these find* methods out into their own - // parsers, an so need a registration scheme for uibinder-specific parsers findResources(elem); messagesWriter.findMessagesConfig(elem); XMLElement uiRoot = elem.consumeSingleChildElement(); @@ -117,17 +118,7 @@ return cssResourceType; } - JClassType publicType = oracle.findType(typeName); - if (publicType == null) { - writer.die("In %s, no such type %s", elem, typeName); - } - - if (!publicType.isAssignableTo(cssResourceType)) { - writer.die("In %s, type %s does not extend %s", elem, - publicType.getQualifiedSourceName(), - cssResourceType.getQualifiedSourceName()); - } - return publicType; + return findCssResourceType(elem, typeName); } private JClassType consumeTypeAttribute(XMLElement elem) @@ -242,9 +233,18 @@ String source = elem.consumeAttribute(SOURCE_ATTRIBUTE); String name = elem.consumeAttribute(FIELD_ATTRIBUTE, "style"); JClassType publicType = consumeCssResourceType(elem); + + String importTypeNames = elem.consumeAttribute(IMPORT_ATTRIBUTE, null); + LinkedHashSet<JClassType> importTypes = new LinkedHashSet<JClassType>(); + if (importTypeNames != null) { + String[] typeNames = importTypeNames.split("\\s+"); + for (String type : typeNames) { + importTypes.add(findCssResourceType(elem, type)); + } + } ImplicitCssResource cssMethod = bundleClass.createCssResource(name, source, - publicType, body); + publicType, body, importTypes); FieldWriter field = fieldManager.registerFieldOfGeneratedType( cssMethod.getPackageName(), cssMethod.getClassName(), @@ -252,6 +252,21 @@ field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(), cssMethod.getName())); } + + private JClassType findCssResourceType(XMLElement elem, String typeName) + throws UnableToCompleteException { + JClassType publicType = oracle.findType(typeName); + if (publicType == null) { + writer.die("In %s, no such type %s", elem, typeName); + } + + if (!publicType.isAssignableTo(cssResourceType)) { + writer.die("In %s, type %s does not extend %s", elem, + publicType.getQualifiedSourceName(), + cssResourceType.getQualifiedSourceName()); + } + return publicType; + } private void findResources(XMLElement binderElement) throws UnableToCompleteException { ======================================= --- /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java Tue Oct 13 10:44:52 2009 +++ /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java Tue Oct 13 13:41:12 2009 @@ -1191,7 +1191,7 @@ } private void writeClassOpen(IndentedWriter w) { - w.write("public class %s extends AbstractUiBinder<%s, %s> implements %s {", + w.write("public class %s implements UiBinder<%s, %s>, %s {", implClassName, uiRootType.getName(), uiOwnerType.getName(), baseClass.getName()); w.indent(); @@ -1199,7 +1199,7 @@ private void writeCssInjectors(IndentedWriter w) { for (ImplicitCssResource css : bundleClass.getCssMethods()) { - w.write("ensureCssInjected(%s.%s());", bundleClass.getFieldName(), + w.write("%s.%s().ensureInjected();", bundleClass.getFieldName(), css.getName()); } w.newline(); @@ -1242,7 +1242,7 @@ private void writeImports(IndentedWriter w) { w.write("import com.google.gwt.core.client.GWT;"); - w.write("import com.google.gwt.uibinder.client.AbstractUiBinder;"); + w.write("import com.google.gwt.uibinder.client.UiBinder;"); w.write("import com.google.gwt.uibinder.client.UiBinderUtil;"); w.write("import %s.%s;", uiRootType.getPackage().getName(), uiRootType.getName()); ======================================= --- /trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java Mon Oct 12 15:15:47 2009 +++ /trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java Tue Oct 13 13:41:12 2009 @@ -30,7 +30,7 @@ // LinkedHashSets for consistent order across recompiles private final LinkedHashSet<ImplicitCssResource> cssMethods = new LinkedHashSet<ImplicitCssResource>(); private final LinkedHashSet<ImplicitImageResource> imageMethods = new LinkedHashSet<ImplicitImageResource>(); - private final LinkedHashSet<ImplicitDataResource> dataMethods = new LinkedHashSet<ImplicitDataResource>(); + private final LinkedHashSet<ImplicitDataResource> dataMethods = new LinkedHashSet<ImplicitDataResource>(); private final String packageName; private final String className; private final String fieldName; @@ -60,12 +60,14 @@ * @param extendedInterface the public interface implemented by this * CssResource, or null * @param body the inline css text + * @param importTypes for the {...@literal @}Import annotation, if any. LinkedHashSet + * to enforce deterministic order across recompiles * @return */ public ImplicitCssResource createCssResource(String name, String source, - JClassType extendedInterface, String body) { + JClassType extendedInterface, String body, LinkedHashSet<JClassType> importTypes) { ImplicitCssResource css = new ImplicitCssResource(packageName, cssBaseName - + name, name, source, extendedInterface, body, logger); + + name, name, source, extendedInterface, body, logger, importTypes); cssMethods.add(css); return css; } ======================================= --- /trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java Mon Oct 12 15:15:47 2009 +++ /trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java Tue Oct 13 13:41:12 2009 @@ -29,6 +29,8 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.Collections; +import java.util.HashSet; import java.util.Set; /** @@ -42,17 +44,19 @@ private final JClassType extendedInterface; private final String body; private final MortalLogger logger; + private final Set<JClassType> imports; private File generatedFile; ImplicitCssResource(String packageName, String className, String name, String source, JClassType extendedInterface, String body, - MortalLogger logger) { + MortalLogger logger, HashSet<JClassType> importTypes) { this.packageName = packageName; this.className = className; this.name = name; this.extendedInterface = extendedInterface; this.body = body; this.logger = logger; + this.imports = Collections.unmodifiableSet(importTypes); if (body.length() > 0) { assert "".equals(source); // Enforced for real by the parser @@ -96,7 +100,7 @@ } CssStylesheet sheet = GenerateCssAst.exec(logger.getTreeLogger(), urls); - return ExtractClassNamesVisitor.exec(sheet); + return ExtractClassNamesVisitor.exec(sheet, imports.toArray(new JClassType[imports.size()])); } /** @@ -105,6 +109,10 @@ public JClassType getExtendedInterface() { return extendedInterface; } + + public Set<JClassType> getImports() { + return imports; + } /** * @return the name of this resource. This is both its method name in the ======================================= --- /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java Mon Oct 12 15:15:47 2009 +++ /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java Tue Oct 13 13:41:12 2009 @@ -58,7 +58,7 @@ public interface Style extends CssResource { String menuBar(); } - + interface Binder extends UiBinder<Widget, WidgetBasedUi> { } private static final Binder binder = GWT.create(Binder.class); @@ -121,6 +121,7 @@ @UiField Image babyWidget; @UiField ParagraphElement simpleSpriteParagraph; @UiField DataResource heartCursorResource; + @UiField CssImportScopeSample cssImportScopeSample; public WidgetBasedUi() { this.bundledLabel = new Label(); ======================================= --- /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml Mon Oct 12 15:15:47 2009 +++ /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml Tue Oct 13 13:41:12 2009 @@ -348,6 +348,9 @@ (This one too, but even more so.) </span> </p> + <demo:CssImportScopeSample ui:field='cssImportScopeSample' wrappedText='Please use it.'> + And this one relies on CssResource's imported scopes feature + </demo:CssImportScopeSample> <h2>Evolving</h2> <p> Things change. This label uses the new ui:field attribute to declare ======================================= --- /trunk/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java Mon Oct 12 15:15:47 2009 +++ /trunk/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java Tue Oct 13 13:41:12 2009 @@ -25,6 +25,7 @@ import com.google.gwt.junit.Platform; import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.DataResource; import com.google.gwt.resources.client.CssResource.NotStrict; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.ui.DisclosurePanel; @@ -355,6 +356,11 @@ public void testDataResource() { assertNotNull(widgetUi.heartCursorResource.getUrl()); } + + @DoNotRunWith(Platform.Htmlunit) + public void testCssImportedScopes() { + assertEquals(100, widgetUi.cssImportScopeSample.inner.getOffsetWidth()); + } public void testSpritedElement() { assertEquals(widgetUi.prettyImage.getWidth(), --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---
