Hi,
I'm fairly new in the GWT development but I'm not sure that the selected
development answers the initial need. Let me explain you why (sorry it is
rather long). It is also very possible that I'm completely wrong.
The main goal is allowing the developers to easily theme their application.
As a developer I know I'm not good at designing a theme, in the end it
always looks ugly. What I want is to be able to browse the themes and say
"Oh this one looks nice, I'll pick that one!". And then, I just want to
download it, add it to my project, add one configuration line that says "now
on, use this theme".
A developer might also have a button in his application that is pretty
special: clicking it will erase everything in the database. He wants to have
a button that doesn't look like the other ones. He wants to be able to
define somewhere a new style for a button with a 10px red border (it's
probably good to ask for the help of a designer at this point...).
Now, lets turn around and have a look at the designer perspective. He wants
to create a theme, he had the nicest ideas. He doesn't really know how to
develop simply because it's not his job, but he is really good at creating
the CSS and HTML that match his Photoshop mockups. If the pre-existing DOM
structure of the widget doesn't match his needs he should be able to simply
redefine it.
A designer might also wants to tweak a little bit an existing theme (the
default one for instance). Imagine that he wants to update how Tabs are
rendered and just that. He should be able to do so.
Therefore I think that:
- Creating a theme should only require to create:
- a package that contains it
- a simple class that inherits the base theme (the default one or another
one).
- a .css file with the name of widget to redefine its style
- a .ui.xml file with the name of the widget in order to change its DOM
structure
- Using a theme should only require to point to the right theme class
The advantages are:
- The rendering of a widget and its style are defined in HTML-like and CSS
files.
- The themes are simple to create/update/use
- If the default theme changes or new widgets are added then there are no
impact on pre-existing themes.
Now pseudo code samples, it doesn't compile, it just illustrates.
First the base Theme class and the Button widget class.
// This is the base theme the class that all theme should extends.
// This annotation indicates that GWT.create should always return the same
instance.
@Singleton
public class Theme {
protected interface UI {
// The creation is performed based on the @UiTemplate annotation
public Element render();
// Inject the CSS based on the @CssSource annotation
injectCss();
}
@UiTemplate("button.ui.xml");
@CssSource("button.css")
public interface ButtonUI extends UI {
// Automatic wiring due to the @UiField annotation
public void setLabel(Element element, @UiField("label") SafeHtml label);
}
private ButtonUI buttonUI = null;
public final ButtonUI getButtonUI() {
// Lazy loading
if (buttonUI == null) {
buttonUI = GWT.create(ButtonUI.class);
buttonUI.injectCss();
}
return buttonUI;
}
// This defines the base style such as the font name and size.
// It isn't linked to any widget but rather to the "body" element
@CssSource("style.css")
public interface PageStyle extends CssResource {}
}
public class Button extends NewWidget {
private final Theme theme;
private SafeHtml label;
public Button() {
// This should always returns the same instance
this.theme = GWT.create(Theme.class)
// Render the button, it creates a new DOM structure
// The CSS should already be injected
setElement(this.theme.getButtonUI().render());
}
public Button(SafeHtml label) {
this();
setLabel(label);
}
public void setLabel(SafeHtml label) {
// We save it because we cannot retrieve it from the UI
this.label = label;
// Update the DOM with the new label
this.theme.getButtonUI().setLabel(getElement(), label);
}
}
The default button.ui.xml looks like that:
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
<button class='gwt-button'>
<span class='icon' ui:field='icon'/>
<span class='label' ui:field='label'/>
</button>
</ui:UiBinder>
The related button.css looks like that:
.gwt-button { /* Style in here */ }
.gwt-button icon { /* Style in here */ }
.gwt-button label { /* Style in here */ }
Now the new theme class:
public UglyTheme extends Theme {
// Nothing in here
}
...with its really ugly button.ui.xml:
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
<table class='gwt-button'>
<tr>
<td class='topleft' />
<td colspan='2' class='top' />
<td class='topright' />
</tr>
<tr>
<td class='left' />
<td class='icon' ui:field='icon' />
<td class='label' ui:field='label' />
<td class='right' />
</tr>
<tr>
<td class='bottomleft' />
<td colspan='2' class='bottom' />
<td class='bottomright' />
</tr>
</table>
</ui:UiBinder>
And we want a new custom look because the button can be scary, so we create
a new button.css
.gwt-button { /* Style in here */ }
.gwt-button icon { /* Style in here */ }
.gwt-button label { /* Style in here */ }
.gwt-button-scary { border: solid 10px #f00 }
And it is simply used like that:
Button btn = new Button("This one should be scary");
// If the scary style isn't defined, then it doesn't crashes since it's all
CSS.
btn.addStyleDependant("scary");
This proposal implies that:
- GWT.create can return singletons
- The .css and .ui.xml files are searched in the directory of the selected
theme and if it's not found then it looks in the directory of the parent
class and so on until it founds the default one.
- New class generation with automatic @UiField wiring
There are still several open points and right now I have no clue on how to
handle them. The major one is: how to handle the events suchs as clicks or
focus especially when the underlying DOM element doesn't support them.
I hope sharing this thought will help designing great widgets.
Antoine.
2011/3/11 John LaBanca <[email protected]>
> Sorry for the long delay. I just read through the email chain and tried to
> encapsulate the common concerns below:
>
> *Appearance: abstract class or interface*
> **I still think Appearance should be an abstract class. tbroyer is
> correct that adding a method might be a breaking change for some apps, but
> thats better than guaranteeing a breaking change. Adding a method to an
> interface also has the same problem that users might already implement the
> method and not realize the change, so I don't see any advantage of using an
> interface. We can document the fact that we may add methods to Appearance
> from time to time.
>
> *DefaultAppearance.Resources constructor is bad*
> I agree that these constructors might confuse users because they might not
> realize that using it precludes their apps from getting the latest version
> of Appearance. We can remove them for now. If users ask for us to add them
> later, we can always do so.
>
> *Hover methods*
> I agree that using :hover styles is better than catching mouseover/out
> events and styling manually. In general, we want to leverage CSS as much as
> possible to improve performance.
>
>
> I'm going to start implementing this and see how far I get.
>
> Thanks,
> John LaBanca
> [email protected]
>
>
> On Mon, Feb 28, 2011 at 4:10 PM, Philippe Beaudoin <
> [email protected]> wrote:
>
>>
>> On Monday, February 28, 2011 12:42:30 PM UTC-8, John LaBanca wrote:
>>>
>>> On Mon, Feb 28, 2011 at 3:14 PM, Jeff Larsen <[email protected]> wrote:
>>>
>>>
>>>>
>>>> On Mon, Feb 28, 2011 at 1:34 PM, John LaBanca <[email protected]>wrote:
>>>>
>>>>> Let me clarify what I had in mind for replacing the default GWT
>>>>> appearance. In the future, we might add a new DefaultAppearance
>>>>> implementation, but would leave the existing one. We would probably give
>>>>> it
>>>>> some trendy name, like ModernAppearance or DefaultAppearance2013, leaving
>>>>> DefaultAppearance. The GWT deferred binding for Appearance would be
>>>>> changed
>>>>> to point to DefaultAppearance2013.
>>>>>
>>>>> Using the default constructor will result in being automatically
>>>>> upgraded to the new appearance:
>>>>> new ButtonCell(); // Always uses the most recent Apperance.
>>>>>
>>>>> Using the Resources convenience constructor will use the old
>>>>> Appearance.
>>>>> new ButtonCell(myResources); // Uses DefaultAppearance. May be
>>>>> deprecated when new appearances are added.
>>>>>
>>>>> We would add a new convenience constructor for the new Appearance:
>>>>> public ButtonCell(DefaultAppearance2013.Resources resources);
>>>>>
>>>>> There is no way around the fact that DefaultAppearance.Resources are
>>>>> tied to DefaultAppearance and won't carry over to the new
>>>>> DefaultAppearance2013.
>>>>>
>>>>
>>>> That wouldn't have to be the case though would it?
>>>>
>>>> Couldn't we stick the Resource in the Appearance, then if the new
>>>> DefaultAppearance2013 for Button doesn't need to add new css definitions,
>>>> there then is no need to add an additional constructor or change up the
>>>> style definitions. Should something happen where DefaultAppearance2013
>>>> needs
>>>> additional classes you still have the option of creating a new ctor for
>>>> that
>>>> new theme, you've just given yourself some more options down the road. The
>>>> main drawback here is that the Appearance.Resources may have css classes
>>>> that are unused in descendant appearances. If that burden seems too high,
>>>> then there still is nothing stopping you from implementing the multiple
>>>> ctor
>>>> solution.
>>>>
>>> Thats actually a big problem. DefaultAppearance.Resources may specify a
>>> background gradient image, but in the future we can use CSS to specify a
>>> background gradient. So, we end up with an unused image and users are
>>> confused about why the gradient doesn't apply. Worse, if we want to add a
>>> style name or resources to the interface, thats a breaking change.
>>>
>>> Still, if DefaultAppearance2013 is a minor change that uses all of the
>>> same style names, we could subclass Appearance and use the same Resources.
>>> It would depend on how much of a change we make to the DOM structure.
>>>
>>
>> I agree, I think defining the ClientBundle outside the Appearance
>> implementation is a bit smelly... But, IMHO this smell is still present in
>> the constructor accepting a DefaultAppearance.Resources, and the
>> constructor-explosion problem mentioned by Jeff tends to confirm it. I think
>> we should acknowledge that ClientBundle depends on the Appearance
>> implementation and invite users to inject Appearance rather than the
>> ClientBundle.
>>
>> --
>> http://groups.google.com/group/Google-Web-Toolkit-Contributors
>>
>
> --
> http://groups.google.com/group/Google-Web-Toolkit-Contributors
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors