mobl: een dsl voor mobiele applicatieontwikkeling
TRANSCRIPT
Zef Hemel, Eelco Visser
230,000/day
200,000/day
applicationdevelopment
Objective-C
Objective-C Java
Objective-C Java J2ME/C++
Objective-C Java J2ME/C++
HTML/Javascript
Objective-C Java J2ME/C++
HTML/Javascript Java
Objective-C Java J2ME/C++
HTML/Javascript Java .NET
AppStore
portability
deployment
WWW
HTML
WebDatabases
WebDatabases
WebDatabases
Location information (GPS)
WebDatabases
WebDatabases
Location information (GPS)
Threading
WebDatabases
WebDatabases
Location information (GPS)
Threading
Canvas
WebDatabases
WebDatabases
Location information (GPS)
Threading
Canvas
WebDatabases
Multi-touch
WebDatabases
Location information (GPS)
Threading
Canvas
WebDatabases
Multi-touch
Offline support
WebDatabases
Location information (GPS)
Threading
Canvas
WebDatabases
Multi-touch
Offline support
Full-screen support
mobile web apps
HTML
CSS
Javascript
SQLcache
manifests
HTML
CSS
Javascript
SQLcache
manifests
user interface abstraction
user interface abstraction
var results = tx.executeQuery("SELECT * FROM Task");for(var i = 0; i < results.length; i++) { alert(results[i].name);}
synchronous programming
render page
query database and process
results
...
time
render page
query database and process
results
...
timebrowser freeze
render page
send query
...
process query result
...
time
tx.executeQuery("SELECT * FROM Task", function(results) { for(var i = 0; i < results.length; i++) { alert(results[i].name); } });...
asynchronous programming
tx.executeQuery("SELECT * FROM Task", function(results) { alert("Hello, "); });alert("world!");
tx.executeQuery("SELECT * FROM Task", function(results) { tx.executeQuery("INSERT ...", function() { alert("Selected, then inserted"); }); });
mix of loosely-coupled DSLs
poor tool-support for mixed-language projects
lack of abstraction in UI
asynchronous programming
task 1:user interface
http://mobl-lang.org/get.htmlhttp://webdsl.org/zip/devnology/eclipselinux64.ziphttp://webdsl.org/zip/devnology/eclipselinux.ziphttp://webdsl.org/zip/devnology/eclipsemac64.ziphttp://webdsl.org/zip/devnology/eclipsewin.zip
application tipcalculator
import mobl::ui::generic
screen root() { var amount = 10 var percentage = 10
header("Tip calculator") group { item { numField(amount, label="amount") } item { numField(percentage, label="percentage") } item { "$" label(amount * (1 + percentage/100)) } }}
application tipcalculator
import mobl::ui::generic
screen root() { var amount = 10 var percentage = 10
header("Tip calculator") group { item { numField(amount, label="amount") } item { numField(percentage, label="percentage") } item { "$" label(amount * (1 + percentage/100)) } }}
application tipcalculator
import mobl::ui::generic
screen root() { var amount = 10 var percentage = 10
header("Tip calculator") group { item { numField(amount, label="amount") } item { numField(percentage, label="percentage") } item { "$" label(amount * (1 + percentage/100)) } }}
application tipcalculator
import mobl::ui::generic
screen root() { var amount = 10 var percentage = 10
header("Tip calculator") group { item { numField(amount, label="amount") } item { numField(percentage, label="percentage") } item { "$" label(amount * (1 + percentage/100)) } }}
application tipcalculator
import mobl::ui::generic
screen root() { var amount = 10 var percentage = 10
header("Tip calculator") group { item { numField(amount, label="amount") } item { numField(percentage, label="percentage") } item { "$" label(amount * (1 + percentage/100)) } }}
application tipcalculator
import mobl::ui::generic
screen root() { var amount = 10 var percentage = 10
header("Tip calculator") group { item { numField(amount, label="amount") } item { numField(percentage, label="percentage") } item { "$" label(amount * (1 + percentage/100)) } }}
task 2:scripting
application test
import mobl::ui::generic
function fac(n : Num) : Num { if(n == 1) { return 1; } else { return n * fac(n-1); }}
screen promptNum(question : String) : Num { var answer = 0 header(question) { button("Done", onclick={ screen return answer; }) } group { item { numField(answer) } }}
screen root() { header("Calculator") var n = 2 group { item { numField(n) } } button("*", onclick={ n = n * promptNum("Multiply with"); }) button("+", onclick={ n = n + promptNum("Add"); }) button("!", onclick={ n = fac(n); })}
application test
import mobl::ui::generic
function fac(n : Num) : Num { if(n == 1) { return 1; } else { return n * fac(n-1); }}
screen promptNum(question : String) : Num { var answer = 0 header(question) { button("Done", onclick={ screen return answer; }) } group { item { numField(answer) } }}
screen root() { header("Calculator") var n = 2 group { item { numField(n) } } button("*", onclick={ n = n * promptNum("Multiply with"); }) button("+", onclick={ n = n + promptNum("Add"); }) button("!", onclick={ n = fac(n); })}
application test
import mobl::ui::generic
function fac(n : Num) : Num { if(n == 1) { return 1; } else { return n * fac(n-1); }}
screen promptNum(question : String) : Num { var answer = 0 header(question) { button("Done", onclick={ screen return answer; }) } group { item { numField(answer) } }}
screen root() { header("Calculator") var n = 2 group { item { numField(n) } } button("*", onclick={ n = n * promptNum("Multiply with"); }) button("+", onclick={ n = n + promptNum("Add"); }) button("!", onclick={ n = fac(n); })}
application todo
import mobl::ui::generic
function fac(n : Num) : Num { if(n == 1) { return 1; } else { return n * fac(n-1); }}
screen promptNum(question : String) : Num { var answer = 0 header(question) { button("Done", onclick={ screen return answer; }) } group { item { numField(answer) } }}
screen root() { header("Calculator") var n = 2 group { item { numField(n) } } button("*", onclick={ n = n * promptNum("Multiply with"); }) button("+", onclick={ n = n + promptNum("Add"); }) button("!", onclick={ n = fac(n); })}
application test
import mobl::ui::generic
function fac(n : Num) : Num { if(n == 1) { return 1; } else { return n * fac(n-1); }}
screen promptNum(question : String) : Num { var answer = 0 header(question) { button("Done", onclick={ screen return answer; }) } group { item { numField(answer) } }}
screen root() { header("Calculator") var n = 2 group { item { numField(n) } } button("*", onclick={ n = n * promptNum("Multiply with"); }) button("+", onclick={ n = n + promptNum("Add"); }) button("!", onclick={ n = fac(n); })}
var pos = getPosition();alert("Your location is: " + pos);
var pos = getPosition();alert("Your location is: " + pos);
var pos;getPosition(function(result) { pos = result; alert("Your location is: " + pos); ...});
continuation-passingstyle transformation
task 3:data modeling and query
entity Task { name : String (searchable) done : Bool dateAdded : DateTime}
entity Task { name : String (searchable) done : Bool dateAdded : DateTime category : Category (inverse: tasks) tags : Collection<Tag> (inverse: tasks)}
entity Category { name : String tasks : Collection<Task> (inverse: category)}
entity Tag { name : String tasks : Collection<Task> (inverse: tags)}
screen root() { header("Tasks") { button("Add", onclick={ addTask(); }) } group { list(t in Task.all() order by dateAdded desc) { taskItem(t) } } button("Search", onclick={ search(); })}
screen search() { var phrase = "" header("Search") { backButton() } searchBox(phrase) group { list(t in Task.searchPrefix(phrase)) { taskItem(t) } }}
screen root() { header("Tasks") { button("Add", onclick={ addTask(); }) } group { list(t in Task.all() order by dateAdded desc) { taskItem(t) } } button("Search", onclick={ search(); })}
screen search() { var phrase = "" header("Search") { backButton() } searchBox(phrase) group { list(t in Task.searchPrefix(phrase)) { taskItem(t) } }}
control taskItem(t : Task) { item(onclick={ taskDetails(t); }) { checkBox(t.done) " " label(t.name) contextMenu { button("Remove", onclick={ remove(t); }) } }}
control taskItem(t : Task) { item(onclick={ taskDetails(t); }) { checkBox(t.done) " " label(t.name) contextMenu { button("Remove", onclick={ remove(t); }) } }}
Task.all()
Task.all() where done == false && ...order by dateAdded desclimit 10offset 5
Task.all() where done == false && ...order by dateAdded desclimit 10offset 5
task 4:higher-order controls and
native interfaces
control taskItem(t : Task) { checkBox(t.done) " " label(t.name) contextMenu { button("Remove", onclick={ remove(t); }) }}
control taskDetail(t : Task) { group { item { textField(t.name, placeholder="Task name") } item { checkBox(t.done, label="Done") } }}
screen root() { header("Tasks") { button("Add", onclick={ addTask(); }) } masterDetail(Task.all() order by dateAdded desc, taskItem, taskDetail) button("Search", onclick={ search(); })}
control taskItem(t : Task) { checkBox(t.done) " " label(t.name) contextMenu { button("Remove", onclick={ remove(t); }) }}
control taskDetail(t : Task) { group { item { textField(t.name, placeholder="Task name") } item { checkBox(t.done, label="Done") } }}
screen root() { header("Tasks") { button("Add", onclick={ addTask(); }) } masterDetail(Task.all() order by dateAdded desc, taskItem, taskDetail) button("Search", onclick={ search(); })}
control masterDetail(items : Collection<Dynamic>, masterItem : Control1<Dynamic>, detail : Control1<Dynamic>) { group { list(it in items) { item(onclick={ detailScreen(it, detail); }) { masterItem(it) } } }}
screen detailScreen(it : Dynamic, detail : Control1<Dynamic>) { header("Detail") { backButton("Back", onclick={ screen return; }) } detail(it)}
external type LocalStorage { static sync function getItem(key : String) : Dynamic static sync function setItem(key : String, val : Object) : void static sync function removeItem(key : String) : void static sync function clear() : void}
<javascript>firstapp.LocalStorage = window.localStorage;</javascript>
what else is there?
service DataProvider { resource getNearbyConferences(lat : Num, long : Num) : [JSON] { uri = "/nearbyConferences" } resource fetchProgram(conferenceId : String) : [JSON] { uri = "/conferenceProgram" }}
web services
service DataProvider { resource getNearbyConferences(lat : Num, long : Num) : [JSON] { uri = "/nearbyConferences" } resource fetchProgram(conferenceId : String) : [JSON] { uri = "/conferenceProgram" }}
function loadLocalConferences() { var position = getPosition(); var nearbyConferencesJson = DataProvider.getNearbyConferences(position.latitude, position.longitude); foreach(jsonObj in nearbyConferencesJson) { Conference.fromSelectJson(jsonObj); }}
web services
location & maps
mobl::ui::ios mobl::ui::jq
mobl::ui::ios mobl::ui::jq
mobl::ui
application confplan
offline truetitle "ConfPlan"
future
adaptive user interfaces
database sync
more high-level controls
web/native hybrid (PhoneGap)
http://mobl-lang.org
http://twitter.com/zef
http://twitter.com/mobllang
http://zef.me