PDA

View Full Version : Trying the Appearance Design Pattern - what am I doing wrong?



jvahabzadeh
3 Jan 2012, 8:51 AM
All,

Ok, so I've NEVER dealt with the Appearance Design Pattern before - in GXT or in GWT. I'm reasonably new to GWT as it is, and newer still to GXT, having dealt only with GXT 3.0 DP5 and GXT 3.0 Beta 1.

So, I've taken a look at the Appearance Design Pattern article (http://www.sencha.com/blog/ext-gwt-3-appearance-design/), and tried to, well, try it out. However, this throws an exception when I try to run it, and I have no idea whatsoever what I'm doing wrong. Can anyone point me in the right direction? I really want to try this and learn it!

I am using Eclipse as a development environment.

My classes all exist in package com.joev.gwttests.client

I created an AppearancePushButton class that consists of the AppearancePushButton code (near the bottom of the article), and includes the Appearance interface and DefaultAppearance implementation of it, as provided in the article.

I also put in a simplified version of the Appearance-substitution code in the project's gwt.xml file. After all the inherits, but before the entry point info, I put:


<replace-with class="AppearancePushButton.DefaultAppearance">
<when-type-is class="AppearancePushButton.Appearance"/>
</replace-with>


In the same package, I created the DefaultAppearance.html and DefaultAppearance.css files, though I had to modify the latter slightly - it appears that the ".testButton .testImage" line is really supposed to read ".testButtonImage"

I then created a VERY short bit of code that implements EntryPoint just to see what the button would look like:

My entry point code:


public void onModuleLoad() {
RootPanel.get().add(new AppearancePushButton("My appearance pushbutton"));
}




The exception is as follows (note that my EntryPoint class is called GWT3BetaTest1):


11:43:57.115 [ERROR] [gwt3betatest1] Failed to create an instance of 'com.joev.gwttests.client.AppearancePushButton$DefaultAppearance' via deferred binding

