Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    SimpleType helper to get locale numeric & currency format for displaying and editing

    Hi,
    The code itself:
    Code:
    package org.yournamehere.client;
    
    import com.google.gwt.i18n.client.NumberFormat;
    import java.util.HashMap;
    
    /**
     * While it is fairly easy to display numeric and currency formats in SmartGWT
     * (SimpleType has setShort/NormalDisplayFormatter()),
     * I haven't been able to provide full locale format support including editing.
     * Although FormItem class has setEditorValueFormatter() and setEditorValueParser() methods,
     * and SimpleType class has setEditorType(FormItem editorType) method,
     * that way goes nowhere because simpleType.setEditorType(editorType) fires exception - something is broken or not yet done here.
     *
     * Desperately seeking a workaround I finally wrote the following code
     * putting together info I got from SmartGWT and SmartClient forums
     * and other sources.
     * This helper creates and registers custom numeric SimpleType
     * based on GWT i18n NumericFormat.
     * Such a SimpleType could be then used just as build-in types.
     *
     * @author michalg
     */
    
    public final class NumericTypeFactory {
    
        private static HashMap<String, NumberFormat> formatMap = new HashMap();
    
        private NumericTypeFactory(){}
    
        public static void registerNumericSimpleType(String name, NumberFormat format) {
            formatMap.put(name, format);
            createNumericSimpleType(name);
        }
    
        private static native void createNumericSimpleType(String name) /*-{
            $wnd.isc.ClassFactory.defineClass(name, "TextItem");
    
            $wnd.isc.ClassFactory.getClass(name).addProperties( {
                mapDisplayToValue : function(value) {
                    retVal = @org.yournamehere.client.NumericTypeFactory::parseNumericValue(Ljava/lang/String;Ljava/lang/String;)(name,value);
                    if (isNaN(retVal))
                        return value;
                    return retVal;
                },
                mapValueToDisplay : function(value) {
                    if (value == null)
                        return "";
                    else if (isNaN(value))
                        return value;
                    else {
                        return @org.yournamehere.client.NumericTypeFactory::formatNumericValue(Ljava/lang/String;D)(name,value);
                    }
                }
            });
    
            $wnd.isc.SimpleType.create({name:name,
                inheritsFrom:"float",
                editorType:name,
                normalDisplayFormatter:function(internalValue){
                    return this.shortDisplayFormatter(internalValue);
                },
                shortDisplayFormatter:function(value){
                    if (value == null)
                        return "";
                    else if (isNaN(value))
                        return value;
                    else {
                        return @org.yournamehere.client.NumericTypeFactory::formatNumericValue(Ljava/lang/String;D)(name,value);
                    }
                }
            });
        }-*/;
    
        private static String formatNumericValue(String name, double value) {
            return formatMap.get(name).format(value);
        }
    
        private static double parseNumericValue(String name, String value) {
            try {
                //System.out.println("parsing \"" + value + "\"");
                double result = formatMap.get(name).parse(value);
                //System.out.println("result: " + result);
                return result;
            } catch (Exception e) {
                return Double.NaN;
            }
        }
    
    }
    The sample use:
    Code:
    package org.yournamehere.client;
    
    import com.google.gwt.core.client.EntryPoint;
    import com.google.gwt.i18n.client.NumberFormat;
    import com.google.gwt.user.client.ui.RootPanel;
    import com.smartgwt.client.data.DataSource;
    import com.smartgwt.client.data.DataSourceField;
    import com.smartgwt.client.widgets.Button;
    import com.smartgwt.client.widgets.Canvas;
    import com.smartgwt.client.widgets.events.ClickEvent;
    import com.smartgwt.client.widgets.events.ClickHandler;
    import com.smartgwt.client.widgets.form.DynamicForm;
    import com.smartgwt.client.widgets.layout.VLayout;
    import com.smartgwt.client.widgets.viewer.DetailViewer;
    
    public class MainEntryPoint implements EntryPoint {
    
        public MainEntryPoint() {
            NumericTypeFactory.registerNumericSimpleType("localDecimal", NumberFormat.getDecimalFormat());
            NumericTypeFactory.registerNumericSimpleType("localCurrency", NumberFormat.getCurrencyFormat());
            NumericTypeFactory.registerNumericSimpleType("USCurrency", NumberFormat.getCurrencyFormat("USD"));
            NumericTypeFactory.registerNumericSimpleType("EUCurrency", NumberFormat.getCurrencyFormat("EUR"));
        }
    
        public void onModuleLoad() {
            RootPanel.get().add(getCanvas());
        }
    
        public Canvas getCanvas() {
    
            VLayout layout = new VLayout();
    
            DataSource ds = new DataSource();
            ds.setClientOnly(true);
    
            DataSourceField numberField = new DataSourceField();
            numberField.setName("localNumber");
            numberField.setAttribute("type", "localDecimal");
            ds.addField(numberField);
    
            DataSourceField currencyField = new DataSourceField();
            currencyField.setName("localMoney");
            currencyField.setAttribute("type", "localCurrency");
            ds.addField(currencyField);
    
            DataSourceField usdField = new DataSourceField();
            usdField.setName("USD");
            usdField.setAttribute("type", "USCurrency");
            ds.addField(usdField);
    
            DataSourceField eurField = new DataSourceField();
            eurField.setName("EUR");
            eurField.setAttribute("type", "EUCurrency");
            ds.addField(eurField);
    
    
            final DynamicForm form = new DynamicForm();
            form.setDataSource(ds);
    
            final DetailViewer view = new DetailViewer();
            view.setDataSource(ds);
    
            Button button = new Button("validate");
            button.addClickHandler(new ClickHandler() {
    
                public void onClick(ClickEvent event) {
                    form.validate();
                    view.setData(form.getRecordList());
                }
            });
    
            layout.addMember(form);
            layout.addMember(button);
            layout.addMember(view);
    
            return layout;
        }
    
    }
    HTH
    MichalG
    Attached Files

    #2
    Hello there,

    thanks for this useful info, which i'm currently using.


    I have an issue though, which i'm hoping someone could shed a light on.

    I have defined a custom type, "position", this way:

    Code:
    NumericTypeFactory.registerNumericSimpleType("position", NumberFormat.getFormat("###,###,##0.##"));
    and in a DS i have something like:
    Code:
    <field name="lon" type="position" title="Longitude" length="256" required="true"/>
    This works fine except for one thing which i can't grasp.

    If i have a form that i submit i get different datatypes in the valuemap depending on what i enter in the form field!

    if i type "1" -> Long on the server

    "1.2" -> Double on the server.

    Not sure what i have to do to make sure that smartgwt always converts it to double...

    pointers would be much appreciated!

    Comment


      #3
      Are you using EE version ?
      If so, I have no knowledge how javascript values are mapped to java objects on server side. We are using Rest datasources and this is done during serialization process - xtream library takes care of it.

      Also, have you tried with standard "float" type ? Does it have the same behavior ?
      MichalG

      Comment


        #4
        What's basically going on is that the server-side system is unaware of your SimpleType definition and so does not know that "position" extends "float". You can add an "isFloat" validator in your DataSource to force conversion to "float" (represented in Java with a Double).

        Comment


          #5
          @michalg - well, i'm using pro, so yeah. I have to map the field to "position" since that's what determines how the form handles and formats the values.

          I would have loved to use the "float" datatype and not have to use your solution, but as you know there's no way to define global, locale-based formatting rules for numeric values in smartgwt... as you can with dates.

          @Isomorphic - thanks, i'll try that.

          Comment


            #6
            Mathias
            Actually I meant whether float is also seen on the server as long or double as you enter 1 or 1.2
            Never mind, from what Isomorphic said I assume that float type is handled fine.
            Thanks,
            MichalG

            Comment


              #7
              @Michal - ah ok, yeah i think float works ok.

              @Isomorphic - the isfloat validator will prompt the user to enter a float, but i'd like the user to be able to enter both, but always convert it to float server-side. I'm not sure how this can be accomplished with a validator on the field?

              EDIT: Iso, i take it back, had put the validator in wrong place. Adding one to the field, like below, causes the conversion to float/double to always happen.

              Code:
                      <field name="lat" type="position" title="Latitude" length="256" required="true">
                          <validators>
                              <validator type="isFloat"/>
                          </validators>
                      </field>
              Last edited by mathias; 16 May 2011, 03:16.

              Comment


                #8
                @Michal G

                I have tried with your solution.But am still remains with now luck.

                TestDS.java

                NumericTypeFactory.registerNumericSimpleType("localCurrency", NumberFormat.getCurrencyFormat());

                final DataSourceField mailDepositAmt = new DataSourceField();
                mailDepositAmt.setName("AMOUNT_" + accSeqNum);
                mailDepositAmt.setAttribute("type", "localCurrency");

                In View Class :

                While Form Is created Setting DS to form as form.setDataSource(testDS);

                On Blur Event

                final BlurHandler blurHandler = new BlurHandler() {

                @Override
                public void onBlur(final BlurEvent event) {
                final DynamicForm form = event.getForm();

                view.setData(form.getRecordList());
                }
                });

                Basically i have TextBox once focus looses then Format Required.To match this implemented in the above way.

                If any body else tried this please help me out.

                Comment


                  #9
                  First of all, thank you for such great and usefull code...

                  I've a question: since I'm overriding the fields of the DynamicForm binded to the DataSource via setFields, how I get this work? I've tried declaring the currency field it as a FormItem and setting the type to one of the registered formats, but doesn't work

                  Best regards

                  Comment


                    #10
                    I am not sure what you mean by "overriding the fields via setFields". If you are trying to change DataSourceField type this way, then I think this is not a good idea.
                    You should only set type as SimpleType at DataSourceField level, define parsers and formatters and this will be used across all your DynamicForms all ListGrids application wide.
                    MichalG
                    ps
                    The code I posted initially is now a kind of obsolete - there is no need to use JSNI to define and register SimpleType any more.

                    Comment


                      #11
                      Hi michalg,

                      by "overriding the fields via setFields" I mean I'm declaring a DataSource and configuring the types of the DataSourceFields in there. Then I define a DynamicForm setting the DataSource and specifying the fields, if they're visible or not and some handlers on the FormItems in the DynamicForm constructor and applying them via DynamicForm.setFields().

                      The problem is when doing this, the SimpleType declared in DataSourceField is overriden by the FormItem declaration (tried FloatItem or TextItem too), even if I set then via setAttribute("type", "localCurrency") doesn't work. So that was the question, there's a way to get this work when configuring manually the DynamicForm?

                      Even if the code is kind of obsolete, it stills working very well. Thanks for your response. Best regards

                      Comment


                        #12
                        OK, so this is in general what I am doing, too.
                        You may have a look at my current SimpleType declaration:
                        Code:
                            private static SimpleType registerNumericSimpleType(final String name) {
                                SimpleType simpleType = new SimpleType(name, FieldType.FLOAT);
                                SimpleTypeFormatter simpleTypeFormatter = new SimpleTypeFormatter() {
                        
                                    public String format(Object value, DataClass field, DataBoundComponent component, Record record) {
                                        if (value != null) {
                                            try {
                                                return numberFormatMap.get(name).format(Double.valueOf(value.toString()));
                                            } catch (Exception e) {
                                                SC.logWarn("Formatting number " + value + " according tp " + numberFormatMap.get(name).getPattern() + " gives en error: " + e.toString());
                                                return value.toString();
                                            }
                                        }
                                        return null;
                                    }
                                };
                                simpleType.setShortDisplayFormatter(simpleTypeFormatter);
                                simpleType.setNormalDisplayFormatter(simpleTypeFormatter);
                                simpleType.setEditFormatter(simpleTypeFormatter);
                                simpleType.setEditParser(new SimpleTypeParser() {
                        
                                    public Object parseInput(String value, DataClass field, DataBoundComponent component, Record record) {
                                        if (value != null && !value.isEmpty()) {
                                            try {
                                                return numberFormatMap.get(name).parse(value);
                                            } catch (Exception e) {
                                                try {
                                                    return NumberFormat.getDecimalFormat().parse(value);
                                                } catch (Exception e1) {
                                                    SC.logWarn("Parsing " + value + " according to " + numberFormatMap.get(name).getPattern() + " gives en error: " + e1.toString());
                                                    return value;
                                                }
                                            }
                                        }
                                        return value;
                                    }
                                });
                                simpleType.setValidOperators(OperatorId.EQUALS, OperatorId.NOT_EQUAL, OperatorId.LESS_THAN,
                                        OperatorId.GREATER_THAN, OperatorId.LESS_OR_EQUAL, OperatorId.GREATER_OR_EQUAL,
                                        OperatorId.BETWEEN_INCLUSIVE, OperatorId.IS_NULL, OperatorId.NOT_NULL,
                                        OperatorId.EQUALS_FIELD, OperatorId.NOT_EQUAL_FIELD, OperatorId.GREATER_THAN_FIELD,
                                        OperatorId.LESS_THAN_FIELD, OperatorId.GREATER_OR_EQUAL_FIELD, OperatorId.LESS_OR_EQUAL_FIELD);
                                simpleType.register();
                                return simpleType;
                            }
                        
                            public static void setSystemWideCurrencyType() {
                                registerNumericSimpleType("currency");
                            }
                        and here is my extended FloatItem for currency:
                        Code:
                        public class ItemCurrency extends FloatItem implements HasItem {
                        
                            public ItemCurrency() {
                                init();
                            }
                        
                            public ItemCurrency(String name) {
                                super(name);
                                init();
                            }
                        
                            private void init() {
                                this.setType("currency");
                                this.setOperator(OperatorId.EQUALS);
                                this.setTextAlign(Alignment.RIGHT);
                            }
                        }
                        HTH
                        MichalG

                        Comment


                          #13
                          Thanks for the new version of the code. So the trick was to setting the type in the constructor. After the Item is created setting the type seems to have no effect

                          Thank you so much and best regards

                          Comment


                            #14
                            Hi michalg,

                            I have another question. I'm trying to register a another type of currency (with another name) using the Factory (as your first example) but I get the following message:

                            Code:
                            Error :Cannot change configuration property 'inheritsFrom' to float after the component has been created.
                            I thought it was problem with the static declaration so I've created another HashMap<String, SimpleType> to manage various SimpleType instances. The problem persisted. Then tried to do it non-static, but stills the same problem. I don't know why is trying to change the inheritedType the component has been created due this happens in the SimpleType constructor

                            Best regards

                            Comment


                              #15
                              Try to make it simple one module, runable test case and see if the problem persist.
                              if it does then you may eventually post your test code here.
                              MichalG

                              Comment

                              Working...
                              X