Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1560 lines (1346 sloc)
42.9 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module mobl | |
// Runtime Javascript files to be copied to www/ | |
load js/gears_init.js | |
load js/jquery-ui-1.8.18.custom.min.js | |
load js/mobl.boot.js | |
load js/gestures.js | |
//load js/iscroll.js | |
// These will automatically be loaded | |
resource js/jquery-1.7.1.min.js | |
resource js/persistence.js | |
resource js/persistence.store.sql.js | |
resource js/persistence.store.websql.js | |
resource js/persistence.store.memory.js | |
resource js/persistence.search.js | |
/** | |
* Built-in types | |
*/ | |
@doc "A value representing nothing" | |
external type void {} | |
@doc "String type" | |
@persistable | |
external type String : Object { | |
length : Num | |
// functions with variable nr of arguments: | |
// concat() Joins two or more strings, and returns a copy of the joined strings | |
// fromCharCode() Converts Unicode values to characters | |
sync function charAt(index : Num) : String | |
sync function charCodeAt(index : Num) : Num | |
sync function indexOf(searchstring : String, start : Num = 0) : Num | |
sync function lastIndexOf(searchstring : String, start : Num = 0) : Num | |
sync function match(regexp : RegExp) : Array<String> | |
sync function replace(regexp : RegExp, newstring : String) : String | |
sync function replace(substr : String, newstring : String) : String | |
sync function search(regexp : RegExp) : Num | |
sync function slice(start : Num, end : Num) : String | |
sync function split(separator : String, limit : Num = 1000) : Array<String> | |
sync function substr(start : Num, length : Num) : String | |
sync function substring(from : Num, to : Num) : String | |
sync function toLowerCase() : String | |
sync function toUpperCase() : String | |
} | |
@doc "Numeric type, represents both integers and floating point numbers" | |
@persistable | |
external type Num : Object { | |
sync function toFixed(digitsAfterDecimal : Num = 0) : String | |
sync function toPrecision(digits : Num) : String | |
} | |
@persistable | |
external type Bool : Object { } | |
external type Dynamic : Object { } | |
external type Style : String { } | |
@doc "RegExp type" | |
external type RegExp : Object { | |
global : Bool | |
ignoreCase : Bool | |
lastIndex : Num | |
multiline : Bool | |
source : String | |
sync function compile(regexp : RegExp) : void | |
sync function compile(regexp : RegExp, modifier : String) : void | |
sync function exec(string : String) : [String] | |
sync function test(string : String) : Bool | |
static sync function fromString(regex : String) : RegExp | |
} | |
<javascript> | |
__ns.Bool = {}; | |
__ns.Num = {}; | |
__ns.String = {}; | |
</javascript> | |
<javascript for=RegExp> | |
__ns.RegExp = { | |
fromString: function(regexp) { | |
return new RegExp(regexp); | |
} | |
}; | |
</javascript> | |
external type Array<T> { | |
length : Num | |
sync function get(n : Num) : T | |
sync function push(item : T) : void | |
sync function join(sep : String) : String | |
function one() : T | |
sync function map(fn : Function1<T, Dynamic>) : [?] | |
sync function filter(fn : Function1<T, Bool>) : [T] | |
sync function reduce(fn : Function2<T, T, ?>) : ? | |
sync function contains(el : T) : Bool | |
sync function containsEntity(el : T) : Bool | |
sync function containsElementWithValue(property : String, value : ?) : Bool | |
sync function splice(idx : Num, numToDelete : Num) : Array<T> | |
sync function insert(idx : Num, item : T) : void | |
sync function remove(item : T) : void | |
} | |
external type DOMEvent : Dynamic { | |
x : Num | |
y : Num | |
sync function preventDefault() : void | |
} | |
external type Map<K, V> { | |
sync function get(k : K) : V | |
sync function set(k : K, v : V) : void | |
sync function keys() : [K] | |
} | |
external type Tuple1<T1> { | |
_1 : T1 | |
} | |
external type Tuple2<T1, T2> { | |
_1 : T1 | |
_2 : T2 | |
} | |
external type Tuple3<T1, T2, T3> { | |
_1 : T1 | |
_2 : T2 | |
_3 : T3 | |
} | |
external type Tuple4<T1, T2, T3, T4> { | |
_1 : T1 | |
_2 : T2 | |
_3 : T3 | |
_4 : T4 | |
} | |
external type Control {} | |
external type Control1<T1> { } | |
external type Control2<T1, T2> { } | |
external type Control3<T1, T2, T3> { } | |
external type Control4<T1, T2, T3, T4> { } | |
external type Control5<T1, T2, T3, T4, T5> { } | |
external type Callback {} | |
external type Function0<RT> { } | |
external type Function1<T1, RT> { } | |
external type Function2<T1, T2, RT> { } | |
external type Function3<T1, T2, T3, RT> { } | |
external type Function4<T1, T2, T3, T4, RT> { } | |
external type Function5<T1, T2, T3, T4, T5, RT> { } | |
@persistable | |
external type Text : Object { } | |
@persistable | |
external type DateTime { | |
static sync function parse(s : String) : DateTime | |
static sync function fromTimestamp(timestamp : Num) : DateTime | |
static sync function create(year : Num, month : Num, day : Num, hour : Num = 0, minute : Num = 0, second : Num = 0, ms : Num = 0) : DateTime | |
sync function getFullYear() : Num | |
sync function getMonth() : Num | |
sync function getHours() : Num | |
sync function getMinutes() : Num | |
sync function getSeconds() : Num | |
sync function getMilliseconds() : Num | |
@doc "Day of the week, starting at 0 (= Sunday)" | |
sync function getDay() : Num | |
@doc "Day of the month, starting at 0" | |
sync function getDate() : Num | |
sync function setFullYear(y : Num) : Num | |
sync function setMonth(m : Num) : Num | |
@doc "Day of the month" | |
sync function setDate(d : Num) : Num | |
sync function setHours(h : Num) : void | |
sync function setMinutes(m : Num) : void | |
sync function setSeconds(s : Num) : void | |
sync function setMilliseconds(ms : Num) : void | |
sync function toString() : String | |
sync function toDateString() : String | |
sync function getTime() : Num | |
} | |
external function sleep(ms : Num) : void | |
external sync function repeat(ms : Num, fn : Callback) : void | |
external type Math { | |
static sync function round(n : Num) : Num | |
static sync function floor(n : Num) : Num | |
static sync function ceil(n : Num) : Num | |
static sync function abs(n : Num) : Num | |
static sync function acos(n : Num) : Num | |
static sync function asin(n : Num) : Num | |
static sync function atan(n : Num) : Num | |
static sync function atan2(n : Num, n2 : Num) : Num | |
static sync function cos(n : Num) : Num | |
static sync function exp(n : Num) : Num | |
static sync function log(n : Num) : Num | |
static sync function pow(n1 : Num, n2 : Num) : Num | |
static sync function random() : Num | |
static sync function sin(n : Num) : Num | |
static sync function sqrt(n : Num) : Num | |
static sync function tan(n : Num) : Num | |
static sync function max(n1: Num, n2 : Num) : Num | |
static sync function min(n1: Num, n2 : Num) : Num | |
static sync function pi() : Num | |
static sync function isNaN(n : Num) : Bool | |
} | |
external type JSON : Dynamic { | |
static sync function parse(s : String) : JSON | |
static sync function stringify(obj : Object) : String | |
} | |
external sync function now() : DateTime | |
external sync function parseNum(s : String) : Num | |
@doc "URL Encodes a string" | |
external sync function escape(s : String) : String | |
function mergeStyles(styles : [Style]) : Style { | |
var styleString : Dynamic = styles.join(" "); | |
return styleString; | |
} | |
external type Object { | |
sync function toString() : String | |
} | |
@doc "A virtual queryable collection" | |
@persistable | |
external type Collection<T> { | |
@doc "Return one item in the collection, or null if the collection is empty" | |
function one() : T | |
@doc "Prefetch a reference property" | |
sync function prefetch(property : String) : Collection<T> | |
@doc "Filter the collection on a property based on an operator `op` (options: '=', '<', '>', '<=', '>=' or '!=') and a value" | |
sync function filter(property : String, op : String, value : Object) : Collection<T> | |
@doc "Order the collection based on a property in ascending (ascending = true) or descending (ascending = false) order" | |
sync function order(property : String, ascending : Bool) : Collection<T> | |
@doc "Reverse the order of the items" | |
sync function reverse() : Collection<T> | |
@doc "Deletes all the items in the collection" | |
function destroyAll() : void | |
@doc "Count the number of items in the collection" | |
function count() : Num | |
@doc "Calculate the collection and return it as an array" | |
function list() : Array<T> | |
function selectJSON(properties : [String]) : JSON | |
sync function limit(n : Num) : Collection<T> | |
sync function skip(n : Num) : Collection<T> | |
sync function add(item : T) : void | |
sync function addAll(items : [T]) : void | |
sync function remove(item : T) : void | |
sync function updated() : void | |
} | |
external type Entity<T> { | |
id : String | |
new : Bool | |
dirty : Bool | |
delete : Bool | |
@doc "A virtual collection containing all instances of this entity" | |
static sync function all() : Collection<T> | |
static function load(id : String) : T | |
static function findBy(property : String, value : Object) : T | |
static sync function search(query : String) : Collection<T> | |
static sync function searchPrefix(query : String) : Collection<T> | |
static function fromSelectJSON(json : JSON) : T | |
function fetch(rel : String) : T | |
sync function toJSON() : JSON | |
function selectJSON(properties : [String]) : JSON | |
} | |
external type LocalStorage { | |
static sync function setItem(key : String, value : Object) : void | |
static sync function getItem(key : String, defaultValue : ? = null) : ? | |
static sync function getNum(key : String, defaultValue : Num = 0) : Num | |
static sync function getString(key : String, defaultValue : String = "") : String | |
static sync function getBool(key : String, defaultValue : Bool = false) : Bool | |
static sync function removeItem(key : String) : void | |
} | |
<javascript for=LocalStorage> | |
__ns.LocalStorage = { | |
setItem: function(key, value) { | |
window.localStorage.setItem(key, JSON.stringify(value)); | |
}, | |
removeItem: function(key) { | |
window.localStorage.removeItem(key); | |
}, | |
getItem: function(key, defaultValue) { | |
var val = JSON.parse(window.localStorage.getItem(key) || "null") || defaultValue; | |
if(val && typeof val === 'object' && !val.addEventListener) { | |
return new mobl.ObservableObject(val); | |
} else { | |
return val; | |
} | |
}, | |
getNum: function(key, defaultValue) { | |
return this.getItem(key, defaultValue); | |
}, | |
getString: function(key, defaultValue) { | |
return this.getItem(key, defaultValue); | |
}, | |
getBool: function(key, defaultValue) { | |
return this.getItem(key, defaultValue); | |
} | |
}; | |
</javascript> | |
external type Type<T> { | |
static sync function fromJSON(json : JSON) : T | |
sync function toJSON() : ? | |
} | |
external sync function log(o : Object) : void | |
external sync function alert(o : Object) : void | |
external sync function add(e : Object) : void | |
external sync function remove(e : Object) : void | |
external function resetDatabase() : void | |
external function flushDatabase() : void | |
external sync function reload() : void | |
external sync function formatDate(d : DateTime) : String | |
external sync function formatDate2(d : DateTime) : String | |
external sync function openUrl(url : String) : void | |
external sync function range(from : Num, to : Num) : Array<Num> | |
external sync function random(max : Num) : Num | |
// Device checks | |
external sync function isIphone() : Bool | |
external sync function isIpad() : Bool | |
external sync function isAndroid() : Bool | |
external sync function isLandscape() : Bool | |
external sync function isPortrait() : Bool | |
external sync function isTouchDevice() : Bool | |
external function isOnline() : Bool | |
<javascript> | |
__ns.isIphone = function() { return !!navigator.userAgent.match(/iPhone/i) || !!navigator.userAgent.match(/iPod/i); }; | |
__ns.isIpad = function() { return !!navigator.userAgent.match(/iPad/i); }; | |
__ns.isAndroid = function() { return !!navigator.userAgent.match(/Android/i); }; | |
__ns.isLandscape = function() { return window.innerHeight < window.innerWidth; }; | |
__ns.isPortrait = function() { return window.innerHeight >= window.innerWidth; }; | |
__ns.isTouchDevice = function() { | |
return 'ontouchstart' in document.documentElement; | |
}; | |
__ns.isOnline = function(callback) { | |
var i = new Image(); | |
i.onload = function() { | |
callback(true); | |
}; | |
i.onerror = function() { callback(false); }; | |
i.src = 'http://gfx2.hotmail.com/mail/uxp/w4/m4/pr014/h/s7.png?d=' + escape(Date()); | |
}; | |
</javascript> | |
external type JQuery : Dynamic { | |
length : Num | |
sync function fadeIn(fn : Callback = null) : JQuery | |
sync function fadeOut(fn : Callback = null) : JQuery | |
sync function slideUp(fn : Callback = null) : JQuery | |
sync function slideDown(fn : Callback = null) : JQuery | |
sync function slideToggle(fn : Callback = null) : JQuery | |
sync function eq(idx : Num) : JQuery | |
sync function find(selector : String) : JQuery | |
sync function parent() : JQuery | |
sync function parents(selector : String) : JQuery | |
sync function children() : JQuery | |
sync function contents() : JQuery | |
sync function hide() : JQuery | |
sync function show() : JQuery | |
sync function toggle() : JQuery | |
sync function detach() : JQuery | |
sync function addClass(cssClass : String) : JQuery | |
sync function hasClass(cssClass : String) : Bool | |
sync function css(name : String, val : String) : JQuery | |
sync function html() : String | |
sync function text() : String | |
sync function val() : String | |
sync function is(what : String) : Bool | |
sync function bind(eventName : String, fn : Callback) : JQuery | |
sync function unbind(eventName : String, fn : Callback) : JQuery | |
sync function replaceWith(coll : JQuery) : JQuery | |
sync function append(coll : JQuery) : JQuery | |
sync function prepend(coll : JQuery) : JQuery | |
sync function remove() : JQuery | |
sync function position() : JQueryPosition | |
sync function offset() : JQueryPosition | |
sync function innerWidth() : Num | |
sync function innerHeight() : Num | |
sync function outerWidth() : Num | |
sync function outerHeight() : Num | |
sync function scrollTop() : Num | |
} | |
external type JQueryPosition { | |
top : Num | |
left : Num | |
} | |
external sync function dyn(o : Object) : Dynamic | |
<javascript for=dyn> | |
__ns.dyn = function(o) { return o; }; | |
</javascript> | |
external sync function $(sel : String) : JQuery | |
// Controls | |
@doc "Injects given HTML directly into the screen" | |
external control html(html : String) | |
control label(s : Object, style : Style = null, onclick : Callback = null) { | |
<span databind=s class=style onclick=onclick></span> | |
} | |
control block(cssClass : String = null, id : String = null, onclick : Callback = null, onswipe : Callback = null) { | |
<div id=id class=cssClass onclick=onclick onswipe=onswipe> | |
elements() | |
</div> | |
} | |
control span(cssClass : Style = null, id : String = null, onclick : Callback = null, onswipe : Callback = null) { | |
<span id=id class=cssClass onclick=onclick onswipe=onswipe> | |
elements() | |
</span> | |
} | |
control link(url : String, target : String = "_blank") { | |
l@<a href=url target=target> | |
elements() | |
</a> | |
script { | |
// Bit hacky, but ok, there's no elements API yet | |
if(l.contents().length == 0) { | |
l.text(url); | |
} | |
} | |
} | |
@doc "New-line control" | |
control nl() { | |
<br/> | |
} | |
control screenContext(id : String = null) { | |
<div class="screenContext" id=id style="position: relative;"> | |
<div class="initialElements"> | |
elements() | |
</div> | |
</div> | |
} | |
@doc "Load a localization bundle" | |
external function fetchLanguageBundle(path : String) : void | |
@doc "Retrieve an localized string from the loaded bundle" | |
external sync function _(key : String, placeholders : [Object] = []) : String | |
<javascript> | |
var bundle = {}; | |
__ns.fetchLanguageBundle = function(path, callback) { | |
$.getJSON(path, function(json) { | |
bundle = json; | |
callback(); | |
}); | |
}; | |
__ns._ = function(key, placeholders) { | |
var s = bundle[key] || key; | |
var parts = s.split('%%'); | |
s = parts[0]; | |
for(var i = 0; i < placeholders.length; i++) { | |
s += placeholders[i]; | |
if(parts[i+1]) { | |
s += parts[i+1]; | |
} | |
} | |
return s; | |
}; | |
</javascript> | |
external sync function dummyMapper(d : ?) : ? | |
external function httpRequest(url : String, method : String = "GET", encoding : String = "json", data : String = null, mapper : Function1<?,?> = dummyMapper) : Dynamic | |
<javascript for=httpRequest> | |
__ns.httpRequest = function(url, method, encoding, data, mapper, callback) { | |
$.ajax({ | |
url: url, | |
dataType: encoding, | |
type: method, | |
data: data, | |
error: function(_, message, error) { | |
console.error(message); | |
console.error(error); | |
callback(null); | |
}, | |
success: function(data) { | |
var result = mapper(data, callback); | |
if(result !== undefined) { | |
callback(result); | |
} | |
} | |
}); | |
}; | |
</javascript> | |
<javascript> | |
var argspec = persistence.argspec; | |
__ns.$ = jQuery; | |
__ns.sleep = function(time, callback) { | |
setTimeout(callback, time); | |
}; | |
__ns.Dynamic = function(props) { | |
for(var p in props) { | |
if(props.hasOwnProperty(p)) { | |
this[p] = props[p]; | |
} | |
} | |
}; | |
__ns.repeat = function(time, callback) { | |
setInterval(callback, time); | |
}; | |
mobl.alert = function(s) { | |
alert(s); | |
}; | |
mobl.log = function(s, _, callback) { | |
console.log(s); | |
if(callback) callback(); | |
}; | |
__ns.parseNum = function(s) { | |
return parseFloat(s, 10); | |
}; | |
__ns.escape = function(s) { | |
return escape(s); | |
}; | |
__ns.add = function(e) { | |
e["new"] = true; | |
var allEnt = persistence.define(e._type).all(); // NOTE: define() is a hack! | |
allEnt.add(e); | |
}; | |
mobl.now = function() { | |
return new Date(); | |
}; | |
mobl.remove = function(e) { | |
persistence.remove(e); | |
var allEnt = persistence.define(e._type).all(); | |
allEnt.triggerEvent('remove', allEnt, e); | |
allEnt.triggerEvent('change', allEnt, e); | |
}; | |
mobl.flushDatabase = function(callback) { | |
persistence.flush(callback); | |
}; | |
mobl.resetDatabase = function(callback) { | |
persistence.reset(function() { | |
persistence.schemaSync(callback); | |
}); | |
}; | |
mobl.reload = function() { | |
persistence.flush(function() { | |
window.location.reload(); | |
}); | |
}; | |
mobl.openUrl = function(url) { | |
location = url; | |
}; | |
mobl.random = function(max) { | |
return Math.round(Math.random()*max); | |
}; | |
persistence.QueryCollection.prototype.updates = function() { | |
this.triggerEvent('change', this); | |
}; | |
// Date stuff | |
mobl.DateTime = { | |
parse: function(s) { | |
return new Date(Date.parse(s)); | |
}, | |
fromTimestamp: function(timestamp) { | |
return new Date(timestamp); | |
}, | |
create: function(year, month, day, hour, minute, second, ms) { | |
return new Date(year, month, day, hour, minute, second, ms); | |
} | |
}; | |
Date.prototype.toDateString = function() { | |
return "" + (this.getMonth()+1) + "/" + this.getDate() + "/" + this.getFullYear(); | |
}; | |
mobl.Math = Math; | |
mobl.Math.pi = function() { return Math.PI; }; | |
mobl.Math.isNaN = function(n) { return isNaN(n); }; | |
mobl.JSON = JSON; | |
mobl.formatDate2 = function(date) { | |
var diff = (((new Date()).getTime() - date.getTime()) / 1000); | |
var day_diff = Math.floor(diff / 86400); | |
if ( isNaN(day_diff) || day_diff < 0 ) | |
return; | |
return day_diff === 0 && ( | |
diff < 60 && "just now" || | |
diff < 120 && "1 minute ago" || | |
diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || | |
diff < 7200 && "1 hour ago" || | |
diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || | |
day_diff === 1 && "Yesterday" || | |
day_diff < 7 && day_diff + " days ago" || | |
day_diff > 6 && "" + (date.getMonth()+1) + "/" + date.getDate() + "/" + date.getFullYear(); | |
}; | |
mobl.formatDate = function(date) { | |
var diff = (((new Date()).getTime() - date.getTime()) / 1000); | |
var day_diff = Math.floor(diff / 86400); | |
if ( isNaN(day_diff) || day_diff < 0 ) | |
return; | |
return day_diff === 0 && ( | |
diff < 60 && "just now" || | |
diff < 120 && "1 minute ago" || | |
diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || | |
diff < 7200 && "1 hour ago" || | |
diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || | |
day_diff === 1 && "Yesterday" || | |
day_diff < 7 && day_diff + " days ago" || | |
day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago"; | |
}; | |
mobl.range = function(from, to) { | |
var ar = []; | |
if(from <= to) { | |
for(var i = from; i < to; i++) { | |
ar.push(i); | |
} | |
} else { | |
for(var i = from; i > to; i--) { | |
ar.push(i); | |
} | |
} | |
return ar; | |
}; | |
mobl.html = function(html, elements, callback) { | |
var root192 = $("<span>"); | |
var node180 = $("<span >"); | |
var ref108 = html; | |
node180.html(html.get().toString()); | |
var ignore51 = false; | |
ref108.addEventListener('change', function(_, ref, val) { | |
if(ignore51) return; | |
if(ref === ref108) { | |
node180.html(val.toString()); | |
} | |
}); | |
ref108.rebind(); | |
root192.append(node180); | |
callback(root192); return; | |
}; | |
mobl.defineType = function(qid, SuperType, fields) { | |
function Type(obj) { | |
this._data = {}; | |
if(this.initialize) { | |
this.initialize(); | |
} | |
for(var p in obj) { | |
if(obj.hasOwnProperty(p)) { | |
this[p] = obj[p]; | |
} | |
} | |
} | |
Type.prototype = SuperType ? new SuperType() : new persistence.Observable(); | |
for(var prop in fields) { | |
if(fields.hasOwnProperty(prop)) { | |
(function() { | |
var p = prop; | |
if(fields[p] === null) { | |
Type.prototype.__defineGetter__(p, function() { | |
return this._data[p]; | |
}); | |
Type.prototype.__defineSetter__(p, function(val) { | |
this._data[p] = val; | |
this.triggerEvent('change', this, p, val); | |
}); | |
} else if(fields[p][0] === '[') { | |
} | |
}()); | |
} | |
} | |
Type.fromJSON = function(json) { | |
return new Type(json); | |
}; | |
Type.prototype.toJSON = function() { | |
var obj = {}; | |
var type = this._data; | |
for(var p in this) { | |
if (this.hasOwnProperty(p) && p==='_data') { | |
if ($.isFunction(type[p]["toJSON"])) { | |
obj[p] = type[p].toJSON(); | |
} else { | |
obj[p] = type[p]; | |
} | |
} | |
} | |
for(var p in type) { | |
if (type.hasOwnProperty(p) && type[p] !== undefined) { | |
if ($.isFunction(type[p]["toJSON"])) { | |
obj[p] = type[p].toJSON(); | |
} else { | |
obj[p] = type[p]; | |
} | |
} | |
} | |
return new mobl.Dynamic(obj); | |
}; | |
return Type; | |
}; | |
persistence.entityDecoratorHooks.push(function(Entity) { | |
Entity.searchPrefix = function(query) { | |
return Entity.search(query, true); | |
}; | |
}); | |
Array.prototype.list = function(tx, callback) { | |
var args = argspec.getArgs(arguments, [ | |
{name: 'tx', optional: true, check: function(obj) { return tx.executeSql; } }, | |
{name: 'callback', optional: false, check: argspec.isCallback() } | |
]); | |
tx = args.tx; | |
callback = args.callback; | |
var valueCopy = []; | |
for(var i = 0; i < this.length; i++) { | |
valueCopy[i] = this[i]; | |
} | |
callback(valueCopy); | |
}; | |
Array.prototype.insert = function(idx, item) { | |
this.splice(idx, 0, item); | |
}; | |
Array.prototype.get = function(idx) { | |
return this[idx]; | |
}; | |
Array.prototype.one = function(callback) { | |
if(this.length === 0) { | |
callback(null); | |
} else { | |
callback(this[0]); | |
} | |
}; | |
Array.prototype.contains = function(el) { | |
for(var i = 0; i < this.length; i++) { | |
if(this[i] === el) { | |
return true; | |
} | |
} | |
return false; | |
}; | |
Array.prototype.containsEnity = function(el) { | |
return this.containsElementWithValue("id", el.id); | |
}; | |
Array.prototype.containsElementWithValue = function(prop, val) { | |
for(var i = 0; i < this.length; i++) { | |
if(this[i][prop] === val) { | |
return true; | |
} | |
} | |
return false; | |
}; | |
Array.prototype.remove = function(el) { | |
for(var i = 0; i < this.length; i++) { | |
if(this[i] === el) { | |
this.splice(i, 1); | |
return; | |
} | |
} | |
}; | |
Array.prototype.addEventListener = function() {}; | |
mobl.dummyMapper = function(data, callback) { | |
callback(data); | |
}; | |
mobl.Map = function() { | |
this.data = {}; | |
}; | |
mobl.Map.prototype.toJSON = function() { | |
var data = this.data; | |
var obj = {}; | |
for(var key in data) { | |
if (data.hasOwnProperty(key)) { | |
if ($.isFunction(data[key].toJSON)) { | |
obj[key]=data[key].toJSON(); | |
} else { | |
obj[key]=data[key]; | |
} | |
} | |
} | |
return obj; | |
}; | |
mobl.Map.prototype.set = function(k, v) { | |
this.data[k] = v; | |
}; | |
mobl.Map.prototype.get = function(k) { | |
return this.data[k]; | |
}; | |
mobl.Map.prototype.keys = function() { | |
var keys = []; | |
for(var p in this.data) { | |
if(this.data.hasOwnProperty(p)) { | |
keys.push(p); | |
} | |
} | |
return keys; | |
}; | |
mobl.screenStack = []; | |
mobl.innerHeight = false; | |
setTimeout(function() { | |
if(mobl.isAndroid) { | |
mobl.innerHeight = window.innerHeight; | |
} | |
}, 200); | |
function updateScrollers () { | |
var scrollwrappers = $("div#scrollwrapper"); | |
if (scrollwrappers.length > 0) { | |
var height = mobl.innerHeight ? mobl.innerHeight : window.innerHeight; | |
height -= $("#footer:visible").height(); | |
height -= $("#tabbar:visible").height(); | |
scrollwrappers.height(height); | |
} | |
var scrollers = $("div#scrollwrapper div#content"); | |
for ( var i = 0; i < scrollers.length; i++) { | |
var scroller = scrollers.eq(i).data("scroller"); | |
if(scroller) { | |
scroller.refresh(); | |
} else { | |
} | |
} | |
} | |
mobl.delayedUpdateScrollers = function() { | |
setTimeout(updateScrollers, 200); | |
}; | |
if(!mobl.isAndroid) { | |
$(window).resize(updateScrollers); | |
} | |
$(function() { | |
// Set flushing at interval | |
setInterval(function() { | |
persistence.flush(); | |
if(persistence.saveToLocalStorage) { | |
persistence.saveToLocalStorage(); | |
} | |
}, 2500); | |
}); | |
mobl.postCallHooks = []; | |
mobl.contextStack = []; | |
if(mobl.contextStack.length === 0) { | |
mobl.contextStack.push([{ | |
screens: [], | |
dom: null, | |
id: '_top' | |
}]); | |
} | |
mobl.findDeepestVisibleContext = function(target) { | |
var idx = mobl.contextStack.length-1; | |
while(idx >= 0) { | |
var top = mobl.contextStack[idx]; | |
for(var i = 0; i < top.length; i++) { | |
if(!top[i].dom) { // body | |
top[i].dom = $("body"); | |
} | |
if(top[i].dom.is(':visible') && (!target || target === top[i].id)) { | |
return top[i]; | |
} | |
} | |
idx--; | |
} | |
}; | |
var TRANSITION_SPEED = 250; | |
__ns.animations = {}; | |
__ns.animations.slide = function(prevNode, nextNode, forward, callback) { | |
//nextNode.show('slide', {direction: forward ? 'right' : 'left'}, TRANSITION_SPEED); | |
//prevNode.hide('slide', {direction: forward ? 'left' : 'right'}, TRANSITION_SPEED, callback); | |
var browserPrefix = jQuery.browser.mozilla ? '-moz-' : '-webkit-'; | |
var makeCss = function(prop, value) { | |
var css = {}; | |
css[browserPrefix + prop] = value; | |
return css; | |
}; | |
nextNode.css(makeCss("transform", "translate3d(" + (forward ? "100%" : "-100%") + ",0px,0px)")); | |
nextNode.css(makeCss("transition-duration", TRANSITION_SPEED + "ms")); | |
nextNode.show(); | |
setTimeout(function() { | |
nextNode.css(makeCss("transition-duration", TRANSITION_SPEED + "ms")); | |
nextNode.css(makeCss("transition-timing-function", "ease-in-out")); | |
prevNode.css(makeCss("transition-duration", TRANSITION_SPEED + "ms")); | |
prevNode.css(makeCss("transition-timing-function", "ease-in-out")); | |
nextNode.css(makeCss("transform", "translate3d(0px,0px,0px)")); | |
prevNode.css(makeCss("transform", "translate3d(" + (forward ? "-100%" : "100%") + ",0px,0px)")); | |
prevNode.bind("webkitTransitionEnd", function() { | |
prevNode.unbind("webkitTransitionEnd"); | |
prevNode.hide(); | |
nextNode.css(makeCss("transition-duration", null)); | |
nextNode.css(makeCss("transition-timing-function", null)); | |
prevNode.css(makeCss("transition-duration", null)); | |
prevNode.css(makeCss("transition-timing-function", null)); | |
callback(); | |
}); | |
}, 5); | |
}; | |
__ns.animations.fade = function(prevNode, nextNode, forward, callback) { | |
nextNode.fadeIn(300); | |
prevNode.fadeOut(300, callback); | |
}; | |
__ns.animations.none = function(prevNode, nextNode, forward, callback) { | |
nextNode.show(); | |
prevNode.hide(); | |
callback(); | |
}; | |
__ns.getCurrentScreen = function() { | |
var screenContext = mobl.findDeepestVisibleContext(); | |
for(var i = 0; i < screenContext.screens.length; i++) { | |
if(screenContext.screens[i].dom.is(':visible')) { | |
return screenContext.screens[i]; | |
} | |
} | |
return null; | |
}; | |
var oldHash = null; | |
setInterval(function() { | |
if(location.hash !== oldHash) { | |
oldHash = location.hash; | |
var screenContext = mobl.findDeepestVisibleContext(); | |
if(screenContext && screenContext.initialElements) { | |
var screens = screenContext.screens; | |
if(screens.length > 1 || (screenContext.initialElements.length > 0 && screens.length > 0)) { | |
screens[screens.length-1].callbackFn(null); | |
} | |
} | |
} | |
}, 250); | |
/* | |
* screenTransitionLock holds the name of the last screen transitioning in/out | |
* A new screen can always be called (for auto-loading screens), | |
* but when the same screen is requested twice in a row (while still processing) | |
* the request is discarded to avoid corrupting the screen context. | |
*/ | |
__ns.screenTransitionLock = null; | |
__ns.acquireScreenTransitionLock = function(resource) { | |
if (__ns.screenTransitionLock === resource) { | |
return false; | |
} | |
__ns.screenTransitionLock = resource; | |
return true; | |
}; | |
$("html").bind("screenTransitionEnded", function() { | |
__ns.screenTransitionLock = null; | |
}); | |
__ns.call = function (screenName, args, callback) { | |
if (!__ns.acquireScreenTransitionLock(screenName)) { | |
return false; | |
} | |
var replace = args[args.length-3].get(); | |
var animate = args[args.length-2].get(); | |
var target = args[args.length-1].get(); | |
args.splice(args.length-3, 3); | |
var screenFrame = { | |
name: screenName, | |
args: args, | |
callback: callback, | |
div: screenName.replace(/\./g, '__'), | |
dom: $("<div>") | |
}; | |
if(!screenName.match(/\.root$/)) { | |
location.hash = "" + Math.round(Math.random() * 99999); | |
} | |
oldHash = location.hash; | |
var screenContext = mobl.findDeepestVisibleContext(target); | |
if(!screenContext) { | |
alert("Error: no matching visible screen context found"); | |
} | |
screenContext.screens.push(screenFrame); | |
// Called on "screen return" | |
var callbackFn = function () { | |
if (!__ns.acquireScreenTransitionLock(screenName)) { | |
return false; | |
} | |
// when callback function is called (i.e. screen return) | |
screenFrame.subs.unsubscribe(); | |
screenContext.screens.pop(); | |
if(screenFrame.dom.find("div.screenContext").length > 0) { // pop context | |
mobl.contextStack.pop(); | |
} | |
mobl.delayedUpdateScrollers(); | |
var domNode; | |
if (screenContext.screens.length > 0) { | |
var previousScreen = screenContext.screens[screenContext.screens.length - 1]; | |
domNode = previousScreen.dom; | |
scrollTo(0, previousScreen.pageYOffset); | |
} else { | |
domNode = screenContext.initialElements; | |
scrollTo(0, 0); | |
} | |
__ns.animations[animate](screenFrame.dom, domNode, false, function() { | |
screenFrame.dom.remove(); | |
domNode.trigger("screenTransitionEnded"); | |
}); | |
if (callback) { | |
callback.apply(null, arguments); | |
} | |
}; | |
screenFrame.callbackFn = callbackFn; | |
var parts = screenName.split('.'); | |
var current = window; | |
for(var i = 0; i < parts.length; i++) { | |
current = current[parts[i]]; | |
} | |
var screenTemplate = current; | |
screenFrame.subs = screenTemplate.apply(null, args.concat( [ function (node) { | |
node.attr('id', screenFrame.div); | |
node.css('position', 'absolute').css('top', '0').css('left', '0px').css('width', '100%'); | |
screenFrame.dom = node; | |
if (screenContext.screens.length > 1) { | |
var previousScreen = screenContext.screens[screenContext.screens.length - 2]; | |
previousScreen.pageYOffset = window.pageYOffset; | |
node.hide(); | |
node.prependTo(screenContext.dom); | |
__ns.animations[animate](previousScreen.dom, node, true, function() { node.trigger("screenTransitionEnded"); }); | |
scrollTo(0, 0); | |
} else { | |
if(screenContext.dom.selector === 'body') { | |
screenContext.initialElements = screenContext.dom.find("div.initialElements"); | |
node.prependTo(screenContext.dom); | |
node.show(); | |
screenContext.initialElements.hide(); | |
node.trigger("screenTransitionEnded"); | |
} else { | |
screenContext.initialElements = screenContext.dom.find("div.initialElements"); | |
node.hide(); | |
node.prependTo(screenContext.dom); | |
__ns.animations[animate](screenContext.initialElements, node, true, function() { node.trigger("screenTransitionEnded"); }); | |
scrollTo(0, 0); | |
} | |
} | |
var localScreenContexts = node.find("div.screenContext"); | |
if(localScreenContexts.length > 0) { | |
var ar = []; | |
for(var i = 0; i < localScreenContexts.length; i++) { | |
ar.push({ | |
screens: [], | |
dom: localScreenContexts.eq(i), | |
id: localScreenContexts.eq(i).attr('id') | |
}); | |
} | |
mobl.contextStack.push(ar); | |
} | |
mobl.postCallHooks.forEach(function(fn) { | |
fn(node); | |
}); | |
if(replace) { | |
var screenToRemove = screenContext.screens[screenContext.screens.length-2]; | |
//screenToRemove.subs.unsubscribe(); | |
//console.log(screenToRemove.dom.html()); | |
screenToRemove.dom.remove(); | |
screenContext.screens.splice(screenContext.screens.length-2, 1); | |
} | |
/* | |
$(function () { | |
var scrollers = $("div#scrollwrapper div#content"); | |
var i = 0; | |
if (scrollers.length > 0) { | |
for (i = 0; i < scrollers.length; i++) { | |
if(!scrollers.eq(i).data("scroller")) { | |
scrollers.eq(i).data("scroller", new iScroll(scrollers.get(i), 'y')); | |
} | |
} | |
mobl.delayedUpdateScrollers(); | |
} | |
});*/ | |
}, callbackFn ])); | |
}; | |
mobl.ref = function(r, prop) { | |
if(prop) { | |
for(var i = 0; i < r.childRefs.length; i++) { | |
if(r.childRefs[i].prop === prop) { | |
return r.childRefs[i]; | |
} | |
} | |
} | |
return new mobl.Reference(r, prop); | |
}; | |
function fromScope(that, prop) { | |
if(prop) { | |
return $(that).scope().get(prop); | |
} else { | |
return $(that).scope(); | |
} | |
} | |
mobl.stringTomobl__Num = function (s) { | |
return parseFloat(s, 10); | |
}; | |
mobl.stringTomobl__String = function (s) { | |
return s; | |
}; | |
mobl.conditionalDef = function(oldDef, condFn, newDef) { | |
return function() { | |
if(condFn()) { | |
return newDef.apply(null, arguments); | |
} else { | |
return oldDef.apply(null, arguments); | |
} | |
}; | |
}; | |
mobl.stringTomobl__DateTime = function(s) { | |
return new Date(s); | |
}; | |
mobl.encodeUrlObj = function(obj) { | |
var parts = []; | |
for(var k in obj) { | |
if(obj.hasOwnProperty(k)) { | |
parts.push(encodeURI(k)+"="+encodeURI(obj[k])); | |
} | |
} | |
return "?" + parts.join("&"); | |
}; | |
function op(operator, e1, e2, callback) { | |
switch(operator) { | |
case '+': callback(e1 + e2); break; | |
case '-': callback(e1 - e2); break; | |
case '*': callback(e1 * e2); break; | |
case '/': callback(e1 / e2); break; | |
case '%': callback(e1 % e2); break; | |
} | |
} | |
mobl.proxyUrl = function(url, user, password) { | |
if(user && password) { | |
return '/proxy.php?user=' + user + '&pwd=' + password + '&proxy_url=' + encodeURIComponent(url); | |
} else { | |
return '/proxy.php?proxy_url=' + encodeURIComponent(url); | |
} | |
}; | |
mobl.remoteCollection = function(uri, datatype, processor) { | |
return { | |
addEventListener: function() {}, | |
list: function(_, callback) { | |
$.ajax({ | |
url: mobl.proxyUrl(uri), | |
datatype: datatype, | |
error: function(_, message, error) { | |
console.log(message); | |
console.log(error); | |
callback([]); | |
}, | |
success: function(data) { | |
callback(processor(data)); | |
} | |
}); | |
} | |
}; | |
}; | |
mobl.ObservableObject = function(props) { | |
this._data = props; | |
this.subscribers = {}; | |
var that = this; | |
for(var property in props) { | |
if(props.hasOwnProperty(property)) { | |
(function() { | |
var p = property; | |
that.__defineGetter__(p, function() { | |
return this._data[p]; | |
}); | |
that.__defineSetter__(p, function(val) { | |
this._data[p] = val; | |
this.triggerEvent('change', this, p, val); | |
}); | |
}()); | |
} | |
} | |
}; | |
mobl.ObservableObject.prototype = new persistence.Observable(); | |
mobl.ObservableObject.prototype.toJSON = function() { | |
var obj = {}; | |
for(var p in this._data) { | |
if(this._data.hasOwnProperty(p)) { | |
obj[p] = this._data[p]; | |
} | |
} | |
return obj; | |
}; | |
function log(s) { | |
console.log(s); | |
} | |
mobl.implementInterface = function(sourceModule, targetModule, items) { | |
for(var i = 0; i < items.length; i++) { | |
targetModule[items[i]] = sourceModule[items[i]]; | |
} | |
}; | |
(function () { | |
function Tuple() { | |
for(var i = 0; i < arguments.length; i++) { | |
this['_' + (i+1)] = arguments[i]; | |
} | |
this.subscribers = {}; // Observable | |
this.length = arguments.length; | |
} | |
Tuple.prototype = new persistence.Observable(); | |
Tuple.prototype.toJSON = function() { | |
var obj = {}; | |
for(var i = 0; i < this.length; i++) { | |
obj['_' + (i+1)] = this['_' + (i+1)]; | |
} | |
return obj; | |
}; | |
var batchedEvents = []; | |
function processEvents() { | |
var toTrigger = []; | |
for(var i = 0; i < batchedEvents.length; i++) { | |
var ev = batchedEvents[i]; | |
var found = false; | |
for(var j = 0; j < toTrigger.length; j++) { | |
var ev2 = toTrigger[j]; | |
if(ev.obj === ev2.obj && ev.eventType === ev2.eventType) { | |
found = true; | |
break; | |
} | |
} | |
if(!found) { | |
toTrigger.push(ev); | |
} | |
} | |
batchedEvents = []; | |
for(i = 0; i < toTrigger.length; i++) { | |
var ev = toTrigger[i]; | |
ev.fn.apply(null, ev.args); | |
} | |
} | |
function CompSubscription(name) { | |
this.subscriptions = []; | |
this.name = name; | |
} | |
CompSubscription.prototype.addSub = function(sub) { | |
if(sub) { | |
if(sub.node && (sub.eventType.indexOf('change') !== -1 || sub.eventType.indexOf('key') !== -1)) { | |
var fn = sub.fn; | |
sub.unsubscribe(); | |
sub = mobl.domBind(sub.node, sub.eventType, function() { | |
batchedEvents.push({obj: sub.node, eventType: sub.eventType, fn: fn, args: Array.prototype.slice.call(arguments)}); | |
if(batchedEvents.length === 1) { | |
setTimeout(processEvents, 300); | |
} | |
}); | |
} else if(sub.obj && sub.obj._filter && (sub.eventType.indexOf('change') !== -1 || sub.eventType.indexOf('key') !== -1)) { | |
var fn = sub.fn; | |
sub.unsubscribe(); | |
sub = sub.obj.addEventListener(sub.eventType, function() { | |
batchedEvents.push({obj: sub.obj, eventType: sub.eventType, fn: fn, args: Array.prototype.slice.call(arguments)}); | |
if(batchedEvents.length === 1) { | |
setTimeout(processEvents, 300); | |
} | |
}); | |
} | |
this.subscriptions.push(sub); | |
} | |
}; | |
CompSubscription.prototype.unsubscribe = function() { | |
this.subscriptions.forEach(function(sub) { | |
sub.unsubscribe(); | |
}); | |
this.subscriptions = []; | |
}; | |
function DomSubscription(node, eventType, fn) { | |
this.node = node; | |
this.eventType = eventType; | |
this.fn = fn; | |
} | |
DomSubscription.prototype.unsubscribe = function() { | |
this.node.unbind(this.eventType, this.fn); | |
}; | |
DomSubscription.prototype.equals = function(o) { | |
return this.node === o.node && this.eventType === o.eventType && this.fn === o.fn; | |
}; | |
mobl.domBind = function(node, eventType, fn) { | |
node.bind(eventType, fn); | |
return new DomSubscription(node, eventType, fn); | |
}; | |
function Reference(ref, prop) { | |
this.ref = ref; | |
this.prop = prop; | |
this.childRefs = []; | |
if(prop) { | |
ref.childRefs.push(this); | |
} | |
this.subscribers = {}; // Observable | |
} | |
Reference.prototype = new persistence.Observable(); | |
Reference.prototype.oldAddListener = Reference.prototype.addEventListener; | |
Reference.prototype.addEventListener = function(eventType, callback) { | |
if(eventType === 'change' && this.prop !== undefined && this.ref.get() && this.ref.get().addEventListener) { | |
var that = this; | |
var subs = new CompSubscription(); | |
subs.addSub(this.ref.get().addEventListener('change', function(_, _, prop, value) { | |
if(prop === that.prop) { | |
callback(eventType, that, value); | |
} | |
})); | |
subs.addSub(this.oldAddListener(eventType, callback)); | |
return subs; | |
} else { | |
return this.oldAddListener(eventType, callback); | |
} | |
}; | |
Reference.prototype.addSetListener = function(callback) { | |
var that = this; | |
if(this.ref.addEventListener) { | |
return this.ref.addEventListener('change', function(_, _, prop, value) { | |
if(prop === that.prop) { | |
callback(eventType, that, value); | |
} | |
}); | |
} | |
}; | |
Reference.prototype.get = function() { | |
if(this.prop === undefined) { | |
return this.ref; | |
} | |
if(this.ref.get) { | |
return this.ref.get()[this.prop]; | |
} | |
}; | |
Reference.prototype.set = function(value) { | |
// trigger rebinding on all child refs | |
//console.log("Set", this, value); | |
if(this.prop === undefined) { | |
this.ref = value; | |
this.triggerEvent('change', this, value); | |
} else { | |
this.ref.get()[this.prop] = value; | |
this.triggerEvent('change', this, value); | |
} | |
var childRefs = this.childRefs.slice(0); | |
for(var i = 0; i < childRefs.length; i++) { | |
var childRef = childRefs[i]; | |
childRef.rebind(); | |
childRef.triggerEvent('change', childRef, childRef.get()); | |
} | |
}; | |
Reference.prototype.rebind = function() { | |
var that = this; | |
var subs = new mobl.CompSubscription(); | |
if(this.prop !== undefined) { | |
if(this.ref.get().addEventListener) { | |
subs.addSub(this.ref.get().addEventListener('change', function(_, _, prop, value) { | |
if(prop === that.prop) { | |
that.triggerEvent('change', that, value); | |
} | |
})); | |
} | |
} | |
//console.log("rebinding", this); | |
var childRefs = this.childRefs.slice(0); | |
for(var i = 0; i < childRefs.length; i++) { | |
subs.addSub(childRefs[i].rebind());//value[this.childRefs[i].prop])); | |
} | |
return subs; | |
}; | |
mobl.Tuple = Tuple; | |
mobl.Reference = Reference; | |
mobl.CompSubscription = CompSubscription; | |
}()); | |
</javascript> | |
type Window { | |
innerWidth : Num | |
innerHeight : Num | |
} | |
var window : Window = Window(); | |
<javascript for=Window> | |
__ns.window.get().innerWidth = window.innerWidth; | |
__ns.window.get().innerHeight = window.innerHeight; | |
window.onresize = function() { | |
mobl.window.get().innerWidth = window.innerWidth; | |
mobl.window.get().innerHeight = window.innerHeight; | |
}; | |
</javascript> | |
// VALIDATION | |
function emailValidator(s : String) : String { | |
return /^[A-Z0-9_%+.\-]+@[A-Z0-9.\-]+\.[A-Z]{2,4}$/i.test(s) ? "" : "Invalid e-mail address"; | |
} | |
@doc "Internal validation use" | |
external sync function setValidationError(id : Num, ok : Bool) : void | |
var allInputValid = true; | |
<javascript> | |
__ns.setValidationError = function(id, ok) { | |
var screen = mobl.getCurrentScreen(); | |
screen.validations = screen.validations || {}; | |
screen.validations[id] = ok; | |
var isValid = true; | |
for(var p in screen.validations) { | |
if(screen.validations.hasOwnProperty(p)) { | |
if(!screen.validations[p]) { | |
isValid = false; | |
} | |
} | |
} | |
__ns.allInputValid.set(isValid); | |
}; | |
</javascript> | |
external sync function setSyncFlag(val : Bool) : void | |
<javascript> | |
__ns.setSyncFlag = function(val) { | |
window["IsSyncing"] = val; | |
}; | |
</javascript> |