java.lang.RuntimeException: Deferred binding failed for 'com.joev.gwttests.client.AppearancePushButton$DefaultAppearance$Template' (did you forget to inherit a required module?)
at com.google.gwt.dev.shell.GWTBridgeImpl.create(GWTBridgeImpl.java:53)
at com.google.gwt.core.client.GWT.create(GWT.java:97)
at com.joev.gwttests.client.AppearancePushButton$DefaultAppearance.<init>(AppearancePushButton.java:63)
at com.joev.gwttests.client.AppearancePushButton$DefaultAppearance.<init>(AppearancePushButton.java:56)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at com.google.gwt.dev.shell.ModuleSpace.rebindAndCreate(ModuleSpace.java:465)
at com.google.gwt.dev.shell.GWTBridgeImpl.create(GWTBridgeImpl.java:49)
at com.google.gwt.core.client.GWT.create(GWT.java:97)
at com.joev.gwttests.client.AppearancePushButton.<init>(AppearancePushButton.java:85)
at com.joev.gwttests.client.GWT3BetaTest1.onModuleLoad(GWT3BetaTest1.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.google.gwt.dev.shell.ModuleSpace.onLoad(ModuleSpace.java:396)
at com.google.gwt.dev.shell.OophmSessionHandler.loadModule(OophmSessionHandler.java:200)
at com.google.gwt.dev.shell.BrowserChannelServer.processConnection(BrowserChannelServer.java:525)
at com.google.gwt.dev.shell.BrowserChannelServer.run(BrowserChannelServer.java:363)
at java.lang.Thread.run(Thread.java:662)
Caused by: com.google.gwt.core.ext.UnableToCompleteException: (see previous log entries)
at com.google.gwt.dev.shell.ModuleSpace.rebind(ModuleSpace.java:595)
at com.google.gwt.dev.shell.ModuleSpace.rebindAndCreate(ModuleSpace.java:455)
at com.google.gwt.dev.shell.GWTBridgeImpl.create(GWTBridgeImpl.java:49)
at com.google.gwt.core.client.GWT.create(GWT.java:97)
at com.joev.gwttests.client.AppearancePushButton$DefaultAppearance.<init>(AppearancePushButton.java:63)
at com.joev.gwttests.client.AppearancePushButton$DefaultAppearance.<init>(AppearancePushButton.java:56)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at com.google.gwt.dev.shell.ModuleSpace.rebindAndCreate(ModuleSpace.java:465)
at com.google.gwt.dev.shell.GWTBridgeImpl.create(GWTBridgeImpl.java:49)
at com.google.gwt.core.client.GWT.create(GWT.java:97)
at com.joev.gwttests.client.AppearancePushButton.<init>(AppearancePushButton.java:85)
at com.joev.gwttests.client.GWT3BetaTest1.onModuleLoad(GWT3BetaTest1.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.google.gwt.dev.shell.ModuleSpace.onLoad(ModuleSpace.java:396)
at com.google.gwt.dev.shell.OophmSessionHandler.loadModule(OophmSessionHandler.java:200)
at com.google.gwt.dev.shell.BrowserChannelServer.processConnection(BrowserChannelServer.java:525)
at com.google.gwt.dev.shell.BrowserChannelServer.run(BrowserChannelServer.java:363)
at java.lang.Thread.run(Thread.java:662)



My DefaultAppearance.css file:


.testButton {
border: 1px solid navy;
font-size: 12px;
padding: 4px;
}

.testButtonImage {
float: left;
}

.testButtonText {
text-align: center;
}


My DefaultAppearance.html file (copied straight from the article):


<div class="{style.testButton}">
<div class="{style.testImage}"></div>
<div class="{style.testText}"></div>
</div>


My AppearancePushButton.java file (copied from the article, including the Appearance interface and implementation parts)


public class AppearancePushButton extends Component implements HasClickHandlers {
private final Appearance appearance;

public interface Appearance {
void render(SafeHtmlBuilder sb);

void onUpdateText(XElement parent, String text);

void onUpdateIcon(XElement parent, Image icon);
}

public static class DefaultAppearance implements Appearance {

public interface Template extends XTemplates {
@XTemplate(source = "DefaultAppearance.html")
SafeHtml template(Style style);
}

public interface Style extends CssResource {

String testButton();

String testButtonText();

String testButtonImage();
}

private final Style style;
private final Template template;

public interface Resources extends ClientBundle {

@Source("DefaultAppearance.css")
Style style();
}

public DefaultAppearance() {
this((Resources)GWT.create(Resources.class));
}

public DefaultAppearance(Resources resources) {
this.style = resources.style();
this.style.ensureInjected();

this.template = GWT.create(Template.class);
}

@Override
public void onUpdateIcon(XElement parent, Image icon) {
XElement element = parent.selectNode("." + style.testButtonImage());
element.removeChildren();
element.appendChild(icon.getElement());
}

@Override
public void onUpdateText(XElement parent, String text) {
parent.selectNode("." + style.testButtonText()).setInnerText(text);
}

@Override
public void render(SafeHtmlBuilder sb) {
sb.append(template.template(style));
}
}

public AppearancePushButton(String text) {
this(text, (Appearance)GWT.create(DefaultAppearance.class));
}

public AppearancePushButton(String text, Appearance appearance) {
this.appearance = appearance;

SafeHtmlBuilder sb = new SafeHtmlBuilder();
this.appearance.render(sb);

setElement(XDOM.create(sb.toSafeHtml()));
setText(text);
sinkEvents(Event.ONCLICK);
}

@Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}

public void setText(String text) {
appearance.onUpdateText(getElement(), text);
}

public void setImage(Image icon) {
appearance.onUpdateIcon(getElement(), icon);
}
}

Colin Alworth
3 Jan 2012, 5:43 PM
The error message indicates that GWT is unable to figure out how to rebind your Template interface. Since that extends XTemplates, the rebind rule in com.sencha.gxt.core.Core should take care of this:


<generate-with class="com.sencha.gxt.core.rebind.XTemplatesGenerator">
<when-type-assignable class="com.sencha.gxt.core.client.XTemplates" />
</generate-with>

The fact that it is not doing so suggests that either an inherits statement is missing in your module file - can you post that file? If you were missing an inherits for Core or GXT though, you shouldn't even be able to subclass Component, or use XElement...

Can you also post a full gwt compile log? Sanitize where necessary, but I suspect there is some other error or warning that may shed some light on this.

With the exception of the error, I think you've got the right idea - have you tried making alternate appearances for gxt components?

jvahabzadeh
4 Jan 2012, 7:24 AM
Here is my module file:


<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='gwt3betatest1'>
<inherits name='com.sencha.gxt.ui.GXT'/>
<inherits name='com.google.gwt.user.User'/>
<inherits name='com.google.gwt.user.theme.clean.Clean'/>

<!-- Bind the default ButtonCell.Appearance. -->
<replace-with class="AppearancePushButton.DefaultAppearance">
<when-type-is class="AppearancePushButton.Appearance"/>
</replace-with>

<entry-point class='com.joev.gwttests.client.GWT3BetaTest1'/>
<source path='client'/>
<source path='shared'/>
</module>



I just have the inherits for com.sencha.gxt.ui.GXT . . but that inherits all modules, so I think I'm good there.

As to the build log, where is it? I'm building via Eclipse, and running in development mode. I thought maybe it was in ${projectname}/.gwt/.gwt-log, so I cleared that file and did a clean and build, but after the build, the .gwt-log file was still empty.

Colin Alworth
4 Jan 2012, 7:39 AM
Clean shouldn't be necessaryunless you are actually using it (from some other GWT widget), and I would place GXT after User.

Is the class in the replace-with correct? Shouldn't there be a package to go with it? Something like



<replace-with class="com.joev.gwttests.client.AppearancePushButton.DefaultAppearance">
<when-type-is class="com.joev.gwttests.client.AppearancePushButton.Appearance"/>
</replace-with>

jvahabzadeh
4 Jan 2012, 7:51 AM
I had gotten the impression that the fully-qualified name wasn't necessary from the Appearance Design Pattern article (http://www.sencha.com/blog/ext-gwt-3-appearance-design/) on the Sencha blogs... at the bottom with the PushButton references.

I tried putting in the fully qualified names in for replace-with, and I also switched the order of inherits for GXT and User... still the same issue.

Where is the gwt compile log file located?

Colin Alworth
4 Jan 2012, 8:44 AM
When you run the 'GWT Compile' within eclipse, it should be printed to the Console view, still in eclipse. If you use maven or ant, the output of those commands is the log.

Like I said, everything else looks correct, so I'll try with your code to build a sample project, see if I get the same issue. The log will help to see what other setup or config option might be stopping this from building correctly.

jvahabzadeh
4 Jan 2012, 10:38 AM
Colin,

Thanks - let me know your results.

As to the compile log, all I get is the following, if it's of any help:
DataNucleus Enhancer (version 1.1.4) : Enhancement of classes
DataNucleus Enhancer completed with success for 0 classes. Timings : input=45 ms, enhance=0 ms, total=45 ms. Consult the log for full details
DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

Colin Alworth
4 Jan 2012, 10:52 AM
That log appears to be the messages emitted by eclipse as it generates classes to work with the Google App Engine and it's data store. When in the Console, make sure you have the GWT compile log opened - there is a blue monitor icon on the right side of the Console view that lets you switch between all of the active consoles. With the console open, run the GWT compile, and use the blue icon's menu to find the correct log.

It should include at least the error message you posted above, and hopefully more errors as well.

jvahabzadeh
4 Jan 2012, 11:32 AM
Unfortunately, that is the only console I get.

I know what you mean about the blue monitor icon, I usually use that when I am running more than one program that dumps info to the console . . but when I compile the code we've been talking about in this thread, I only have just the one console - that displays the DataNucleus information I quoted in my previous post.

Colin Alworth
4 Jan 2012, 11:56 AM
That's interesting/annoying...

Here is what I get when clicking the red toolbox 'GWT Compile Project...' button in my Console view:

Compiling module com.example.Appearance_sample
Scanning for additional dependencies: file:/home/colin/workspaces/sencha/appearance-sample/src/com/example/client/AppearancePushButton.java
Computing all possible rebind results for 'com.example.client.AppearancePushButton.DefaultAppearance.Template'
Rebinding com.example.client.AppearancePushButton.DefaultAppearance.Template
Invoking generator com.sencha.gxt.core.rebind.XTemplatesGenerator
Creating XTemplate method template
Running class com.sencha.gxt.data.rebind.ValueProviderCreator
[WARN] Method getTestImage could not be found
[ERROR] No getter can be found, unable to proceed
[WARN] For the following type(s), generated source was never committed (did you forget to call commit()?)
[WARN] com.example.client.AppearancePushButton_DefaultAppearance_Style_testImage_ValueProviderImpl
[WARN] com.example.client.AppearancePushButton_DefaultAppearance_TemplateImpl
[ERROR] Errors in 'file:/home/colin/workspaces/sencha/appearance-sample/src/com/example/client/AppearancePushButton.java'
[ERROR] Line 63: Failed to resolve 'com.example.client.AppearancePushButton.DefaultAppearance.Template' via deferred binding
[ERROR] Cannot proceed due to previous errors

Notice the earlier error from the one you posted - the template can't compile correctly. Your template tries to use style.testImage, but there is no method called testImage (or getTestImage) in your Style, so compilation fails.

jvahabzadeh
4 Jan 2012, 12:14 PM
I was just about to post that I'd found it . . .

Somewhere along the way, in the Development Mode window, I noticed it complaining about getTestImage not existing...

I then found that the .html file had the wrong info.

This is what I had before (copied from the article) in the DefaultAppearance.html file:

<div class="{style.testButton}">
<div class="{style.testImage}"></div>
<div class="{style.testText}"></div>
</div>

And this is what I changed it to:

<div class="{style.testButton}">
<div class="{style.testButtonImage}"></div>
<div class="{style.testButtonText}"></div>
</div>

And it seems to work. Zero functionality, of course, but I haven't played with any changes to Appearance yet.

Colin Alworth
4 Jan 2012, 1:34 PM
Great! The xtemplate error messages do need some work, but they aren't a lot of good if you can't see them.

Let us know if you run into more issues, or have any suggestions on making this process easier! We anticipate that appearances will make it easier to declare custom ways to draw all components by default.

Remember also that an individual component instance can have a custom appearance - most/all GXT 3 components/cells have a constructor that takes an appearance. The <replace-with> option is great for changing all of them, but to change just a single one requires much less code.

Another hint: if your template takes only one parameter (style in your case), there is no need to refer to that data by name in the template - you can write this instead (as you would do in GXT 2):

<div class="{testButton}">
<div class="{testButtonImage}"></div>
<div class="{testButtonText}"></div>
</div>

Of course, if you plan on adding additional params, it is probably best to prefix the variables with the param name.

jvahabzadeh
5 Jan 2012, 1:00 PM
Ok, I wasn't sure if I should post these in separate threads or not, but here's a few things I'd offer as suggestions.

In the article about Appearance Design Pattern, I'd recommend:
1) For the "original" PushButton, change the styles in the css portion (and associated references in the code) to match what is later done with the Style interface for DefaultAppearance (that is, "testText" should be "testButtonText", "testImage" should be "testButtonImage" etc)

