Today’s exercise: build an application in mobl for this blog, which supports offline reading of blog posts. I’ll record my progress here.
I created a mobl project using the Eclipse wizard and put the project directory in the mobile subdirectory of the blog project.
application mobile
import mobl::ui::generic
screen root() {
header("mobile blog")
"test"
}
For the time being I extended my Makefile to copy the files generated in the www
subdirectory to the tomcat directory for the WebDSL application. I need to find a better deployment route later.
So, the target can be configured in the config.mobl
file.
configuration
title "Blog"
release output "../WebContent"
output "www"
For quick deployment I’m still sticking with copying the contents of www/
to my tomcat dir for now.
The next step is testing the connection with the WebDSL application on the server. First of all I create a basic service for the blog:
module blog/blog-service
imports blog/blog-model
section blog
extend entity Blog {
function json(): JSONObject {
var obj := JSONObject();
obj.put("id", id);
obj.put("title", title);
obj.put("about", about);
obj.put("description", description);
obj.put("modified", modified);
return obj;
}
}
service apiblog() {
return mainBlog().json();
}
Now the URL http://localhost:8080/blog/apiblog
produces
{"id":"795fe67c-3e3a-46a2-b20b-c18c2b9c8d45",
"title":"Rethinking | Eelco Visser",
"description":"",
"about":"This is the blog of [[root()|Eelco Visser]].",
"modified":"2011-05-07 13:33:13.0"
}
Then a basic mobl app that reads and displays these data:
application mobile
import mobl::ui::generic
screen root() {
var title = "blog"
header(title)
var blog = async(Blog.data())
whenLoaded(blog) {
label(blog.about)
label(blog.description)
}
}
service Blog {
resource data(): JSON {
uri = "/apiblog"
}
}
However, when I run this, I get a non-terminating “Loading” screen. How do we debug this? … Aha, the problem is that I specified the uri as /apiblog
whereas it is actually published at /blog/apiblog
. So if I fix the service definition as follows, I get actual data from the server in the mobl app:
service Blog {
resource data(): JSON {
uri = "/blog/apiblog"
}
}
However, this is somewhat unfortunate. I would really like to specify the uri relative to the application root of the WebDSL application. While in my test set-up I deploy the WebDSL application at /blog
, but in the production version it is a ROOT
application and the uri will have to be /apiblog
. Will need to figure out how to factor this out later.
At least I have the core of an application up and running. Let’s see if I can display some blog posts.
On the WebDSL side I create a function to marshal Post
s as JSON objects:
section post
extend entity Post {
function json(): JSONObject {
var obj := JSONObject();
obj.put("id", id);
obj.put("number", number);
obj.put("key", key);
obj.put("urlTitle", urlTitle);
obj.put("title", title);
obj.put("description", description);
obj.put("content", content);
obj.put("extended", extended);
obj.put("created", created);
obj.put("modified", modified);
return obj;
}
}
This is a bit boring. We have been discussing automating this. However, it is typically not desirable to include all properties in the JSON data. Some control is needed. But it could be syntactically lighter weight.
The following function is due to WebDSL’s lack of polymorphism, which needs to be fixed soon. At least we have function overloading so that we don’t have to invent a new name for each of these maps.
function json(posts: List<Post>): JSONArray {
var array := JSONArray();
for(p: Post in posts) { array.put(p.json()); }
return array;
}
Finally, the recent posts can be published as a service:
service apirecentposts() {
return json(mainBlog().recentPublicPosts(0, 5));
}
On the mobl side, I extend the service definition with a recentposts
resource:
service Blog {
resource data(): JSON{
uri = "/blog/apiblog"
}
resource recentposts(): JSON {
uri = "/blog/apirecentposts"
}
}
Now we can change the root
screen of the mobl app to display a list of titles of recent posts:
screen root() {
var title = "blog"
header(title)
var posts = async(Blog.recentposts())
whenLoaded(posts) {
list(post in posts) {
item{ label(post.title) }
}
}
}
In order to get a better handle on the data obtained from the server, we map it to proper mobl entities. First we define an entity Post
to mirror the entity on the WebDSL side:
entity Post {
number : Num
key : String // symbolic identity
urlTitle : String
title : String (searchable)
description : String (searchable)
content : String (searchable)
extended : String (searchable)
created : DateTime
modified : DateTime
}
Then we define a mapper function that converts the JSON array to a list of Post
s:
function postsMapper(json : JSON) : [Post] {
return json;
}
Finally, we update the recentposts
resource to return a list of Posts
instead of a JSON object:
service Blog {
resource recentposts(): [Post] {
uri = "/blog/apirecentposts"
mapper = postsMapper
}
}
Now all references to Post
objects in the view are type checked.
The support for definition and use of services in WebDSL and mobl are catering for unknown applications connecting to the service. But for the case where we are co-developing a WebDSL and mobl application, we can clearly automate much of the mappings defined above. Given a WebDSL data model we can generate the corresponding mobl data model and service definitions.
Next we want to see more than just the titles of the blog posts. We use the masterDetail
control from the mobl library to show a list of blog post titles that can be clicked to reveal the content of each post:
screen root() {
var title = "blog"
header(title)
var posts = async(postsCollection(Blog.recentposts()))
whenLoaded(posts) {
masterDetail(posts order by created desc, postItem, postDetail) { }
}
}
control postItem(p: Post) {
label(p.title)
}
control postDetail(p: Post) {
label(p.title)
block{ label(p.content) }
}
Since the masterDetail
control expects a Collection<Post>
we need a little conversion function (which should not be necessary actually):
function postsCollection(ps: [Post]): Collection<Post> {
var pcol : Collection<Post> = Collection<Post>();
foreach(p: Post in ps) { pcol.add(p); }
return pcol;
}
The result:
Unfortunately, the content is not very pretty. The service that we defined sends us the raw markdown. In WebDSL this is automatically displayed as HTML, but mobl doesn’t know about this. We’ll have to fix this.
But it is time for a break. In the next post I’ll continue we’re I’ve left off.
Status: 60 lines of mobl code and 55 lines of WebDSL code, most of which was spent on services on both sides.