Finally, note that Appearance is an abstract class. This allows us to add more state logic in the future without breaking existing Appearances. For example, we could add new methods "setRightFlush()" and "setLeftFlush()" which would make the right and left edges of the button flat, such that they could be lined up next to each other. Existing appearances may not support the new feature, but they would still work.
Since the new method is probably something "obvious" I might already have implemented it. This is really evil because my code might still compile but not work any more.
Some Cells support methods to change how the Cell is rendered. For example, ButtonCell could provide a setTabIndex() method to set the tab index of the element. ButtonWidget would expose the same methods and forward through to the Cell.
> Finally, I don't really like the Widget suffix naming convention, that's
> what "namespaces" (packages in Java) are for (tell a Button from another
> Buton), but I sure could live with it.
> I already have those "naming conflicts" using Guava and gwt-dev –which
> includes a rebased, olderguava–, and some other library that transitively
> depends on a rebased Apache Commons, in the same project (so I have many
> "Lists" classes to choose from; not to mention java.util.List vs.
> jaa.awt.List, and java.util.Date vs. java.sql.Date). When I'm angry that
> eclipse always pick the wrong one by default, I configure it so it ignores
> the others when suggesting completions. Or maybe the GPE could plug into
> Eclipse to make it prefer the "new" Button so it's always displayed before
> the "old" one in such autocomplete or "organize imports" lists?
>
+1 for right name, different package (and aggressive deprecation, please).
> --
> http://groups.google.com/group/Google-Web-Toolkit-Contributors
Does it mean there could finally be a "Button" interface (implemented by both ButtonCell and ButtonWidget) as a few people have asked for: http://code.google.com/p/google-web-toolkit/issues/detail?id=5275
The introduction mentions this:
The assumptions that current widgets make regarding their structure
prevent us from modernizing existing widgets without breaking
applications that rely on the existing DOM structures.
Which is true, but I don't think the Appearance pattern will alleviate
this? E.g. if ButtonCell's default appearance outputs a certain DOM
structure (whether by SafeHtml/otherwise), aren't users' apps going to
couple themselves to the resulting DOM structure, either via custom
Element-based hacks, or CSS rules that use selectors based on the DOM
output?
Seems like you'll still end up with DefaultAppearances that can never
change?
(Not that this sinks the whole proposal, but I thought I'd point
it out. Though perhaps I'm missing something?)
- Stephen
Out of curiosity, would this allow Panels (all the way up to RootPanel)
to render their initial contents/major changes as one giant SafeHtml +
set innerHTML operation, CellTable-style?
If so, that seems pretty slick.
- Stephen
Out of curiosity, would this allow Panels (all the way up to RootPanel)
> Please take a look at the design doc and let me know what you think:
to render their initial contents/major changes as one giant SafeHtml +
set innerHTML operation, CellTable-style?
If so, that seems pretty slick.
Which is true, but I don't think the Appearance pattern will alleviate
this? E.g. if ButtonCell's default appearance outputs a certain DOM
structure (whether by SafeHtml/otherwise), aren't users' apps going to
couple themselves to the resulting DOM structure, either via custom
Element-based hacks, or CSS rules that use selectors based on the DOM
output?
Seems like you'll still end up with DefaultAppearances that can never
change?
On Fri, Feb 25, 2011 at 12:34 PM, Stephen Haberman <ste...@exigencecorp.com> wrote:Out of curiosity, would this allow Panels (all the way up to RootPanel)
> Please take a look at the design doc and let me know what you think:
to render their initial contents/major changes as one giant SafeHtml +
set innerHTML operation, CellTable-style?We're working toward that. We could add support for Cells in HtmlPanel, such that a UiBinder backed HtmlPanel containing only HTML and Cells would render as a sstring.
@Override
public void render(Context context, SafeHtml data, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<button type=\"button\" tabindex=\"-1\">");
if (data != null) {
sb.append(data);
}
sb.appendHtmlConstant("</button>");
}
@Override
public void render(Context context, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<button type=\"button\" tabindex=\"-1\">");
context.doChildren(sb);
sb.appendHtmlConstant("</button>");
}
True, but ButtonWidget would have addStyleName/removeStyleName.
John is right that appearances could be added/deprecated, but I was
thinking since the default cstr would always have to use the original
version, GWT would always have legacy shackles to it. (Though I also
realize now that, with GWT.create, either new defaults or old defaults
could be enabled/restored by importing a "I want the new appearance" or
"keep me on the old appearance" modules.)
- Stephen
1) The ButtonCell(DefaultAppearance.Resources) constructor can be confusing, I think it should be dropped.
This constructor gives the impression that you are using the app-wide Appearance where in fact you are using DefaultAppearance. This could lead to the following confusing use case:
- The user has two type of buttons: some initialized with new ButtonCell() and some with new ButtonCell(otherCellButtonResources)
- He changes the style app-wide via <replace-with>
- Buttons initialized with new ButtonCell() change, the ones initialized with new ButtonCell(otherCellButtonResources) don't.
Dropping that constructor would make it explicit you are actually forcing the use of DefaultAppearance.
2) Providing a custom DefaultAppearance.Resources lead to somewhat smelly code.
DefaultAppearance.Resources ties buttonCellStyle() to a specific CSS @Source. Therefore, I believe overriding it means writing something like:
public interface MyResources extends ButtonCell.DefaultAppearance.Resources {
@Override
@Source("com/mygroup/myapp/client/ButtonCell.css")
Style buttonCellStyle();
}
Maybe it's just me, but this looks slightly smelly. Also, does GWT.create() like two @Source for the same method?
3) Minor one-time style modifications are hard to make.
Defining a custom Appearance or Resources is good when you want to style all the buttons in your app, but impractical for small changes like adjusting padding for a single button. We should think about a way to allow that (and make sure it works in UiBinder). Ideas:
- Provide an addStyleName() in CellButton and Appearance. I don't like this, however, as it creates the problem mentioned by Stephen (see point 4 below) -- moreover, I really like the idea of Appearance moving us to more "semantic" styling.
- Provide a setProperty(String name, String value) in CellButton and Appearance and let Appearance apply it however it likes
- Provide semantic methods for frequently applied properties, like setMargin(), setPadding()...
I'm really not sure I like any of the above proposals however. Maybe we should just acknowledge that as a limitation of the new styling mechanism? Is it too limiting?
5) We should make sure GIN can be used to inject Appearance into ButtonCell and Resources into Appearance.
Weak coupling between ButtonCell, Appearance, and Resource seems like a perfect use-case for DI. I have not reviewed the code from that perspective, but it would be nice if we allowed users to subclass ButtonCell and Appearance to provide @Inject-ed versions.
6) I agree with Thomas about:
- Not being sure making Appearance an abstract class is the right choice.
- Favoring :hover and other pseudoclasses in the DefaultAppearance.
1) The ButtonCell(DefaultAppearance.Resources) constructor can be confusing, I think it should be dropped.
I would prefer the option. Lets say you have a 2 styles of buttons. A blue cancel button and a grey standard type button. With the current proposal, I would be able to use button and just inject different styles into the class. With your proposal, I have to extend button to be able to provide a different style than the norm for my cancel button, and what is worse, is that the style substitution is now buried in the .gwt.xml file. I'd much rather do my programming in the code than in .gwt.xml files. I would definitely like the ability to override styles without being forced to extend the widget classes.
3) Minor one-time style modifications are hard to make.
Is that really necessary as ButtonWidget extends CellWidget which extends Widget which gives you access to addStyleName and removeStyle name, making it easy to add padding and margin around widgets without having to get all Appearance.Resouce extendy.
Could/should we add javax.inject to the list of dependencies for GWT allowing for DI on some of the components out of the box?
I thinks there is a misunderstanding. My proposal is just to not provide the convenience constructor, so instead of:
new ButtonCell(resources);
You'd do:
new ButtonCell(new DefaultAppearance(resources));
Making it crystal-clear that your ButtonCell is using DefaultAppearance. The convenience constructor leads you to believe you're using "resources" together with the Appearance you bound in the .gwt.xml -- which is not true: you are using DefaultAppearance no matter which <replace-with> you provided.
Now, addStyleName is very limited since you can only style the wrapping <div>. It's good enough to add blank padding, but it wont let you make the internal part of the button wider or taller... Unless you use CSS type selectors but, as Stephen points out, this makes the app dependent on a specific widget style, and the app breaks as soon as this widget DOM structure changes. (So you can't easily upgrade to newer styles.)
In fact, I almost wish CellWidgets did not have a addStyleName given that it's really not that useful. I would rather have a slightly richer set of semantic styling methods in Appearance and having ways to access them from ButtonWidget (and UiBinder).
There are still two constructors:
- The default (empty) constructor uses GWT.create()
- A constructor accepting an Appearance. This appearance can be
instantiate with new, if you want to inject a Resources manually, or
with GWT.create if you're happy with the Appearance's default
resource.
> Now, addStyleName is very limited since you can only style the wrapping
> <div>. It's good enough to add blank padding, but it wont let you make the
> internal part of the button wider or taller... Unless you use CSS type
> selectors but, as Stephen points out, this makes the app dependent on a
> specific widget style, and the app breaks as soon as this widget DOM
> structure changes. (So you can't easily upgrade to newer styles.)
> In fact, I almost wish CellWidgets did not have a addStyleName given that
> it's really not that useful. I would rather have a slightly richer set of
> semantic styling methods in Appearance and having ways to access them from
> ButtonWidget (and UiBinder).
>
> All true, I guess if I'm looking to alter the height/width of a cell
> widget's internals or its internal structure I would expect to extend the
> bundle. If I'm just looking to do things like set the float, padding/margine
> addStyleName is the perfect tool. Maybe there is a better way for dealing
> with boilerplate code, maybe with UiBinder as the hook. I'm not sure I'm
> still a little foggy on the best practices when dealing with CssResources
> especially with UiBinder.
> Another option would be an enhancement GEP to make it codegen that
> boilerplate for you. Admittedly it doesn't get rid of the code smell, but it
> does save your fingers some work.
Ok, I think we're on the same page... I guess what I'd like is the
ability to both add semantic styling methods to Appearance (a-la
setFlatLeft, setFlatRight, setInnerWidth...) and to access these
methods from UiBinder.
(Also: GEP?)
John, I really like this idea. It's well thought-out and the customization hooks seem at once powerful and easy to use. I like the use of <replace-with> to provide a simple way to perform an app-wide appearance switch. Here are a couple of remarks:Sorry it's so long. Here is the tl;dnr version:1) The ButtonCell(DefaultAppearance.Resources) constructor can be confusing, I think it should be dropped.2) Providing a custom DefaultAppearance.Resources lead to somewhat smelly code.
3) Minor one-time style modifications are hard to make.
4) Contrary to Stephen, I think this design makes it hard to use selector-based CSS that assumes the widget obeys a precise DOM structure.5) We should make sure GIN can be used to inject Appearance into ButtonCell and Resources into Appearance.6) I agree with Thomas about:- Not being sure making Appearance an abstract class is the right choice
Agreed on (2), still not sure about (1) even after the discussing it
with Jeff. I'd like to see his reply on this. Also, I'd love to hear
your thoughts on why the extra constructor is not confusing in the use
case I proposed (and why it is needed).
>> 3) Minor one-time style modifications are hard to make.
>
> There's no "one-time style modification"; if you style an instance slightly
> differently, it's related to the context in which you use it, which means
> that you're likely to find that context a second time in your app (where
> you'd want/need to apply the same style modification); if not today, then
> tomorrow when you'll add a feature.
> It's similar to "I won't externalize this value –golden number– into a
> constant because it's only used once" (until it's used a second time, and
> you're still not sharing the value definition, so next time you'll want to
> change the value, you'll miss an instance).
> And it's actually not that hard to override a CssResource: 5 lines of Java
> and a CSS file.
I don't like the comparison with a golden number: I think design is
all about balancing the level of abstractions. Sure, using a golden
number is a smell showing a missing abstraction level, but you can't
say every decision about decreasing abstraction is similar to not
externalizing a golden number.
In that specific case, I disagree with you: in practice most people do
not build webapps by abstracting out all the possible styles that they
will need. The occasional float, the occasion padding, the occasional
wider button to match a margin, the occasional combination of
"flush-left + rounded-right" are typically described on a case-by-case
basis. This is for four reasons:
- Even in a well-styled app, styling is about choosing many components
from a tool box. Abstracting all the styles lead to hard-to-manage
combinatorial explosions (flush-left/rounded-right,
flush-left/flush-right, ...) In that case, 5 lines of Java and a CSS
resource are, in fact, a lot.
- Many people are happy trading good-styling for increased development speed.
- CSS is notoriously hard for most people, and is prone to
context-dependent behaviors. If you find a fix that correct the look
of a specific use of a widget, being forced to fix the global widget
style can prove very hard.
- It's really hard to apply aggressive refactoring to styling because
it's typically not part of automated tests.
Moreover, GWT already acknowledges that local/one-time styling can be
useful by providing support for UiBinder's local <ui:style> (among
other things).
Cheers,
Philippe
> --
> http://groups.google.com/group/Google-Web-Toolkit-Contributors
> On Saturday, February 26, 2011 9:14:37 PM UTC+1, Philippe Beaudoin wrote:
>> 1) The ButtonCell(DefaultAppearance.Resources) constructor can be
>> confusing, I think it should be dropped.
>> 2) Providing a custom DefaultAppearance.Resources lead to somewhat smelly
>> code.
>
> -1 on both the above
> (note: ClientBundle only reads the annotations from the method definition,
> it does not look at the overridden methods)
Agreed on (2), still not sure about (1) even after the discussing it
with Jeff. I'd like to see his reply on this. Also, I'd love to hear
your thoughts on why the extra constructor is not confusing in the use
case I proposed (and why it is needed).
new DefaultAppearance(Resource)
In its current form, I don't think the constructor accepting a
CssResource lets GWT swap the default appearance without impacting the
user's code that relies on it. Let's look at your proposed
implementation for the constructor in question:
// Replace the styles used by this cell instance.
public ButtonCell(DefaultAppearance.Resources resources) {
this(new DefaultAppearance(resources));
}
This constructor is not using deferred binding. Therefore, if you want
user's code relying on it to switch to the new appearance you have to
roll-out a new implementation of the constructor. Let's say you do
that:
// Replace the styles used by this cell instance.
public ButtonCell(DefaultAppearance.Resources resources) {
this(new NewDefaultAppearance(resources));
}
The problem, here, is that you are passing DefaultAppearance.Resources
to NewDefaultAppearance. It means that your new appearance
implementation cannot use CSS classes that did not exist in the
original CssResource. In fact, in the current implementation, the
CssResource is defined by the Appearance _implementation_ (it's
DefaultAppearance.Resource, not Appearance.Resource).
The confusion therefore is:
a) Will ButtonCell(DefaultAppearance.Resources resources) update its
appearance automatically when GWT upgrades to a new default
appearance; OR
b) Will it bind me forever to DefaultAppearance?
My proposition of dropping it was assuming you wanted to go with (b),
now I understand that you want the behavior of (a) -- which I agree is
much preferable -- but the current design does not seem to allow for
that goal. Maybe we should discuss ways to decouple the CssResource
from the Appearance implementations instead?
Also: I agree with the rest of your post and your reasoning.
I prefer the second one, as the first one does not make it obvious that:
new ButtonCell()
and
new ButtonCell(myResources)
lead to potentially different appearances. The first one using
DefaultAppearance2013 and the second using DefaultAppearance.
You say people will get to injecting the ClientBundle, but even if
they do that they are tied to the old appearance -- unless they change
the type of the injected ClientBundle wherever they use it. It's
clearer in code:
@Inject Provider<ButtonCell> buttonCellProvider;
@Inject Provider<DefaultAppearance.Resouces> resourcesProvider;
...
buttonCellProvider.get(); // Leads to DefaultAppearance2013
new ButtonCell(resourcesProvider.get()); // Leads to DefaultAppearance
If you inject the ClientBundle you cannot change the global appearance
simply by switching one binding. In other words: the convenience
constructor doesn't buy you anything.
(What you can do, however, is inject the appearance itself, and this
should be the recommended pattern given the fact that the ClientBundle
depends on the Appearance implementation.)
Cheers,
Philippe
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.
On Mon, Feb 28, 2011 at 1:34 PM, John LaBanca <jlab...@google.com> 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.
On Mon, Feb 28, 2011 at 3:14 PM, Jeff Larsen <lars...@gmail.com> wrote:
On Mon, Feb 28, 2011 at 1:34 PM, John LaBanca <jlab...@google.com> 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.
// This is the base theme the class that all theme should extends.// This annotation indicates that GWT.create should always return the same instance.@Singletonpublic class Theme {protected interface UI {// The creation is performed based on the @UiTemplate annotationpublic Element render();// Inject the CSS based on the @CssSource annotationinjectCss();}@UiTemplate("button.ui.xml");@CssSource("button.css")public interface ButtonUI extends UI {// Automatic wiring due to the @UiField annotationpublic void setLabel(Element element, @UiField("label") SafeHtml label);}private ButtonUI buttonUI = null;public final ButtonUI getButtonUI() {// Lazy loadingif (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 instancethis.theme = GWT.create(Theme.class)// Render the button, it creates a new DOM structure// The CSS should already be injectedsetElement(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 UIthis.label = label;// Update the DOM with the new labelthis.theme.getButtonUI().setLabel(getElement(), label);}}
<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>
.gwt-button { /* Style in here */ }.gwt-button icon { /* Style in here */ }.gwt-button label { /* Style in here */ }
public UglyTheme extends Theme {// Nothing in here}
<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>
.gwt-button { /* Style in here */ }.gwt-button icon { /* Style in here */ }.gwt-button label { /* Style in here */ }.gwt-button-scary { border: solid 10px #f00 }
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");
In particular, you should know that we're working on a change to
UiBinder to allow it to generate SafeHtmlRenderer instances, and then
to allow those instances to manage cell event handling. We'll share a
design proposal soon.
That said, there is still too much boilerplate and hackery required to
make a new one of these things. It does seem like we should be able to
require a lot less mindless configuration than we do. Making
FooWidget.java, Foo.css and Foo.ui.xml just work by existing, perhaps
allowing me to GWT.create(FooWidget.class) with no extra
configuration, seems like something we should make an explicit goal.
> --
> http://groups.google.com/group/Google-Web-Toolkit-Contributors
> --
> http://groups.google.com/group/Google-Web-Toolkit-Contributors
Nice. And will you have the shorter:
<progress value="250" max="1000" />
For permutations that are guaranteed to support it?