January 9th, 2008 · Posted by Tejus Parikh · 5 Comments

GWT Dynamic Table in Appcelerator

Appcelerator started as an internal framework to help us build solutions very quickly for our clients. When we say “More App. Less Code,” it’s because we really believe it to be true. We can pontificate endlessly about it, but unless we can prove our claim, it doesn’t mean anything. To that end, we’ve embarked on a quest to see how our solution compares against the others.

One prominent competitor is the Google Web Toolkit (GWT). There’s a lot we like about GWT and a lot we share in common. We’re both Open-Source, provide cross-browser functionality without plugins, give you widgets so that you don’t have to write html, and are Atlanta-based. Two key features that set Appcelerator apart from GWT are Appcelerator’s Web Expression Language and integration with prominent server-side frameworks.

The former probably has the greatest impact in code size. Lets look at a sample call to a “service” in GWT (taken from their doc):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void menuCommandEmptyInbox() {
  MyEmailServiceAsync emailService = (MyEmailServiceAsync) 
          GWT.create(MyEmailService.class);
 
 
  ServiceDefTarget endpoint = (ServiceDefTarget) emailService;
  String moduleRelativeURL = GWT.getModuleBaseURL() + "email";
  endpoint.setServiceEntryPoint(moduleRelativeURL);
 
  AsyncCallback callback = new AsyncCallback() {
    public void onSuccess(Object result) {
      // do some UI stuff to show success
    }
 
    public void onFailure(Throwable caught) {
      // do some UI stuff to show failure
    }
  };
  emailService.emptyMyInbox(fUsername, fPassword, callback);
}

This is how you do the same thing in Appcelerator:

1
<button on="click then r:mail.empty.mailbox">Empty Mailbox</button>

To take things a step further, we decided to implement one of their examples in our language to see how it compared. Their Dynamic Table example appeared to be suitable benchmark, as it was non-trivial, but simple enough to illustrate the development benefits of Appcelerator.

Even with its simplicity, the Dynamic Table is a lot of Java code. Google’s implementation has 8 classes dedicated to application functionality, model objects not included. These classes amount to about 400 lines of code (sans comments and whitespace) that you have to write. Yes GWT does save you from having to deal with the quirky implementations of javascript provided by browers, but at the cost of a lot of additional Java.

Our implementation of the GWT Dynamic Table has one html file, one class for the service, and one class for data-access. We also copied the model objects directly from the Google example. Our java source for servicing requests is 100 lines. Our html file is also about 75 lines for a grand total of 175 lines of stuff. That’s less than half of what was needed to do the same thing in GWT. Even more telling is that the Appcelerator version includes communication with a database, while the GWT version is in-memory only.

I showed earlier in this post how the power of the Web Expression Language greatly simplifies communication with the back-end server. Lets take a look at some other features in Appcelerator that allow us to create simpler, and more compact, code.

Front-End
First, look at the code for rendering the dynamic table. This code is found in index.html:

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<div style="display:none" id="results_table" class="content_container" 
        on="r:dynatable.has.data.response[data=true] then show">
    <app:pagination id="schedule_pagination" request="r:dynatable.list.request" 
            response="r:dynatable.list.response"  
            resultsString="#{start}-#{end}" totalsString="of #{total}" nextText="next"  
            prevText="prev" showTotals="true" startProperty="start" endProperty="end"
            totalProperty="total" fieldset="days">  
    </app:pagination>
    <div class="content">
		<app:iterator id="dynatable" on="r:dynatable.list.response then execute" 
                           property="people" table="true" headers="Name,Description,Schedule"
		           rowEvenClassName="even_row" rowOddClassName="odd_row">
			<html:td class="name">
				#{name}
			</html:td>
			<html:td class="description">
				#{description}
			</html:td>
			<html:td class="schedule">
                                #{schedule}				
			</html:td>
		</app:iterator> 
    </div>
</div>

The app:pagination widget tracks which data range you are currently viewing and inserts the html to transition between ranges. Since the iterator and pagination widgets respond to the same message, r:dynatable.list.response, the pagination widget effectively controls the data that populates the iterator. All the app:iterator has to do is layout the incoming data in a tabular form. The app:iterator defaults to creating a div, so we have set the attribute table to “true” in order to force a table container.

The other interesting bit on the front-end is the filter box. It actually looks like normal html, except with one twist:

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<div style="display:none" id="list_of_days" class="content_container" on="r:dynatable.has.data.response[data=true] then show">
    <span>select days</span>
    <div class="content">
        <ul id="list_days">
            <li><input id="sunday" on="click then r:dynatable.list.request" fieldset='days' 
                         type="checkbox" name="sunday" checked='checked'/>Sunday</li>
            <li><input id="monday" on="click then r:dynatable.list.request" fieldset='days' 
                         type="checkbox" name="monday" checked='checked'/>Monday</li>
            <li><input id="tuesday" on="click then r:dynatable.list.request" fieldset='days' 
                         type="checkbox" name="tuesday" checked='checked'/>Tuesday</li>
            <li><input id="wednesday" on="click then r:dynatable.list.request" fieldset='days' 
                         type="checkbox" name="wednesday" checked='checked'/>Wednesday</li>
            <li><input id="thursday" on="click then r:dynatable.list.request" fieldset='days' 
                         type="checkbox" name="thursday" checked='checked'/>Thursday</li>
            <li><input id="friday" on="click then r:dynatable.list.request" fieldset='days' 
                         type="checkbox" name="friday" checked='checked'/>Friday</li>
            <li class="bottom"><input id="saturday" on="click then r:dynatable.list.request" fieldset='days' 
                         type="checkbox" name="saturday" checked='checked'/>Saturday</li>
        </ul>
    </div>
