PDA

View Full Version : Enum-based ComboBox?



intrinsical
1 May 2009, 5:28 PM
Hi,

I'm still very new to GXT so please forgive me if this is a stupid question. I have the following enum:


public enum UserType {
Guest, User, Admin
}

I am trying to figure out how I can get a ComboBox to use this UserType's enum strings in its dropdown selection box, but store have the ComboBox update values based on the UserType's ordinal/integer value. I would appreciate it if anyone can provide me with directions or sample code.

sven
10 May 2009, 6:44 AM
You have to convert the enum to use ModelData

sdc
12 May 2009, 3:36 AM
Could it work with the @BEAN annotation + BeanModelMarker interface ?

sven
12 May 2009, 3:37 AM
No, as this are two different things

Colin Alworth
12 May 2009, 1:53 PM
My quick and dirty solution would be to use a simple modeldata as sven suggested, then use a custom Converter class to transfer back and forth.

Try this as a model object - this assumes that the toString() method of the enum has been overridden to display something useful (you aren't just displaying the PascalCased words on your otherwise pretty UI are you? Alternatively, you could make the constructor take an additional String param if you like...


public class EnumWrapper<E extends Enum<E>> extends BaseModelData {
public EnumWrapper(E enumValue) {
set("enum", enumValue);
set("value", enumValue.toString());
}
}Then the Converter subclass looks something like this:


public class EnumConverter<E> extends Converter {
public Object convertFieldValue(Object value) {
return new EnumWrapper<E>((E)value);
}
public Object convertModelValue(Object value) {
return ((EnumWrapper)value).get("enum");
}
}I think this is a good question, one which I will need answered for myself in the coming weeks, so if anyone has good/bad luck with this approach or finds another one, please share!



FWIW, I am find a lot of cases where a Converter class is essential to get many faucets of GXT working well. They are ugly to have to patch in, as you only want a single instance of each one, and Java's syntax makes them rather verbose - at least 6 lines of code before you even start adding custom code to the thing... Anyone have better ideas on how some of this could be implemented?

I am aware that it is a big deal to get the ComboBox working well, especially if you want it to contain complex data, but this whole idea of using a Map<String, Object> (i.e. ModelData) feels like you are trying to write Javascript components in Java. And while yes, it will all be compiled to Javascript in the end, the entire point of using Java is to stay far away from Javascript ;)...

jadrake75
13 May 2009, 9:24 AM
This is exactly what I have done in my application. It would be nice to perhaps have an EnumConverter OOTB with GXT. One exception is I would say there should be a public method "toDisplayString( Enum val )" which can convert the enum (or internal name) of the selection to a proper display string using the localized resources. By default this could simply do a val.toString( ) unless implemented.

intrinsical
13 May 2009, 10:00 PM
I agree.

I have been evaluating Gxt for the past month and while I feel that Gxt is a nice library, it suffers from little pieces of complexities like this that makes it not only frustrating to use, but also seriously slows down development speed. I'm spending more time figuring out how to do get Gxt to do what I want than actually developing my web app.

Btw, this is the first time I've even heard about Converters. How would I plug this converter into the grid editor?

Colin Alworth
14 May 2009, 5:42 AM
Converters are attached to FieldBinding objects, and are only used when binding occurs. While using the CellEditor class, it appears you could subclass it and override the preProcessValue(Object) and postProcessValue(Object) methods to act like a Converter Object.

In my opinion, the Converter object should be pluggable into the CellEditor using those methods, much like the FieldBinding object accepts an optional Converter. Not doing so is rather inconsistent...

But aside from these inconsistencies, I have to say that using GWT (and the GXT library) yields slightly more rapid initial development, but also far faster subsequent development. It isn't entirely due to _using_ the language, but more to the assurances that Java gives you in terms of types, and so the nice refactoring tricks you can do. Eclipse's 'find all references' and Javadoc have made my work with these libraries much simplier than any AJAX project that I have undertaken before.

I agree that GXT is not fully mature yet, but then again, this is a milestone release. The changes discussed in this thread are API breaking, so I don't know if we will get them with the 2.0 release (can a dev comment on this?), but at least the Converter+CellEditor issue seems to be a pretty obvious one, with very little additional code required. I'm not sure I agree that it actually slows down development time, but it does have a long ways to go.

bigmountainben
21 May 2009, 2:55 PM
You can also use the BeanModelMarker method:


@BeanModelMarker.BEAN(MyEnum.class)
public class MyEnumModel implements BeanModelMarker { }


And then you can use the BeanModel in your combo box. Add a getDescription to your Enum:


Enum UserType {

User("A User"),
Admin("An Admin");

String description;

UserType(String desc) { this.description = desc; }

private String getDescription() { return description; }
}


And set the combo box's displayField to be 'description'.

comboBox.getValue().getBean() will get you back the Enum.

Ben

Colin Alworth
21 May 2009, 6:15 PM
Clever - I haven't done any with the bean code, so its all sorta magic in my book still ;-). My next project is going to have to include some experimentation with these things.

How about working with GWT's localizable constant code to make the interface change text based on language? By this pattern, the Enum is written in the server code, so it can't interact with the GWT.create command for injecting strings...

Thanks for the cool suggestion - any thoughts?

bigmountainben
22 May 2009, 8:53 AM
Sure, that would work too:



enum MyEnum {

Admin(Util.getMessages().adminDesc());
User(Util.getMessages().userDesc());
...
}


etc.

where


final class Util {

static MyMessages messages;

static {
messages = GWT.create(MyMessages.class);
}

static public MyMessages getMessages() { return messages; }

...
}

Nice thing too is you can always user enum.name() and Enum.valueOf() as a way to go back and forth from a string rep (which is what I use for xml serialization, for example), and it continues to work with i18n.

sape
23 Jun 2009, 5:36 AM
My quick and dirty solution would be to use a simple modeldata as sven suggested, then use a custom Converter class to transfer back and forth.

Try this as a model object - this assumes that the toString() method of the enum has been overridden to display something useful (you aren't just displaying the PascalCased words on your otherwise pretty UI are you? Alternatively, you could make the constructor take an additional String param if you like...


public class EnumWrapper<E extends Enum<E>> extends BaseModelData {
public EnumWrapper(E enumValue) {
set("enum", enumValue);
set("value", enumValue.toString());
}
}Then the Converter subclass looks something like this:


public class EnumConverter<E> extends Converter {
public Object convertFieldValue(Object value) {
return new EnumWrapper<E>((E)value);
}
public Object convertModelValue(Object value) {
return ((EnumWrapper)value).get("enum");
}
}I think this is a good question, one which I will need answered for myself in the coming weeks, so if anyone has good/bad luck with this approach or finds another one, please share!



FWIW, I am find a lot of cases where a Converter class is essential to get many faucets of GXT working well. They are ugly to have to patch in, as you only want a single instance of each one, and Java's syntax makes them rather verbose - at least 6 lines of code before you even start adding custom code to the thing... Anyone have better ideas on how some of this could be implemented?

I am aware that it is a big deal to get the ComboBox working well, especially if you want it to contain complex data, but this whole idea of using a Map<String, Object> (i.e. ModelData) feels like you are trying to write Javascript components in Java. And while yes, it will all be compiled to Javascript in the end, the entire point of using Java is to stay far away from Javascript ;)...

I tried this approach with the classes you provided but the EnumConverter doesn't even compile. Can you maybe provide me with a working example of the code and how to use it in practice?

Colin Alworth
23 Jun 2009, 5:50 AM
It would help to know what doesnt build... After I posted, I copied my own code into our project in case someone needed it later, and after brief visual inspection, I don't see any differences between my code that builds and the sample I posted...

The only thing I see is the make the generic parameters the same in both classes, but that can also be achieved by making one the inner class of the other.

sape
23 Jun 2009, 6:02 AM
It would help to know what doesnt build... After I posted, I copied my own code into our project in case someone needed it later, and after brief visual inspection, I don't see any differences between my code that builds and the sample I posted...

The only thing I see is the make the generic parameters the same in both classes, but that can also be achieved by making one the inner class of the other.

The compiles starts to complain about the generics on the line where you create a new EnumWrapper of type E which will complain about that it's not a compatible type.

When I change the the E to


public class EnumConverter<E extends Enum<E>> extends Converter {

public Object convertFieldValue(Object value) {
return new EnumWrapper<E>((E) value);
}

public Object convertModelValue(Object value) {
return ((EnumWrapper) value).get("enum");
}
}


Than the code compiles but the convertion goes wrong. I guess I am doing something wrong.

I created the EnumConverter like this:


final FieldBinding fieldBinding = new FieldBinding(tarrifType, "tarrifType");
fieldBinding.setConvertor(new EnumConverter<TarrifType>());

binding.addFieldBinding(fieldBinding);


With TarrifType being the Java enum on my POJO.

Used this code to populate the combobox (which doesn't work):


final TarrifType[] tarrifTypes = TarrifType.values();

ListStore<ModelData> store = new ListStore<ModelData>();
for (TarrifType type : tarrifTypes) {
EnumWrapper<TarrifType> tarrifTypeWrapper = new EnumWrapper<TarrifType>(type);
store.add(tarrifTypeWrapper);
}

tarrifType.setStore(store);


So can you please point me out what I am doing wrong here?

Thanks

Colin Alworth
23 Jun 2009, 6:12 AM
I am assuming that you are using a ComboBox for this then, probably ComboBox<EnumWrapper<TarrifType>>. Did you call ComboBox.setDisplayField("value")? or perhaps change the wrapper to call set("text", enumValue.toString())? ComboBox assumes that there is a property called text, or that you will set a custom template (or display field) to handle the model objects.

sape
23 Jun 2009, 6:20 AM
I am assuming that you are using a ComboBox for this then, probably ComboBox<EnumWrapper<TarrifType>>. Did you call ComboBox.setDisplayField("value")? or perhaps change the wrapper to call set("text", enumValue.toString())? ComboBox assumes that there is a property called text, or that you will set a custom template (or display field) to handle the model objects.

Ah ok thanks.. that was one of the problems but I also needed to change the code to look like this instead:


public class EnumConverter<E extends Enum<E>> extends Converter {

@SuppressWarnings("unchecked")
public Object convertFieldValue(Object value) {
return ((EnumWrapper) value).get("enum");
}

@SuppressWarnings("unchecked")
public Object convertModelValue(Object value) {
return new EnumWrapper<E>((E) value);
}
}


This because my Model has the enum TarrifType and not a EnumWrapper. The code you posted was the other way around.

Thanks again :D

Arno.Nyhm
17 Sep 2009, 3:41 AM
i found also this idea from gslender:


I use this...


public enum FreqType {
DAILY("Daily"), WEEKLY("Weekly"), FORTNIGHTLY("Fortnightly"), MONTHLY("Monthly"), QUARTERLY("Quarterly"), YEARLY("Yearly");

private String name;

FreqType(String name) {
this.name = name;
}

public String toString() {
return name;
}
}and then I use this in SimpleCombo and POJO like...



freqTypeFld = new SimpleComboBox<FreqType>();
freqTypeFld.setFieldLabel("Frequency");
freqTypeFld.add(Arrays.asList(FreqType.values()));
freqTypeFld.setEmptyText("Choose...");
freqTypeFld.setEditable(false);
freqTypeFld.setAllowBlank(false);

....

freqTypeFld.setSimpleValue(mypojo.getFreq());
see: http://www.extjs.com/forum/showthread.php?p=277382#post277382

Arno.Nyhm
17 Sep 2009, 4:27 AM
You can also use the BeanModelMarker method:


@BeanModelMarker.BEAN(MyEnum.class)
public class MyEnumModel implements BeanModelMarker { }
And then you can use the BeanModel in your combo box. Add a getDescription to your Enum:


enum UserType {

User("A User"),
Admin("An Admin");

String description;

UserType(String desc) { this.description = desc; }

private String getDescription() { return description; }
}
And set the combo box's displayField to be 'description'.

comboBox.getValue().getBean() will get you back the Enum.

Ben

i like this suggestion, but how i use this enum with the combobox?


it start some code but this not works:


ComboBox cb = new ComboBox<UserType>();maybe i need to use the BeanModelReader? but i found only examples with the RpcProxy. but i dont need the enum via a proxy? or need i to write a enum->bean proxy?

maybe someone have a hint for me how to solve this!?

Arno.Nyhm
17 Sep 2009, 5:05 AM
maybe the idea with the beanmodel is not working?

i get this compile errors:


Refreshing TypeOracle
Processing types in compilation unit: file:/C:/NetBeansProjects/GXTTest1/src/java/com/mycompany/myapplication/client/customer/test/UserTypeBeanModel.java
Found type 'UserTypeBeanModel'
Resolving annotation '@BeanModelMarker.BEAN(value = UserType.class)'
[ERROR]
java.lang.ClassNotFoundException: com.mycompany.myapplication.client.customer.test.UserType
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:247)
at com.google.gwt.dev.javac.TypeOracleMediator.getClassLiteral(TypeOracleMediator.java:763)
at com.google.gwt.dev.javac.TypeOracleMediator.getAnnotationElementValue(TypeOracleMediator.java:674)
at com.google.gwt.dev.javac.TypeOracleMediator.createAnnotationInstance(TypeOracleMediator.java:442)
at com.google.gwt.dev.javac.TypeOracleMediator.resolveAnnotation(TypeOracleMediator.java:836)
at com.google.gwt.dev.javac.TypeOracleMediator.resolveAnnotations(TypeOracleMediator.java:857)
at com.google.gwt.dev.javac.TypeOracleMediator.resolveTypeDeclaration(TypeOracleMediator.java:1384)
at com.google.gwt.dev.javac.TypeOracleMediator.addNewUnits(TypeOracleMediator.java:389)
at com.google.gwt.dev.javac.TypeOracleMediator.refresh(TypeOracleMediator.java:417)
at com.google.gwt.dev.javac.CompilationState.refresh(CompilationState.java:179)
at com.google.gwt.dev.javac.CompilationState.<init>(CompilationState.java:93)
at com.google.gwt.dev.cfg.ModuleDef.getCompilationState(ModuleDef.java:264)
at com.google.gwt.dev.Precompile.precompile(Precompile.java:283)
at com.google.gwt.dev.Compiler.run(Compiler.java:170)
at com.google.gwt.dev.Compiler$1.run(Compiler.java:124)
at com.google.gwt.dev.CompileTaskRunner.doRun(CompileTaskRunner.java:88)
at com.google.gwt.dev.CompileTaskRunner.runWithAppropriateLogger(CompileTaskRunner.java:82)
at com.google.gwt.dev.Compiler.main(Compiler.java:131)

...
Scanning for additional dependencies: jar:file:/C:/NetBeansProjects/GXTTest1/lib/gwt/gxt-2.0.1/gxt.jar!/com/extjs/gxt/ui/client/data/BeanModelLookup.java
Computing all possible rebind results for 'com.extjs.gxt.ui.client.data.BeanModelLookup'
Rebinding com.extjs.gxt.ui.client.data.BeanModelLookup
Invoking <generate-with class='com.extjs.gxt.ui.rebind.core.BeanModelGenerator'/>
[ERROR] Class com.extjs.gxt.ui.client.data.BeanModelLookup not found.
java.lang.NullPointerException
at com.extjs.gxt.ui.rebind.core.BeanModelGenerator.getMarkerBean(BeanModelGenerator.java:184)
at com.extjs.gxt.ui.rebind.core.BeanModelGenerator.generate(BeanModelGenerator.java:54)
at com.google.gwt.dev.cfg.RuleGenerateWith.realize(RuleGenerateWith.java:49)
at com.google.gwt.dev.shell.StandardRebindOracle$Rebinder.tryRebind(StandardRebindOracle.java:113)
at com.google.gwt.dev.shell.StandardRebindOracle$Rebinder.rebind(StandardRebindOracle.java:62)
at com.google.gwt.dev.shell.StandardRebindOracle.rebind(StandardRebindOracle.java:172)
at com.google.gwt.dev.shell.StandardRebindOracle.rebind(StandardRebindOracle.java:161)
at com.google.gwt.dev.Precompile$DistillerRebindPermutationOracle.getAllPossibleRebindAnswers(Precompile.java:204)
at com.google.gwt.dev.jdt.WebModeCompilerFrontEnd.doFindAdditionalTypesUsingRebinds(WebModeCompilerFrontEnd.java:128)
at com.google.gwt.dev.jdt.AbstractCompiler$CompilerImpl.process(AbstractCompiler.java:151)
at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:444)
at com.google.gwt.dev.jdt.AbstractCompiler$CompilerImpl.compile(AbstractCompiler.java:85)
at com.google.gwt.dev.jdt.AbstractCompiler$CompilerImpl.compile(AbstractCompiler.java:181)
at com.google.gwt.dev.jdt.AbstractCompiler$CompilerImpl.access$400(AbstractCompiler.java:71)
at com.google.gwt.dev.jdt.AbstractCompiler.compile(AbstractCompiler.java:473)
at com.google.gwt.dev.jdt.WebModeCompilerFrontEnd.getCompilationUnitDeclarations(WebModeCompilerFrontEnd.java:73)
at com.google.gwt.dev.jjs.JavaToJavaScriptCompiler.precompile(JavaToJavaScriptCompiler.java:259)
at com.google.gwt.dev.Precompile.precompile(Precompile.java:300)
at com.google.gwt.dev.Compiler.run(Compiler.java:170)
at com.google.gwt.dev.Compiler$1.run(Compiler.java:124)
at com.google.gwt.dev.CompileTaskRunner.doRun(CompileTaskRunner.java:88)
at com.google.gwt.dev.CompileTaskRunner.runWithAppropriateLogger(CompileTaskRunner.java:82)
at com.google.gwt.dev.Compiler.main(Compiler.java:131)
[ERROR] Errors in 'jar:file:/C:/NetBeansProjects/GXTTest1/lib/gwt/gxt-2.0.1/gxt.jar!/com/extjs/gxt/ui/client/data/BeanModelLookup.java'
[ERROR] Line 33: Failed to resolve 'com.extjs.gxt.ui.client.data.BeanModelLookup' via deferred binding

chalu
28 Aug 2010, 2:50 AM
Hmmm.... all this just to load a Combo with values from a construct (Enum) that itemizes things. I think something is wrong somewhere, I really hope this gets simplified. Thanks fro your suggestions.

bpossolo
15 Apr 2011, 8:12 AM
The solutions above all sound pretty overkill however they may be a bit outdated.

You can simply leverage SimpleComboBox to list enums as follows:

public enum Condition {


New,
Used,
Damaged
}

SimpleComboBox<Condition> conditionCombo = new SimpleComboBox<Condition>();

for( Condition condition : Condition.values() )

conditionCombo.add(condition);


You can control the text that renders by overriding the Condition.toString() method.

if you need more power over what is displayed (for example, multilingual apps), then you can either override the SimpleComboBox methods (or provide a property editor) or with some clever tricks on the SimpleComboValue class (which the SimpleComboBox uses internally)

Colin Alworth
15 Apr 2011, 4:36 PM
The code really hasn't changed too much, and while you could override those things, they would inevitably need a switch case, or a map lookup, or something to finally get the i18n'd value. If you have many enums throughout your app, it quickly becomes clear that the i18n has to be on the data side of things, not just in subclassing the view over and over.

The_Jackal
17 Apr 2011, 8:31 PM
I had a case where I didn't want to use the enum's toString() method, rather I wanted to call a getLabel() method that does my GWT.create() call for i18n.

I didn't want to use toString() for the i18n as I want toString() to still work on the server side.

Here's the code to make to call a different method to get the text representation. It does the following:

Convert the value in the ComboBox with a property editor to call getLabel()
Convert the values in the list with a ModelProcessor to call getLabel() and Template


The ComboBox that uses Condition enum (with a getLabel() method for i18n)


final SimpleComboBox<Condition> combo = new SimpleComboBox<Condition>();
combo.setFieldLabel("Condition");
combo.setName("condition");
combo.add(Arrays.asList(Condition.values()));
combo.setEditable(false);
combo.setAllowBlank(false);
combo.setTriggerAction(TriggerAction.ALL);
combo.setSimpleValue(Condition.NEW);




// Replace the text in the box with the enums label
ListModelPropertyEditor<SimpleComboValue<Condition>> propEditor =
new ListModelPropertyEditor<SimpleComboValue<Condition>>()
{
public String getStringValue(SimpleComboValue<Condition> value) {
return value.getValue().getLabel();
}
};

propEditor.setDisplayProperty("label");
combo.setPropertyEditor(propEditor);

// Replace the text in the list with the enums labels
combo.getView().setModelProcessor(new ModelProcessor<SimpleComboValue<Condition>>() {
public SimpleComboValue<Condition> prepareData(SimpleComboValue<Condition> model) {
model.set("label", model.getValue().getLabel());
return model;
}
});

// Use getLabel in list (by default SimpleComboBox displayProperty "value" is used - calls enum toString)
String html = "<tpl for=\".\"><div role=\"listitem\" class=\"" + combo.getListStyle() + "-item\">{" + "label" + "}</div></tpl>";
combo.setTemplate(html);


Verbose, but not too tricky. Hope this helps someone do the same.