Announcement

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

    Problem: Sharing ListGridRecord between client and server

    I have a shared DTO objects that I want to edit on the client and then later persist on the server. Now, if I try to make my DTO object extend ListGridRecord, I get the following when trying to instantiate DTO object on server:

    com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract com.xxx.config.model.Config com.xxx.j2ee.config.client.ConfigService.getConfig(java.lang.String)' threw an unexpected exception: java.lang.UnsatisfiedLinkError: com.smartgwt.client.util.LogUtil.setJSNIErrorHandler()V

    Is it possible at all to use the same java class in a grid as a ListGridRecord and on the server?

    #2
    any solution?

    Have you found any solution to this? I'm having the same problem.
    Or is this totally wrong? (instantiate ListGridRecord -or Record for that matter- in server)

    Comment


      #3
      Since SmartGwt's ListGridRecord is a wrapper around a JavaScriptObject (which is the SmartClient *real* ListGridRecord), it makes sense that it may not be instantiated on the server side.


      What we do in our project is the following:

      Code:
      // Common interface
      public interface CompanyGwt {
      
          public static class Cloner {
              public static void clone(CompanyGwt from, CompanyGwt to) {
                  to.setId(from.getId());
                  to.setName(from.getName());
              }
          }
      
          void setId(String id);
          String getId();
      
          void setName(String name);
          String getName();
      
      }
      
      
      // ListGridRecord implementation of the common interface
      // As you can see, since a ListGridRecord is a simple wrapper around
      // a JavaScriptObject, we make our getters and setters "write through"
      // to the JavaScriptObject
      public class CompanyGwtRecord extends ListGridRecord implements CompanyGwt {
      
          public CompanyGwtRecord() {
              super();
          }
      
          public CompanyGwtRecord(JavaScriptObject jsObj) {
              super(jsObj);
          }
      
          @Override
          public String getId() {
              return getAttributeAsString("id");
          }
      
          @Override
          public String getName() {
              return getAttributeAsString("name");
          }
      
          @Override
          public void setId(String id) {
              setAttribute("id");
          }
      
          @Override
          public void setName(String name) {
              setAttribute("name", name);
          }
      
          public static CompanyGwtRecord copyOf(CompanyGwt from) {
              CompanyGwtRecord to = new CompanyGwtRecord();
              Cloner.clone(from, to);
              return to;
          }
      
      }
      
      
      
      
      // DTO implementation of the common interface
      public class CompanyGwtDto implements CompanyGwt, Serializable {
          private String id;
      
          private String name;
      
          public void setId(String id) {
              this.id = id;
          }
      
          public String getId() {
              return id;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getName() {
              return name;
          }
      
      }
      You can then program to the interface in your code. Use the GwtDto on the server side and in the RPC communication, and use the GwtRecord on the client side (which you can then pass to ListGrids, since it correctly wraps a JavaScript Object). To copy back and forth, use the static inner class Cloner defined in the common interface.

      Hope this helps


      PS: if you need to create a CompanyGwtRecord from a ListGridRecord, you can't simply cast it, you need to do something like new CompanyGwtRecord(myListGridRecord.getJsObj())

      Of course, you can add static builder methods to write that in a more consice way

      Comment


        #4
        That's a huge amount of extra effort. Take a look at this sample from SmartGWT Pro. No need to write additional "DTO" classes every time you need to access a new entity.
        Last edited by Isomorphic; 7 Jul 2009, 16:23.

        Comment


          #5
          Why don't you use some reflection utils to copy the listrecord properies in dto?(I didn't test that, it may not work)!

          Comment


            #6
            GWT doesn't support reflection client-side, hence the need to solve this on the server side unless you want to write extra DTO classes and a long list of setAttribute("blah", getBlah()) calls that need to be updated every time you add new fields.

            This is just one thing that Pro does for you - take a look at the overall feature set.

            Comment


              #7
              I was just curious if there is any other (better) solution for this problem. After some thinking, I came up with the following:

              Code:
              public class Employee {
              
                String name;
                String password;
                int age;
              
              }
              Code:
              public class EmployeeRecord extends ListGridRecord {
              
                Employee actualEmployee;
              
              }
              The second class is just a wrapper for a corresponding data object. It's really not beautiful, but it eliminates the code duplication. Will this even work? I haven't tested it yet, it's just an idea.

              Comment


                #8
                I do not think wrapping the Employee like this would work.

                As I understand it, SmartGwt's ListGridRecord class is actually a Javascript Overlay Type, which is a Java class that actually wraps a JavaScript object (more information on GWT's overlay types here.

                The idea is that SmartClient needs a javascript object to describe a ListGridRecord (which is actually a javascript map of names and values). But instead of manipulating these raw javascript objects, SmartGwt wraps them inside ListGridRecord, and adds java methods to access its properties more easily.

                Thus, in my example mentioned above, when I call setName() on my CompanyGwtRecord, it actually stores the name inside the JavaScript object (via setAttribute()), and not in a GWT variable.

                This translation between the real Java Object and the ListGridRecord's javascript object needs to be done at some point. In my case, I use a clone method:
                Code:
                public static class Cloner {
                        public static void clone(CompanyGwt from, CompanyGwt to) {
                            to.setId(from.getId());
                            to.setName(from.getName());
                        }
                    }
                This method works in both directions. If I clone from a CompanyGwtDto into a CompanyGwtRecord, the method will call the DTO's getter to retrieve the "java state" of the object, and then call the Record's setters which will in turn store the data in the underlying javascript object. If I do the opposite, the ListGridRecord's javascript object will be accessed through CompanyGwtRecord's getters, and the DTO's fields will be updated via their standard setters.

                Another solution would be to remove the CompanyGwt interface and the CompanyGwtRecord class, and add some kind of getListGridRecord() method on the CompanyGwtDto, as well as some kind of factory method that creates such a DTO from a ListGridRecord object. But, even in this case, you would need to write the getAttributeAsString() and setAttribute() methods, they would simply be located in one method.
                It would look like this (out of my head, with no available IDE, so sorry if the syntax isn't quite right):
                Code:
                public class CompanyGwtDto implements Serializable {
                    private String id;
                
                    private String name;
                
                    public void setId(String id) {
                        this.id = id;
                    }
                
                    public String getId() {
                        return id;
                    }
                
                    public void setName(String name) {
                        this.name = name;
                    }
                
                    public String getName() {
                        return name;
                    }
                
                
                    public ListGridRecord createListGridRecord() {
                	ListGridRecord record = new ListGridRecord();
                	record.setAttribute("id", id);
                	record.setAttribute("name", name);
                        return record;
                    }
                
                    public static CompanyGwtDto create(ListGridRecord record) {
                	CompanyGwtDto dto = new CompanyGwtDto();
                        dto.setId(record.getAttributeAsString("id");
                        dto.setName(record.getAttributeAsString("name");
                        return dto;
                    }
                
                }
                A third solution is to use SmartGwt EE which seems to avoid this boilerplate code elegantly (see the link posted by Isomorphic above), but I don't know how it works underneath so I can't comment.


                Regards,

                -TylerGalt
                Last edited by TylerGalt; 7 Jul 2009, 15:09.

                Comment


                  #9
                  SmartGWT EE / Pro uses server-side reflection. As shown in this example, complex nested structures can be delivered to the client, edited, and saved back to the server without writing any code.

                  This system handles complex cases like multiple levels of nesting, translating types including special cases like enums, autodetecting types from Java generics declarations, and also XPath-based declarative remapping of data between business objects and UI components that want to present a simplified viewing and editing interface.

                  Beyond just eliminating redundant DTOs, you don't even have to write an actual bean until you have real reason to - beanless mode works without one, allowing you to define a new Hibernate entity purely within Visual Builder and write the bean later (if you find you need one!).

                  All of the above included at the Pro level at $745 per developer, along with a huge list of other features. So guys, frankly a bit silly to still be brainstorming approaches here? It's a solved problem.

                  Comment


                    #10
                    I have now tried the "wrapper" approach, and it works for me.

                    Code:
                    public class EmployeeRecord extends ListGridRecord {
                    
                      Employee actualEmployee;
                    
                      public EmployeeRecord( Employee employee )
                      {
                        this.actualEmployee= employee;
                      }
                    
                      public Employee getEmployee()
                      {
                        return actualEmployee;
                      }
                    
                    }
                    create some test data:

                    Code:
                        List<EmployeeRecord> emp = Arrays.asList(
                            new EmployeeRecord(new Employee("Bernhold Betz", 33)),
                            new EmployeeRecord(new Employee("Harald Hosenbein", 44))
                        );
                    and display in ListGrid:

                    Code:
                      nameField.setCellFormatter(new CellFormatter() {  
                            public String format(Object value, ListGridRecord record, int rowNum, int colNum) {
                                Employee emp = ((EmployeeRecord) record).getEmployee();
                                return emp.getName();
                            }  
                       });

                    Comment


                      #11
                      I now better understand what you wanted to do.


                      But I think it's not the right design choice, and you'll have all sorts of problems when using SmartClient, because your ListGridRecord underlying javascript object actually contains NO DATA. In GWT, it does contain an employee, but SmartClient won't see it and won't access it.

                      I think many things won't work, from filtering, to sorting, to validating, and so on...

                      Regards,

                      -TylerGalt

                      Comment


                        #12
                        Hello Tyler,

                        you're probably right. At the moment, I am not using filtering or sorting. If I experience problems with this, I'll mention it here.

                        Comment

                        Working...
                        X