简单的To-do列表
为了真正了解mobl是什么样子的,我会在这个部分演示如何实现一个简单的to-do列表管理器。
首先我们要在Eclipse中创建一个新的mobl项目,这样会得到mobl项目的基本框架,其中有唯一的应用程序文件,我们把它命名为todo.mobl:
- application todo
- import mobl::ui::generic
- screen root()
- {
- header("todo")
- }
这个mobl模块的第一行定义这是一个application模块。mobl有三种不同类型的模块:
1.application模块:通常每个项目有一个,这是应用程序的主要入口点。
2.regular模块:这是一个定义的库,通常会由一个或者多个其他(应用程序)模块导入。
3.configuration模块(config.mobl):定义应用程序的配置选项。
application和regular模块包含任意数量的定义:用户界面、数据模型、样式、web服务接口和函数等等。
首先,让我们来使用entity定义来定义一个数据模型。实体的实例会持久化到移动设备本身的本地数据库中。我们的to-do应用程序只需要唯一一个实体,名称为Task。对于每个task对象,我们都希望能够记录它的名称,以及任务是否已经完成。
- entity Task
- {
- name : String (searchable)
- done : Bool
- }
对于每个属性我们都会指定名称和类型,并有选择地加上一个或者多个注解。mobl支持两种类型的注解:inverse注解(定义反向的关系)和searchable(在全文搜索中包含字段)。尽管这个应用程序不需要,但我们还是可以指定多个实体以及它们之间的关系,包括一对一、一对多和多对多的关系。
接下来,我们对root屏幕进行改写,从而显示我们能够勾选和取消勾选的任务列表。在mobl中,用户界面是用screen和control定义的。通常screen会通过组合大量control来定义实体屏幕布局。这样,control会定义更小的用户界面元素,像按钮、标签和表格等等。此外,屏幕或者控件也能够定义本地状态(使用变量),并使用控件结构来对集合进行迭代,从而根据条件显示用户界面的各个部分。
下面是我们的root屏幕的最初定义(当程序运行时所显示的第一个屏幕)。它使用了大量mobl::ui::generic库中的控件,包括header(渲染出屏幕的标题)、group(对一个或者多个item进行分组)和checkBox。
- screen root() {
- header("Tasks")
- group {
- list(t in Task.all()) {
- item { checkBox(t.done, label=t.name) }
- }
- }
- }
list控件的结构与for-each循环类似:它会遍历一个集合,对于集合中的每个项目,它都会进行渲染。checkBox与两个Task对象属性绑定:done和name。数据绑定会在应用程序状态(例如本地变量或者实体属性)和用户界面之间创造出同步的关系。例如,当用户选择复选框或者取消对它的选择时,t.done的值就会据此更新。类似地,当应用程序的某些其他部分更新任务t的name属性时,复选框也会自动更新它的标签。这被叫做反应性编程(reactive programming),这也是根本的mobl特性之一——用户界面会对应用程序状态的改变做出反应。
最初时,数据库是空的,从而在我们的列表中不会显示任何任务。我们怎样才能添加任务呢? 为了这个目的,我们定义了addTask屏幕:
- screen addTask() {
- var newTask = Task()
- header("Add") {
- button("Done", onclick={
- add(newTask);
- screen return;
- })
- }
- group {
- item { textField(newTask.name, placeholder="Task name") }
- }
- }
使用 var结构我们可以创建嵌入在特定屏幕中的状态。在这种情况下,我们定义了变量newTask,并使用新建的Task实体实例对其进行初始化。我们把textField控件与newTasks的name属性绑定。当用户输入完任务的名称时,他就会点击显示在应用程序标题处的“Done”按钮。按钮拥有名为onclick的参数,它的值是Callback,当事件发生的时候,就会执行一段命令式的应用程序逻辑。在这个特定的情况下,发生了两件事情:
1.向数据库添加了newTask对象。这会在后台对Task.all()集合做出改变,使得新建的任务被自动添加到root屏幕的list中。
2.然后用户会返回当初的屏幕(screen return和函数中的return类似)。
然而,尽管我们定义了root和addTask屏幕,但没有办法从一个屏幕跳转到另一个。我们需要做的就是向root屏幕添加一个“Add”按钮,它会带我们跳转到addTask屏幕。因此,我们需要在root中把对header控件的调用调整为下面这样:
- header("Tasks") {
- button("Add", onclick={ addTask(); })
- }
正如你所看到的,对屏幕的调用和对一般函数的调用类似,事实上,和函数一样,屏幕也能够返回值。
现在我们的应用程序的功能已经基本完备。我们还要添加最后一个特性:搜索。在我们的数据模型中,我们对Task的name属性使用了(searchable)注解。利用,我们就可以使用搜索来过滤任务列表(从而更快地找到我们想要查找的任务)。
我们需要把root屏幕调整为下面这样:
- screen root(){
- var phrase = ""
- header("Tasks") {
- button("Add", onclick={ addTask(); })
- }
- searchBox(phrase)
- group {
- list(t in Task.searchPrefix(phrase)) {
- item { checkBox(t.done, label=t.name) }
- }
- } }
我们向屏幕中添加了新的本地变量phrase,可以在其中存放查询的短语。我们使用searchBox,并将其与phrase绑定。然后,我们并没有遍历list中的Task.all(),而是遍历了搜索集合Task.searchPrefix(phrase)。在运行的时候,当我们在搜索查询中输入内容时,搜索结果的列表就会更新。此时用户界面会再一次根据应用程序的状态(这种情况下是phrase变量)自动调整。
现在我们已经完成了包含搜索功能的基本to-do列表应用程序的构建工作,接下来可以部署了。当我们保存mobl模块的时候,同时也会把它们编译成JavaScript、HTML和CSS,这些文件位于Eclipse项目的www/目录下。我们可以把这些生成的文件部署到任意一个能够为静态文件提供服务的web服务器上(例如:Apache、IIS或者Tomcat),mobl完全不需要后端程序。
To-do列表之外
当然,to-do列表只是个玩具一样的例子,它只使用了一些简单的控件,像group、item、button和textField。mobl的标准库还提供了一些高级的控件,像标签组、主从视图、上下文菜单以及可扩展的列表等等。
除了定义用户界面、数据模型以及应用程序逻辑的语言结构之外,mobl还拥有以下结构:
1、定义样式,在此它使用一种与CSS非常类似的语言。
2、对Web服务的访问。从服务器拉入数据并缓存在本地。在将来,mobl还会支持透明的数据同步。
想要了解这些特性,你可以查看mobl站点上的教程。
使用DSL还是不使用DSL
我们可以把mobl描述为一种领域特定语言(DSL),也就是一种针对特定应用程序领域的语言。在传统上,DSL的领域很有限。例如,HTML是一种定义结构化web页面的DSL。SQL是用来解释数据库查询的DSL。移动应用程序的领域比上述要大得多。事实上,它非常大,以至于需要大量你通常只能在一般目的的语言或者GPL——像Java、Python和Ruby——中才能够找到的特性。这些典型的GPL特性包括面向对象编程、if指令和for循环等等。既然mobl拥有GPL特性,那么它还是一种DSL吗?
我们觉得是,因为它拥有语言结构,这些结构都特别地适合数据驱动的移动web应用程序领域。例如,如果我们使用一种带有样式支持的一般目的语言,编写的是用于处理科学计算的程序,那么就不太合理了。而把screen结构应用于服务端计算也不是很合理。像实体、屏幕、控件、样式以及web服务等语言特性并不针对一般目的——它们都是针对特定领域的。
mobl不仅仅是针对移动开发的DSL。类似的还包括Applause和由此衍生的Applitude。然而,这些DSL的灵活性都有限。它们都拥有一系列的内建控件、内建函数,一旦这些都无法满足你的需求,你就需要重新使用Objective-C或者Java来编码了。mobl的目标就是,既要灵活,又要具备较强的表达能力。
为了达到这个目的,我们让这门语言尽可能小,并且通过使用mobl本身编写库来增加功能。例如,我们用来构建to-do列表应用程序的控件都没有内建在语言之中。相反,它们都是从mobl库导入的,而这个库本身又是使用mobl定义的。在最低层级上,对控件的实现使用了低级的HTML标签和CSS样式。一般用户只会使用高级别的概念控件,像标题或者按钮等等,而专家级的开发者能够通过调整库中的底层HTML代码来精确地设计按钮的显示样式。
mobl除了能够在库中定义控件之外,它还拥有暴露了大量HTML5 API的库,包括集合定位、使用Canvas进行2D绘图以及WebSockets等等。这些库只是封装了已存在的JavaScript API,而mobl API使用mobl的本地接口,这使得它能够调用“本地”JavaScript代码。这样,mobl就可以通过库机制进行扩展,这让用户可以扩展平台,而不需要扩展语言和编译器本身。
在特定的情况下,mobl会为特定的库添加句法的特性。例如,对于查询,mobl暴露了Collection类型,它拥有对实体对象的集合进行过滤、排序和分页的方法。我们可以像下面这样来调用这些方法:
- var doneTasks = Task.all().filter("done", "=", true).order("date", false).limit(10);
很明显,这些语法有些麻烦。因此,mobl为查询添加了句法特性,让我们可以把上面的查询写成这样:
- var doneTasks = Task.all() where done == true order by date desc limit 10;
这不仅更加简洁,而且现在IDE可以检查事实上Task是否拥有done和date属性,并且提供了恰当的代码自动完成功能。
结论
mobl没有在已存在的语言基础之上构建框架,而是从头开始,构建了一种外部DSL。这种方法有优点也有缺点。缺点在于用户需要学习新语言、新库以及新的工具。优点在于,选择这种语言来进行设计,可以显著减少开发者所要编写的代码。在mobl中,我们保持它的语法与语义与JavaScript类似,从而让开发者觉得这种语言很熟悉。此外,mobl能够集成到现有的Eclipse IDE和外围工具中,提供在输入时检测错误、引用解析、代码自动完成和保存时编译等功能。
mobl还是一种很年轻的语言。第一次公开发布是在2011年1月。它的编译器、工具和文档还在逐步完善中。尽管如此,我们觉得它已经显示出在移动领域使用DSL的潜力。
http://developer.51cto.com/art/201105/261021_1.htm