2) Likewise under the XTemplate section, change the styles referred to in the divs to match them as well.



Now, onto questions again!

I tried swapping in a new Appearance. It seems to be completely ignored.

Here's what I've done:
1) I created a separate class, called AppearanceType2, that implements AppearancePushButton.Appearance. It is identical to DefaultAppearance in EVERY way EXCEPT that AppearanceType2.Resources has a @Source line that refers to "Appearance2.css"

2) I created Appearance2.css, identical to DefaultAppearance.css except that the border color, font size, and padding for testButton is different.

3) In my project's .gwt.xml file, I changed the replace-with as follows:


<!-- <replace-with class="AppearancePushButton.DefaultAppearance"> THIS WAS THE ORIGINAL LINE -->
<replace-with class="com.joev.gwttests.AppearanceType2">
<when-type-is class="AppearancePushButton.Appearance"/>
</replace-with>



The results? Nothing! It's as if I made no changes whatsoever. What am I missing?

Also, if I wanted to add an onHover type of behavior to the AppearancePushButton (using the orignal Appearance interface and DefaultAppearance static class within AppearancePushButton), how would I correctly do this with the Appearance Design Pattern?


My source code for the new classes is below....

AppearanceType2:


package com.joev.gwttests.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.ui.Image;
import com.sencha.gxt.core.client.XTemplates;
import com.sencha.gxt.core.client.dom.XElement;

