用于多个“主题”的 ClientBundle

发布于 2024-11-02 07:22:20 字数 481 浏览 1 评论 0原文

我们有一个 Web 应用程序,需要为每个主要客户提供不同的主题。最初的开发人员通过查看 JavaScript 中的 URL 并添加样式表来覆盖默认主题来做到这一点。

这样做的一个问题是该网站有几秒钟的默认外观,然后突然切换到正确的主题。另一个是它似乎浪费了大量的带宽/时间。

我当前的想法是创建一个“默认”ClientBundle 具有我们默认的外观和感觉,它扩展了该界面,并使用 @ImageResouce 等各种注释并指向不同的位置,用客户端的图像覆盖每个条目(根据需要)。

有人有这样做的经验吗?我预见到的一个问题是无法使用 uibinder 样式标签,因为它们静态地指向特定的资源包。

有什么想法吗?

We have a web application that needs a different theme for each major client. The original developer did this by looking at the URL in javascript and adding a stylesheet to override the default theme.

One problem with this is the site has the default look for a few seconds then suddenly swaps to the correct theme. Another is that it seems to waste a lot of bandwidth/time.

My current idea is to create a "default" ClientBundle with our default look and feel extend that interface and override each entry (as needed) with the client's images using the various annotations like @ImageResouce and pointing to a different location.

Has anybody had experience doing this? One problem I forsee is not being able to use the uibinder style tags as they statically point to a specific resource bundle.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

无力看清 2024-11-09 07:22:20

覆盖捆绑包

是的,可以。

我已经用 ClientBundles 进行了覆盖,并且工作正常。您必须做的一件事是也继承属性的类型。例如:

BigBundle {
  Nestedundle otherBundle();
  ImageResource otherImage();
  Styles css();
}

然后您必须以这种方式继承:

OtherBigBundle extends BigBundle {
  OtherNestedBundle otherBundle(); // if you want to change it
  ImageResource otherImage(); // of you want to change it
  OtherStyles css(); // of you want to change it
}

并且 OtherNestedBundle extends NestedBundle
OtherStyles extends Styles

至少使用 css: 如果声明属性时不使用子接口,它们将为相同的 CSS 类名生成样式,并且所有样式都会混合。因此,使用子接口声明重写样式:)

灵活的 UIBinders

如果您使用 UiField(provided=true) 注释,您可以从包外部进行设置。这样你首先设置bundle,然后调用uibindler。假设资源字段已经创建,它将使用该资源字段。

延迟绑定

您可以使用 GWT.runAsync 来加载正确的包。

一些示例

ui.xml

<ui:with field='res' type='your.package.TheBundle'/>

相应的类

@UiField(provided=true) TheBundle bundle;

private void createTheThing() {
  this.bundle = factory.createBundle();
  MyUiBindler binder = GWT.create(MyUiBindler.class);
  this.panel = binder.createAndBindUi(this);
  ...
}

一些捆绑包接口

interface TheBundle extends ClientBundle {
  @ImageResource("default.png")
  ImageResource image1();

  @Source("default.css")
  TheCss css();
}

interface Theme1Bundle extends TheBundle {
  @ImageResource("one.png")
  ImageResource image1(); // type: imageresource is ok

  @Source("one.css")
  OneCss css(); // type: OneCss => use other compiled css class-names

  interface OneCss extends TheCss { // inner-interface, just for fun
     // don't need to declare each String method
  }
}

如果您不重写某些东西,那没关系

捆绑包工厂的选项

1)完全

if (...) {
  return GWT.create(TheBundle.class);
} else if (...) {
  return GWT.create(Theme1Bundle.class);
}

2)runAsync (只需加载所需的部分...但在执行初始部分之后)

if (...) {
   GWT.runAsync(new RunAsyncCallback() {
      public void onSuccess() {
        return GWT.create(TheBundle.class);
      }
      // please program the onFailure method
   });
} else if (...) {
   GWT.runAsync(new RunAsyncCallback() {
      public void onSuccess() {
        return GWT.create(Theme1Bundle.class);
      }
      // please program the onFailure method
   });
}

3)使用延迟绑定和生成器在编译时基于带注释的包自动生成工厂,例如 @ThemeBundle("one")

这个示例来自现实世界。我使用 DynamicEntryPointWidgetFactory(简称 DEPWidgetFactory)根据标识符字符串创建小部件。每个小部件都是一个应用程序屏幕,每个主菜单项都有它必须创建的 widgetName。

在您的情况下,id 将是要创建的主题。

重要提示:如果您使用 runAsync,则无法像前面的示例代码一样在创建 UI 之前创建资源包。您必须请求主题,当它准备好时(在回调中)将其传递给您的小部件构造函数,并且您的小部件可以将其分配给其字段。

工厂接口:

public interface DynamicEntryPointWidgetFactory
{
   public void buildWidget(String widgetName, AsyncCallback<Widget> callback);
}

要生成的小部件的注释:

@Target(ElementType.TYPE)
public @interface EntryPointWidget 
{
    /**
     * The name wich will be used to identify this widget.
     */
    String value();
}

模块配置:

它表示:工厂的实现将使用此类生成(另一个选项是使用替换,但在我们的例子中,我们没有预定义每个区域设置或浏览器的选项,但更动态)。

<generate-with class="com.dia.nexdia.services.gwt.rebind.entrypoint.DynamicEntryPointFactoryGenerator">
  <when-type-assignable class="com.dia.nexdia.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactory" />
</generate-with>

发电机:

public class DynamicEntryPointFactoryGenerator extends Generator {
    @Override
    public String generate(TreeLogger logger, GeneratorContext context,
            String typeName) throws UnableToCompleteException {
        PrintWriter pw = context.tryCreate(logger,
                "x.services.gwt.client.entrypoint",
                "DynamicEntryPointWidgetFactoryImpl");

        if (pw != null) {
            // write package, imports, whatever
            pw.append("package x.services.gwt.client.entrypoint;");
            pw.append("import x.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactory;");
            pw.append("import com.google.gwt.core.client.GWT;");
            pw.append("import com.google.gwt.core.client.RunAsyncCallback;");
            pw.append("import com.google.gwt.user.client.rpc.AsyncCallback;");
            pw.append("import com.google.gwt.user.client.ui.Widget;");

            // the class
            pw.append("public class DynamicEntryPointWidgetFactoryImpl implements DynamicEntryPointWidgetFactory {");

            // buildWidget method
            pw.append("   public void buildWidget(String widgetName, final AsyncCallback<Widget> callback) {");

            // iterates over all the classes to find those with EntryPointWidget annotation
            TypeOracle oracle = context.getTypeOracle();
            JPackage[] packages = oracle.getPackages();
            for (JPackage pack : packages) 
            {
                JClassType[] classes = pack.getTypes();
                for (JClassType classtype : classes) 
                {
                    EntryPointWidget annotation = classtype.getAnnotation(EntryPointWidget.class);
                    if (annotation != null) 
                    {
                        String fullName = classtype.getQualifiedSourceName();
                        logger.log(TreeLogger.INFO, "Entry-point widget found: " + fullName);

                        pw.append("if (\"" + annotation.value() + "\".equals(widgetName)) {");
                        pw.append("   GWT.runAsync(" + fullName + ".class, new RunAsyncCallback() {");
                        pw.append("      public void onFailure(Throwable t) {");
                        pw.append("         callback.onFailure(t);");
                        pw.append("      }");
                        pw.append("      public void onSuccess() {");
                        pw.append("         callback.onSuccess(new " + fullName + "());");
                        pw.append("      }");
                        pw.append("   });");
                        pw.append("   return;");
                        pw.append("}");
                    }
                }
            }
            pw.append("callback.onFailure(new IllegalArgumentException(\"Widget '\" + widgetName + \"' not recognized.\"));");

            pw.append("   }");
            pw.append("}");

            context.commit(logger, pw);         
        }

        // return the name of the generated class
        return "x.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactoryImpl";
    }

Overriden bundles

Yes you can.

I've did the override thing with ClientBundles and works fine. One thing you MUST do is inherit the types of the properties too. By example:

BigBundle {
  Nestedundle otherBundle();
  ImageResource otherImage();
  Styles css();
}

And then you must inherit this way:

OtherBigBundle extends BigBundle {
  OtherNestedBundle otherBundle(); // if you want to change it
  ImageResource otherImage(); // of you want to change it
  OtherStyles css(); // of you want to change it
}

and OtherNestedBundle extends NestedBundle
and OtherStyles extends Styles

At least with css's: if the properties are declared NOT USING the child interface they will produce styles for the same CSS classname and all will be mixed. So declare overriden styles with the child interfaces :)

Flexible UIBinders

You can set from outside the bundle to use if you use UiField(provided=true) annotation. In this way you first set the bundle and then call the uibindler. It will use the resource field assuming it's already created.

Deferred binding

You could use GWT.runAsync for loading just the correct bundle.

Some example

The ui.xml

<ui:with field='res' type='your.package.TheBundle'/>

the corresponding class

@UiField(provided=true) TheBundle bundle;

private void createTheThing() {
  this.bundle = factory.createBundle();
  MyUiBindler binder = GWT.create(MyUiBindler.class);
  this.panel = binder.createAndBindUi(this);
  ...
}

Some bundle interfaces

interface TheBundle extends ClientBundle {
  @ImageResource("default.png")
  ImageResource image1();