</div>

It’s just a list with a bunch of input elements, except each input element has the fieldset attribute set to “days”. A fieldset in Appcelerator is used to link input fields together for the purpose of sending a message. When a message is generated by a member of a fieldset, the name value pair for each element that has the fieldset “days” will be placed into the data payload. Look back at the first snippet and note that fieldset is also used on the iterator. This means that regardless of whether you interact with the checkboxes or the iterator, all the information needed to page and filter will be available in the generated message.

Services
The java server-side Appcelerator code is just as straight foward. Since the fieldset bundles all the necessary data together into one message, the we only need to implement one service on the server. Here are the 50 lines needed to service this request on the server:

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@Service(request = "dynatable.list.request", response = "dynatable.list.response")
protected void getListOfFolks (Message request, Message response)
{
    try
    {
        IMessageDataObject reqData = request.getData();
        int start = reqData.getInt("start");
        int end = reqData.getInt("end");
        String direction = reqData.optString("dir");
        boolean[] daysFilter = new boolean[]{reqData.getBoolean("sunday"), 
                reqData.getBoolean("monday"), reqData.getBoolean("tuesday"), 
                reqData.getBoolean("wednesday"), reqData.getBoolean("thursday"),
                reqData.getBoolean("friday"), 
                reqData.getBoolean("saturday")};
 
        //Just return the same rows if no direction
        //IE, someone is just filtering the results
        if(direction.length() > 0) {
            if(PREVIOUS.equals(direction))
            {
                end = start;
                start -= NUM_ROWS;
            } 
            else 
            {
                start = end;
                end += NUM_ROWS; 
            }
        }
        final String query = "from Person person " + 
                " join fetch person.schedule schedule " + 
                " join fetch schedule.timeSlots as timeSlots " + 
                " order by person.id";
        IMessageDataList<Person> list = 
                personDAO.findResultsByPageAsDataList(query, start, NUM_ROWS);
        IMessageDataList<IMessageDataObject> people = 
                new JSONMessageDataList<IMessageDataObject>();
        for(Person person : list )
        {
            IMessageDataObject flattenedPerson = 
                    MessageUtils.createMessageDataObject();
            flattenedPerson.put("name", person.getName());
            flattenedPerson.put("description", person.getDescription());
            flattenedPerson.put("schedule", person.getSchedule(daysFilter));
            people.add(flattenedPerson);
        }
 
        response.getData().put("people", people).put("start", start)
                .put("end", end).put("total", personDAO.count())
                .put("success", true);
    }
    catch(MessageDataObjectException e) 
    {
        response.getData().put("success", false);
        response.getData().put("message", e.getMessage());
    }
}

Extract the parameters, run a query, then generate a result message; it can’t get any more straight-forward than that. Of particular note are calls to methods like personDAO.findResultsByPageAsDataList(query, start, NUM_ROWS);. The DAO code is vastly simplified by making using of the helper functions appcelerator-corp.jar library. You can use the helper functions on any model object that has an associated DAO. Again, this is really easy to do:

@SuppressWarnings("unchecked")
public class PersonDAO extends AbstractHibernateDAO
{
    protected PersonDAO()
    {
        super(Person.class);
    }
}

Model Objects
Like GWT, Appcelerator requires you to make some minor modifications to your model objects. GWT requires that your models implement com.google.gwt.user.client.rpc.IsSerializable. Appcelerator allows you to use POJOS, but requires that you annotate serializable fields with @MessageAttr. If you want to use the fancy DAO helpers found in appcelerator_corp.jar, you will need to extend com.appcelerator.model.AbstractModelObject. However, this is not necessary for normal serialization. Our models are backed by the DB, so we added the persistence annotations. The class Person illustrates all of these features:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
        name="person_type",
        discriminatorType=DiscriminatorType.STRING
    )
@Table(name="people")
public abstract class Person extends AbstractModelObject
{
 
    @MessageAttr //doesn't really need to be here once APPSDK-220 is resolved
    private String description = "DESC";
 
    @MessageAttr
    private String name;
 
    @MessageAttr
    private Schedule schedule = new Schedule();
 
    public Person()
    {
    }
 
    @Id
    @GeneratedValue
    public Long getId()
    {
        return super.getId();
    }
 
    @Column(name="description", length=1024)
    public String getDescription()
    {
        return description;
    }
 
    public void setDescription(String description)
    {
        this.description = description;
    }
 
    @Column(name="name", length=255)
    public String getName()
    {
        return name;
    }
 
    public void setName(String name)
    {
        this.name = name;
    }
 
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="schedule_id")
    public Schedule getSchedule()
    {
        return schedule;
    }
 
    public void setSchedule(Schedule teachingSchedule)
    {
        this.schedule = teachingSchedule;
    }
 
    @Transient
    public String getSchedule(boolean[] daysFilter)
    {
        return schedule.getDescription(daysFilter);
    }
 
}

Summary

At the end of the day, you can deliver the same experience, in the same browsers, to your end users with a lot few lines of code with Appcelerator. You don’t have to take our word for it, you can easily try it for yourself. If you want a good starting point, you can download the code for this example from SVN.

Popularity: 9% [?]

Tags: Example · Opinion