public class AppearanceType2 implements AppearancePushButton.Appearance {

public interface Template extends XTemplates {
@XTemplate(source = "DefaultAppearance.html")
SafeHtml template(Style style);
}

public interface Style extends CssResource {

String testButton();

String testButtonText();

String testButtonImage();

}

private final Style style;
private final Template template;

public interface Resources extends ClientBundle {

@Source("Appearance2.css")
Style style();
}

public AppearanceType2() {
this((Resources)GWT.create(Resources.class));
}

public AppearanceType2(Resources resources) {
this.style = resources.style();
this.style.ensureInjected();
this.template = GWT.create(Template.class);
}

@Override
public void onUpdateIcon(XElement parent, Image icon) {
XElement element = parent.selectNode("." + style.testButtonImage());
element.removeChildren();
element.appendChild(icon.getElement());
}

@Override
public void onUpdateText(XElement parent, String text) {
parent.selectNode("." + style.testButtonText()).setInnerText(text);
}

@Override
public void render(SafeHtmlBuilder sb) {
sb.append(template.template(style));
}
}


Appearance2.css:


.testButton {
border: 1px solid red;
font-size: 16px;
padding: 2px;
}

.testButtonImage {
float: left;
}

.testButtonText {
text-align: center;
text-decoaration: underline;
}