  @Source("default.css")
  TheCss css();
}

interface Theme1Bundle extends TheBundle {
  @ImageResource("one.png")
  ImageResource image1(); // type: imageresource is ok

  @Source("one.css")
  OneCss css(); // type: OneCss => use other compiled css class-names

  interface OneCss extends TheCss { // inner-interface, just for fun
     // don't need to declare each String method
  }
}

If you don't override something it's ok

Options for the bundle factory

1) just altogether

if (...) {
  return GWT.create(TheBundle.class);
} else if (...) {
  return GWT.create(Theme1Bundle.class);
}

2) runAsync (just load the needed part... but after the initial part is executed)

if (...) {
   GWT.runAsync(new RunAsyncCallback() {
      public void onSuccess() {
        return GWT.create(TheBundle.class);
      }
      // please program the onFailure method
   });
} else if (...) {
   GWT.runAsync(new RunAsyncCallback() {
      public void onSuccess() {
        return GWT.create(Theme1Bundle.class);
      }
      // please program the onFailure method
   });
}

3) use deferred-binding and generators for autogenerating factory in compile-time based on annotated bundles like @ThemeBundle("one")

This example is from the real world. I use a DynamicEntryPointWidgetFactory (DEPWidgetFactory for short) for creating widget based on an identifier string. Each widget is an application screen and each main menu ítem has the widgetName it has to create.

In your case the id will be the theme to create.

Important: if you use runAsync you cannot create the resourcebundle just before creating the UI like in the sample code before. You must ask for the theme and when it's ready (in the callback) pass it to your widget constructor and your widget can assign it to its field.

The factory interface:

public interface DynamicEntryPointWidgetFactory
{
   public void buildWidget(String widgetName, AsyncCallback<Widget> callback);
}

The annotation for widgets to generate:

@Target(ElementType.TYPE)
public @interface EntryPointWidget 
{
    /**
     * The name wich will be used to identify this widget.
     */
    String value();
}

The module configuration:

It says: the implementation for the Factory will be generated with this class (the other option is to use replace-with, but in our case we don't have predefined options for each locale or browser, but something more dynamic).

<generate-with class="com.dia.nexdia.services.gwt.rebind.entrypoint.DynamicEntryPointFactoryGenerator">
  <when-type-assignable class="com.dia.nexdia.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactory" />
</generate-with>

The generator:

public class DynamicEntryPointFactoryGenerator extends Generator {
    @Override
    public String generate(TreeLogger logger, GeneratorContext context,
            String typeName) throws UnableToCompleteException {
        PrintWriter pw = context.tryCreate(logger,
                "x.services.gwt.client.entrypoint",
                "DynamicEntryPointWidgetFactoryImpl");

        if (pw != null) {
            // write package, imports, whatever
            pw.append("package x.services.gwt.client.entrypoint;");
            pw.append("import x.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactory;");
            pw.append("import com.google.gwt.core.client.GWT;");
            pw.append("import com.google.gwt.core.client.RunAsyncCallback;");
            pw.append("import com.google.gwt.user.client.rpc.AsyncCallback;");
            pw.append("import com.google.gwt.user.client.ui.Widget;");

            // the class
            pw.append("public class DynamicEntryPointWidgetFactoryImpl implements DynamicEntryPointWidgetFactory {");

            // buildWidget method
            pw.append("   public void buildWidget(String widgetName, final AsyncCallback<Widget> callback) {");

            // iterates over all the classes to find those with EntryPointWidget annotation
            TypeOracle oracle = context.getTypeOracle();
            JPackage[] packages = oracle.getPackages();
            for (JPackage pack : packages) 
            {
                JClassType[] classes = pack.getTypes();
                for (JClassType classtype : classes) 
                {
                    EntryPointWidget annotation = classtype.getAnnotation(EntryPointWidget.class);
                    if (annotation != null) 
                    {
                        String fullName = classtype.getQualifiedSourceName();
                        logger.log(TreeLogger.INFO, "Entry-point widget found: " + fullName);

                        pw.append("if (\"" + annotation.value() + "\".equals(widgetName)) {");
                        pw.append("   GWT.runAsync(" + fullName + ".class, new RunAsyncCallback() {");
                        pw.append("      public void onFailure(Throwable t) {");
                        pw.append("         callback.onFailure(t);");
                        pw.append("      }");
                        pw.append("      public void onSuccess() {");
                        pw.append("         callback.onSuccess(new " + fullName + "());");
                        pw.append("      }");
                        pw.append("   });");
                        pw.append("   return;");
                        pw.append("}");
                    }
                }
            }
            pw.append("callback.onFailure(new IllegalArgumentException(\"Widget '\" + widgetName + \"' not recognized.\"));");

            pw.append("   }");
            pw.append("}");

            context.commit(logger, pw);         
        }

        // return the name of the generated class
        return "x.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactoryImpl";
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文