EDIT: I realize, by the way, that having a separate Appearance implementation is absurd to deal with only a .css change - I'm just trying it out to see if my use of different Appearance classes work, in anticipation of doing more with it....

Colin Alworth
5 Jan 2012, 4:50 PM
I should have caught this earlier, sorry about that.

The reason your change isn't having any affect is the same reason GWT didn't seem to care about the missing package name in those lines - in fact, if you deleted the <replace-with... altogether, it would still work. Here is why:


public AppearancePushButton(String text) {
this(text, (Appearance)GWT.create(DefaultAppearance.class));
}

This should be calling GWT.create on Appearance.class. The way that GWT.create works is that it first looks for the last declared replace-with or generate-with declaration, and if it doesnt find one, it calls the default constructor of the class. Since DefaultAppearance is a class, not an interface, this is legal.

If you had written


public AppearancePushButton(String text) {
this(text, (Appearance)GWT.create(Appearance.class));
}

Then you would have seen an error much earlier on, complaining that Appearance is abstract, and can't be created. Make this change and your replace-with should work correctly.

jvahabzadeh
6 Jan 2012, 5:52 AM
That fixed it, thanks!

So, um, I guess I'll add to my list of suggestions for the Appearance Design Pattern article to change that line of code in what you've provided . . so that it reads:

this(text, (Appearance)GWT.create(Appearance.class));
rather than

this(text, (Appearance)GWT.create(DefaultAppearance.class));

However, I'd also note that the replace-with clause and the when-class-is clause *does*, as you suggested earlier, require the fully-qualified class name with package. Just found that out as a result of this as well.... the DefaultAppearance.class bit was masking that problem.

I definitely appreciate all your help with this!