diff --git a/TODO b/TODO
new file mode 100644
index 0000000..c438a11
--- /dev/null
+++ b/TODO
@@ -0,0 +1,4 @@
+Moving off Titanium:
+ app.rake
+
+Use new packaging system
diff --git a/assets/bowline.js b/assets/bowline.js
new file mode 100644
index 0000000..ccab235
--- /dev/null
+++ b/assets/bowline.js
@@ -0,0 +1,132 @@
+var Bowline = {
+ msgs: [],
+ callbacks: {},
+ uuid: 0,
+ bound: {},
+
+ id: function(){
+ return ++uuid;
+ }
+
+ // Usage: invoke(klass, method, *args)
+ invoke: function(){
+ var args = $.makeArray(arguments);
+ var klass = args.shift();
+ var method = args.shift();
+ var id = id();
+
+ var bopts = args.pop();
+ if(typeof(bopts) == "object" && bopts.callback){
+ callbacks[id] = bopts.callback;
+ } else {
+ args.push(bopts);
+ }
+
+ msgs.push({
+ klass:klass,
+ method:method,
+ args:args,
+ id:id
+ });
+ },
+
+ // Usage: instanceInvoke(klass, id, method, *args)
+ instanceInvoke: function(){
+ var args = $.makeArray(arguments);
+ args.splice(1, 0, "instance");
+ invoke.apply(this, args);
+ },
+
+ helper: function(){
+ // TODO
+ },
+
+ bind: function(el, klass, options){
+ el = jQuery(el);
+ el.chain(options);
+ el.data('bowline', klass);
+ if(!bound[klass]) bound[klass] = [];
+ bound[klass].push(el);
+ },
+
+ // Bowline functions
+
+ pollJS: function(){
+ return JSON.stringify(msgs);
+ }
+
+ invokeJS: function(str){
+ log("Evaling: " + str);
+ return JSON.stringify(eval(str));
+ },
+
+ invokeCallback: function(id, res){
+ // TODO - delete callback after
+ callbacks[id](JSON.parse(res));
+ },
+
+ created: function(klass, id, item){
+ jQuery.each(callbacks[klass], function(){
+ this.items('add', item);
+ });
+ },
+
+ updated: function(klass, id, item){
+ jQuery.each(callbacks[klass], function(){
+ this.items('update', findItem(this, id));
+ });
+ },
+
+ removed: function(klass, id){
+ jQuery.each(callbacks[klass], function(){
+ this.items('remove', findItem(this, id));
+ });
+ },
+
+ trigger: function(klass, event, data){
+ jQuery.each(callbacks[klass], function(){
+ this.trigger(event, data);
+ });
+ },
+
+ // System functions
+
+ findItem: function(el, id){
+ return jQuery.grep(el.items(true), function(n, i){
+ return n.item().id == id;
+ })[0];
+ },
+
+ log: function(msg){
+ console.log(msg);
+ }
+};
+
+function($){
+ $.fn.invoke = function(){
+ if($(this).chain('active')){
+ var args = $.makeArray(arguments);
+ if($(this).data('bowline')){
+ // Class method
+ var klass = $(this).data('bowline');
+ args.unshift(klass);
+ Bowline.invoke.apply(this, args);
+ } else {
+ // Instance method
+ var klass = $(this).item('root').data('bowline');
+ var id = $(this).item().id;
+ args.unshift(id);
+ args.unshift(klass);
+ Bowline.instanceInvoke.apply(this, args);
+ }
+ } else {
+ throw 'Chain not active';
+ }
+ };
+
+ $.fn.bind = function(){
+ var args = $.makeArray(arguments);
+ args.unshift(this);
+ Bowline.bind.apply(this, args);
+ };
+}(jQuery);
\ No newline at end of file
diff --git a/assets/jquery.bowline.js b/assets/jquery.bowline.js
deleted file mode 100755
index b53ea53..0000000
--- a/assets/jquery.bowline.js
+++ /dev/null
@@ -1,156 +0,0 @@
-(function($){
- var init = false;
- var TI = Titanium;
- var UI = TI.UI;
- var mainWin = UI.mainWindow.window;
-
- $.bowline = {
- setup: function(name, el){
- var rb = mainWin.eval("bowline_" + name + "_setup");
- if(!rb) throw 'Unknown class';
- rb(el);
- },
-
- klass: function(name){
- var rb = mainWin.eval("bowline_" + name);
- if(!rb) throw 'Unknown class';
- return rb;
- },
-
- instance: function(name, el){
- var rb = mainWin.eval("bowline_" + name + "_instance");
- if(!rb) throw 'Unknown class';
- return rb(el);
- },
-
- helper: function(){
- return(
- bowline_helper.apply(
- bowline_helper,
- arguments
- )
- );
- },
-
- load: function(){
- $(function(){
- setTimeout(function(){
- $(document.body).trigger('loading.bowline');
- var script = $("");
- script.attr('type', 'text/ruby');
- script.attr('src', '../script/init');
- $('head').append(script);
- }, 100);
- });
- },
-
- ready: function(func){
- if(init) return func();
- $(document).bind('loaded.bowline', func);
- },
-
- dialog: function(name, options, callback){
- if(!callback && typeof(options) == 'function') {
- callback = options;
- options = {};
- }
- $.extend(options, {
- 'url': 'app://public/' + name + '.html',
- 'height': 200,
- 'width': 350,
- 'transparency': 0.9,
- 'resizable': false,
- 'usingChrome': false,
- 'onclose': function(res){
- if(callback) callback(res);
- }
- });
- return Titanium.UI.showDialog(options);
- },
-
- setupForms: function(){
- // $('form').bind('submit', function(e){
- // var src = $(this).attr('src').split('.');
- // var rb = $.bowline.klass[src[0]];
- // rb.params = $(this).serialize();
- // rb.send(src[1]);
- // return false;
- // });
- },
-
- // A lot of JS libs require hashes
- // without any functions in them
- _rubyHash: function( hsh ) {
- res = {};
- var key;
- for(key in hsh){
- var value = hsh[key];
- if(typeof(value) != 'function'){
- res[key] = value;
- }
- }
- return res;
- }
- },
-
- window.bowline_loaded = function(){
- init = true;
- $(document.body).trigger('loaded.bowline');
- }
-
- $.fn.bowline = function(name, options){
- var self = $(this);
- $.bowline.ready(function(){
- self.chain(options);
- $.bowline.setup(name, self);
- self.data('bowline', name);
- self.trigger('setup.bowline');
- });
- return self;
- };
-
- $.fn.invoke = function(){
- if($(this).chain('active')){
- if($(this).data('bowline')){
- // Class method
- var name = $(this).data('bowline');
- var func = $.bowline.klass(name);
- } else {
- // Instance method
- var name = $(this).item('root').data('bowline');
- var func = $.bowline.instance(name, $(this));
- }
- var args = $.makeArray(arguments);
- var opts = args.pop();
- if(typeof(opts) == "object" && opts.async){
- setTimeout(function(){
- func.apply(func, args);
- }, 100);
- } else {
- args.push(opts);
- func.apply(func, args);
- }
- } else {
- throw 'Chain not active';
- }
- };
-
- $.fn.updateCollection = function( items ){
- items = $.map(items, function(n){
- return $.bowline._rubyHash(n);
- });
- $(this).items('replace', items);
- $(this).trigger('update.bowline');
- };
-
- $.fn.updateSingleton = function( item ){
- item = $.bowline._rubyHash(item);
- $(this).item('replace', item);
- $(this).trigger('update.bowline');
- };
-
- // main window
- if(UI.currentWindow.equals(UI.mainWindow)){
- $.bowline.load();
- }
-})(jQuery)
\ No newline at end of file
diff --git a/assets/json2.js b/assets/json2.js
new file mode 100644
index 0000000..394d7ba
--- /dev/null
+++ b/assets/json2.js
@@ -0,0 +1,479 @@
+/*
+ http://www.JSON.org/json2.js
+ 2009-09-29
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+*/
+
+/*jslint evil: true, strict: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+ this.JSON = {};
+}
+
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf()) ?
+ this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
\ No newline at end of file
diff --git a/lib/bowline.rb b/lib/bowline.rb
index 521c7b2..fcf9241 100755
--- a/lib/bowline.rb
+++ b/lib/bowline.rb
@@ -1,18 +1,15 @@
Thread.abort_on_exception = true
module Bowline
- # The raw JavaScript window object
- def self.js
- Window.new
+ def page
+ Bowline::Desktop::Proxy.new
end
-
- # Change which page we're on
- def self.show_view(name)
- js.location = "app://public/#{name}.html"
- end
-
- class Base
+ module_function :page
+
+ def bowline
+ page.Bowline
end
+ module_function :bowline
end
$LOAD_PATH << File.dirname(__FILE__)
@@ -24,15 +21,17 @@ class Base
require 'bowline/ext/class'
require 'bowline/ext/string'
-require 'bowline/window'
-require 'bowline/async'
+require 'bowline/watcher'
+require 'bowline/local_model'
+
+require 'bowline/desktop/js'
+require 'bowline/desktop/proxy'
+require 'bowline/desktop/bridge'
+
require 'bowline/helpers'
require 'bowline/dependencies/lib/dependencies'
require 'bowline/initializer'
require 'bowline/jquery'
-require 'bowline/observer'
-require 'bowline/binders'
-require 'bowline/binders/collection'
-require 'bowline/binders/singleton'
\ No newline at end of file
+require 'bowline/binders'
\ No newline at end of file
diff --git a/lib/bowline/async.rb b/lib/bowline/async.rb
deleted file mode 100644
index 963ce99..0000000
--- a/lib/bowline/async.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module Bowline
- module Async
- def self.included(base)
- base.extend Methods
- base.send :include, Methods
- class << base
- extend Methods
- end
- end
-
- module Methods
- def async(*methods)
- methods.each {|meth|
- define_method("#{meth}_with_async") {|*args|
- callback = nil
- if(args.last.is_a?(::RubyKMethod))
- callback = args.pop
- end
- Thread.new do
- res = send("#{meth}_without_async", *args)
- callback.call(res) if callback
- end
- }
- alias_method_chain meth, :async
- }
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/bowline/binders.rb b/lib/bowline/binders.rb
index ffcb8f8..12e6813 100644
--- a/lib/bowline/binders.rb
+++ b/lib/bowline/binders.rb
@@ -1,165 +1,151 @@
module Bowline
module Binders
class Base
- class << self
- # See Bowline::js
- def js
- Bowline::js
- end
+ extend Bowline::Watcher::Base
+ include Bowline::Desktop::Bridge::ClassMethods
+ js_expose
- # Equivalent of the 'jQuery' function
- def jquery
- @@jquery ||= JQuery.new
- end
+ # TODO - setup
- # See the Observer class
- def observer
- @observer ||= Observer.new
+ class << self
+ # TODO - use something more secure than 'send'
+ def invoke(meth, *args) #:nodoc:
+ send(meth, *args)
end
- # See Bowline::logger
- def logger
- Bowline::logger
- end
-
- # See Bowline::show_view
- def show_view(*args)
- Bowline::show_view(*args)
+ def instance_invoke(id, meth, *args) #:nodoc:
+ self.new(id).send(meth, *args)
end
- def params
- @params
+ def find(id)
+ klass.find(id)
end
- def params=(p) #:nodoc:
- case p
- when String
- # Params comes in a string (since it's a
- # serialized form) - we need to make it into
- # a nestled hash. Stolen from Ramaze
- m = proc {|_,o,n|o.merge(n,&m)}
- @params = params.inject({}) do |hash, (key, value)|
- parts = key.split(/[\]\[]+/)
- hash.merge(parts.reverse.inject(value) { |x, i| {i => x} }, &m)
- end
- else
- @params = p
- end
+ def all
+ klass.all
end
- def elements
- @elements
+ def created(item)
+ bowline.created(
+ id,
+ item.id,
+ item.to_js
+ ).call
end
- def setup(d) #:nodoc:
- @elements ||= []
- @elements << d
- self.item_sync!
+ def updated(item)
+ bowline.updated(
+ id,
+ item.id,
+ item.to_js
+ ).call
end
- def trigger(event, data = nil)
- @elements ||= []
- @elements.map {|e|
- e.trigger(format_event(event), data)
- }
+ def removed(item)
+ bowline.removed(
+ id,
+ item.id
+ ).call
end
- def loading(&block)
- trigger(:loading, true)
- yield
- trigger(:loading, false)
- end
+ protected
+ # klass needs to respond to:
+ # * all
+ # * find(id)
+ # * after_create(method)
+ # * after_update(method)
+ # * after_destroy(method)
+ #
+ # klass instance needs to respond to:
+ # * id
+ # * to_js
+ def expose(klass)
+ @klass = klass
+ @klass.after_create(method(:created))
+ @klass.after_update(method(:updated))
+ @klass.after_destroy(method(:removed))
+ end
+
+ def klass
+ @klass || raise("klass not set - see expose method")
+ end
+
+ # See Bowline::page
+ def page
+ Bowline::page
+ end
+
+ def bowline
+ Bowline::bowline
+ end
- def instance(el) #:nodoc:
- self.new(el).method(:send)
- end
-
- def inherited(child) #:nodoc:
- return if self == Bowline::Binders::Base
- return if child == Bowline::Binders::Singleton
- return if child == Bowline::Binders::Collection
- name = child.name.underscore
- name = name.split('/').last
- js.send("bowline_#{name}_setup=", child.method(:setup))
- js.send("bowline_#{name}_instance=", child.method(:instance))
- js.send("bowline_#{name}=", child.method(:send))
- end
+ # Equivalent of the 'jQuery' function
+ def jquery
+ JQuery.new
+ end
- def format_event(name) #:nodoc:
- name.is_a?(Array) ?
- name.join('.') :
- name.to_s
- end
+ # See Bowline::logger
+ def logger
+ Bowline::logger
+ end
+
+ def trigger(event, data = nil)
+ bowline.trigger(
+ id,
+ format_event(event),
+ data
+ ).call
+ end
+
+ def loading(&block)
+ trigger(:loading, true)
+ yield
+ trigger(:loading, false)
+ end
+
+ def id #:nodoc:
+ [name, __id__].join("_")
+ end
+
+ def format_event(name) #:nodoc:
+ name.is_a?(Array) ?
+ name.join('.') :
+ name.to_s
+ end
end
attr_reader :element
attr_reader :item
- def initialize(element, *args) #:nodoc:
- # jQuery element
- @element = element
- # Calling chain.js 'item' function
- @item = element.item()
- if @item
- # If possible, find Ruby object
- @item = self.class.find(@item._id.to_i)
- end
- end
-
- # Trigger jQuery events on this element
- def trigger(event, data = nil)
- self.element.trigger(
- self.class.format_event(event),
- data
- )
- end
-
- # Raw DOM element
- def dom
- self.element[0]
- end
-
- # Shortcut methods
-
- # See self.class.show_view
- def show_view(*args)
- self.class.show_view(*args)
+ def initialize(id, *args) #:nodoc:
+ @element = JQuery.for_id(id)
+ @item = self.class.find(id)
end
+
+ protected
+ # Trigger jQuery events on this element
+ def trigger(event, data = nil)
+ element.trigger(
+ self.class.format_event(event),
+ data
+ ).call
+ end
- # See self.class.js
- def js
- self.class.js
- end
- alias :page :js
+ # Shortcut methods
- # See self.class.jquery
- def jquery
- self.class.jquery
- end
+ # See self.class.js
+ def page
+ self.class.page
+ end
- # See self.class.observer
- def observer
- self.class.observer
- end
+ # See self.class.jquery
+ def jquery
+ self.class.jquery
+ end
- # See self.class.logger
- def logger
- self.class.logger
- end
-
- private
- # This is just a unique identifier
- # for the item - and isn't
- # used in the dom
- def item_id
- if item.respond_to?(:dom_id)
- item.dom_id
- else
- [
- item.id,
- self.class.name.underscore
- ].join("_")
- end
+ # See self.class.logger
+ def logger
+ self.class.logger
end
end
end
diff --git a/lib/bowline/binders/collection.rb b/lib/bowline/binders/collection.rb
deleted file mode 100644
index f8193e0..0000000
--- a/lib/bowline/binders/collection.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-module Bowline
- module Binders
- class Collection < Base
- class ItemsProxy
- def initialize(&block)
- @callback = block
- @items = []
- end
-
- def real
- @items
- end
-
- def method_missing(*args, &block)
- diff = @items.hash
- res = @items.send(*args, &block)
- if diff != @items.hash
- @callback.call
- end
- res
- end
- end
-
- class << self
- def items=(args)
- if args
- items.replace(args)
- else
- items.clear
- end
- end
-
- def items
- @items ||= ItemsProxy.new {
- self.item_sync!
- }
- end
-
- def item_sync!
- return unless @items && @elements
- value = @items.real.map {|item|
- hash = item.to_js
- hash.merge!({:_id => item.__id__})
- hash.stringify_keys
- }
- @elements.each {|i|
- i.updateCollection(value)
- }
- end
-
- def find(id)
- @items.real.find {|item|
- item.__id__ == id
- }
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/bowline/binders/singleton.rb b/lib/bowline/binders/singleton.rb
deleted file mode 100644
index a58b4fc..0000000
--- a/lib/bowline/binders/singleton.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module Bowline
- module Binders
- class Singleton < Base
- class << self
- def item
- @item
- end
-
- def item=(arg)
- @item = arg
- self.item_sync!
- end
-
- def item_sync!
- return unless @item && @elements
- value = @item.to_js
- value.merge!({:_id => @item.__id__})
- value.stringify_keys!
- # Call the chain.js function 'item' on elements
- @elements.each {|i|
- i.updateSingleton(value)
- }
- end
-
- def find(*a)
- @item
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/bowline/desktop.rb b/lib/bowline/desktop.rb
new file mode 100644
index 0000000..609ac07
--- /dev/null
+++ b/lib/bowline/desktop.rb
@@ -0,0 +1,25 @@
+module Bowline
+ module Desktop
+ extend Bowline::Watcher::Base
+ watch :startup, :load, :idle
+
+ def enabled?
+ $0 == "bowline"
+ end
+
+ @@loaded = false
+ def loaded
+ unless @@loaded
+ @@loaded = true
+ watcher.call(:startup)
+ end
+ watcher.call(:load)
+ end
+ module_function :loaded
+
+ def idle
+ watcher.call(:idle)
+ end
+ module_function :idle
+ end
+end
\ No newline at end of file
diff --git a/lib/bowline/desktop/bridge.rb b/lib/bowline/desktop/bridge.rb
new file mode 100644
index 0000000..f97befe
--- /dev/null
+++ b/lib/bowline/desktop/bridge.rb
@@ -0,0 +1,52 @@
+module Bowline
+ module Desktop
+ module Bridge
+ module ClassMethods
+ def js_expose(opts = {})
+ # TODO - implement options,
+ # like :except and :only
+ define_method(:js_exposed?) do
+ true
+ end
+ end
+ end
+
+ class Message
+ def self.from_array(arr)
+ arr.map {|i| self.new(i) }
+ end
+
+ def initialize(atts)
+ atts.with_indifferent_access!
+ @id = atts[:id]
+ @klass = atts[:klass]
+ @method = atts[:method]
+ end
+
+ def invoke
+ # TODO - error support
+ klass = @klass.constantize
+ if klass.respond_to?(:js_exposed?) &&
+ klass.js_exposed?
+ result = klass.send(@method)
+ proxy = Proxy.new
+ proxy.Bowline.invokeCallback(@id, result)
+ run_js_script(proxy.to_s)
+ end
+ end
+ end
+
+ def setup
+ Desktop.on_idle(method(:poll))
+ end
+ module_function :setup
+
+ def poll
+ result = JSON.parse(run_js_script("Bowline.pollJS()"))
+ messages = Message.from_array(result)
+ messages.each(&:invoke)
+ end
+ module_function :poll
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/bowline/desktop/js.rb b/lib/bowline/desktop/js.rb
new file mode 100644
index 0000000..90449b9
--- /dev/null
+++ b/lib/bowline/desktop/js.rb
@@ -0,0 +1,57 @@
+module Bowline
+ module Desktop
+ module JS
+ class Script
+ include Bowline::Logging
+
+ attr_reader :str, :prok
+ def initialize(str, prok = nil)
+ @str, @prok = str, prok
+ end
+
+ def call
+ if Desktop.enabled?
+ result = JSON.parse(run_js_script(str))
+ Thread.new { prok.call(result) } if prok
+ result
+ else
+ trace "Pseudo JS eval: #{str}"
+ end
+ end
+ end
+
+ def poll
+ run_scripts
+ end
+ module_function :poll
+
+ def setup
+ Desktop.on_idle(method(:poll))
+ end
+ module_function :setup
+
+ def eval(str, method = nil, &block)
+ script = Script.new(str, method||block)
+ if Thread.current == Thread.main
+ script.call
+ else
+ scripts << script
+ end
+ end
+ module_function :eval
+
+ private
+ def run_scripts
+ while script = scripts.shift
+ script.call
+ end
+ end
+ module_function :run_scripts
+
+ def scripts
+ Thread.main[:scripts] ||= []
+ end
+ module_function :scripts
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/bowline/desktop/proxy.rb b/lib/bowline/desktop/proxy.rb
new file mode 100644
index 0000000..5dd5d0e
--- /dev/null
+++ b/lib/bowline/desktop/proxy.rb
@@ -0,0 +1,65 @@
+module Bowline
+ module Desktop
+ class Proxy
+ attr_reader :crumps
+
+ # Fulfills two main objectives:
+ # * JS needs to be called all at once
+ # * We don't know if it's a method call, or a variable
+ #
+ # Usage:
+ # proxy.Bowline.messages = [1,2,3] #=> "Bowline.messages = [1,2,3]"
+ # proxy.Bowline.hi.call #=> "Bowline.hi()"
+ # proxy.Bowline.hi(1,2,3).bye.call #=> "Bowline.hi(1,2,3).bye()"
+ # proxy.Bowline.messages.res #=> "Bowline.messages"
+ #
+ def initialize
+ @crumbs = []
+ end
+
+ def call(&block)
+ if @crumbs.empty?
+ raise "No method provided"
+ end
+ string = to_js
+ string << "()" unless string.last == ")"
+ Bowline::Desktop::JS.eval(
+ "Bowline.invokeJS(#{string.inspect});",
+ &block
+ )
+ end
+
+ def res(&block)
+ if @crumbs.empty?
+ raise "No attribute provided"
+ end
+ Bowline::Desktop::JS.eval(
+ "Bowline.invokeJS(#{to_s.inspect});",
+ &block
+ )
+ end
+
+ def method_missing(sym, *args)
+ method_name = sym.to_s
+ @crumbs << [method_name, args]
+ if method_name.last == "="
+ call
+ end
+ end
+
+ def to_s
+ @crumps.inject("") do |str, (method, args)|
+ str << method
+ if args.any?
+ str << "(" + args.to_json[1..-2] + ")"
+ end
+ str
+ end
+ end
+
+ def inspect
+ "<#{self.class.name} #{to_s}>"
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/bowline/generators/application.rb b/lib/bowline/generators/application.rb
index c4d9fe8..d3b04b7 100644
--- a/lib/bowline/generators/application.rb
+++ b/lib/bowline/generators/application.rb
@@ -40,9 +40,10 @@ def full_name
glob! "script"
- file :jquery, "../assets/jquery.js", "public/javascripts/jquery.js"
- file :chainjs, "../assets/jquery.chain.js", "public/javascripts/jquery.chain.js"
- file :bowlinejs, "../assets/jquery.bowline.js", "public/javascripts/jquery.bowline.js"
+ file :jquery, "../assets/jquery.js", "public/javascripts/jquery.js"
+ file :chainjs, "../assets/jquery.chain.js", "public/javascripts/jquery.chain.js"
+ file :json2js, "../assets/json2.js", "public/javascripts/json2.js"
+ file :bowlinejs, "../assets/bowline.js", "public/javascripts/bowline.js"
empty_directory :app, "app"
empty_directory :models, "app/models"
diff --git a/lib/bowline/generators/binder.rb b/lib/bowline/generators/binder.rb
index 0cbeb48..47bf2b0 100644
--- a/lib/bowline/generators/binder.rb
+++ b/lib/bowline/generators/binder.rb
@@ -5,16 +5,15 @@ class BinderGenerator < NamedGenerator
DESC
def class_name
- super + " < Bowline::Binders::#{type.to_s.camel_case}"
+ super + "Binder < Bowline::Binders::Base"
end
- def modules
- ['Binders']
+ def file_name
+ super + "_binder"
end
first_argument :name, :required => true, :desc => "binder name"
- option :type, :desc => "Binder type (collection/singleton)", :default => "collection"
-
+
template :binder do |template|
template.source = "binder.rb"
template.destination = "app/binders/#{file_name}.rb"
diff --git a/lib/bowline/generators/model.rb b/lib/bowline/generators/model.rb
index 880f9f8..4c56f6f 100644
--- a/lib/bowline/generators/model.rb
+++ b/lib/bowline/generators/model.rb
@@ -4,11 +4,20 @@ class ModelGenerator < NamedGenerator
Generates a new model.
DESC
+ def class_name
+ if local
+ super + " < Bowline::LocalModel"
+ else
+ super + " < ActiveRecord::Base"
+ end
+ end
+
def modules
[]
end
first_argument :name, :required => true, :desc => "model name"
+ second_argument :local, :required => false
template :model do |template|
template.source = "model.rb"
diff --git a/lib/bowline/initializer.rb b/lib/bowline/initializer.rb
index 81fc784..d878608 100644
--- a/lib/bowline/initializer.rb
+++ b/lib/bowline/initializer.rb
@@ -262,7 +262,8 @@ def load_app_config
end
def initialize_js
- Bowline.js.bowline_loaded
+ return unless Bowline::Desktop.enabled?
+ Bowline::Desktop::JS.setup
end
def process
@@ -426,9 +427,7 @@ def gem(*args)
attr_accessor :icon
attr_accessor :sdk
attr_accessor :copyright
-
- attr_accessor :titanium_version
-
+
# Create a new Configuration instance, initialized with the default values.
def initialize
set_root_path!
@@ -452,7 +451,6 @@ def initialize
self.publisher = default_publisher
self.copyright = default_copyright
- self.titanium_version = default_titanium_version
for framework in default_frameworks
self.send("#{framework}=", Bowline::OrderedOptions.new)
@@ -597,11 +595,7 @@ def default_helper_glob
def default_initalizer_glob
File.join(root_path, *%w{ config initializers **/*.rb })
end
-
- def default_titanium_version
- "0.6.0"
- end
-
+
def default_publisher
"Bowline"
end
diff --git a/lib/bowline/jquery.rb b/lib/bowline/jquery.rb
index 2597081..ca3ee75 100644
--- a/lib/bowline/jquery.rb
+++ b/lib/bowline/jquery.rb
@@ -7,24 +7,13 @@ def method_missing(sym, args)
class << self
# Equivalent to: $('#item_id')
- def for_element(el)
- Bowline::js.send("jQuery", el)
- end
-
- # For binding global events
- # Equivalent to: $('body').bind()
- def bind(event, fun, data)
- for_element("body").bind(event, data, fun)
+ def for_id(id)
+ Bowline::page.jQuery("##{id}")
end
# Equivalent to: $
def dollar
- Bowline::js.send("jQuery")
- end
-
- # Equivalent to: $.bowline
- def bowline
- dollar.bowline
+ Bowline::page.jQuery
end
end
end
diff --git a/lib/bowline/local_model.rb b/lib/bowline/local_model.rb
new file mode 100644
index 0000000..2a7dfb4
--- /dev/null
+++ b/lib/bowline/local_model.rb
@@ -0,0 +1,92 @@
+module Bowline
+ class LocalModel
+ include ActiveSupport::Callbacks
+ define_callbacks :before_save, :after_save,
+ :before_create, :after_create,
+ :before_update, :after_update,
+ :before_destroy, :after_destroy
+
+ @@records = []
+
+ class << self
+ def populate(array)
+ array.each {|r| create(r) }
+ end
+
+ def find(id)
+ @@records.find {|r| r.id == id } || raise('Unknown Record')
+ end
+ alias :[] :find
+
+ def first
+ @@records[0]
+ end
+
+ def all
+ @@records
+ end
+
+ def destroy(id)
+ find(id).destroy
+ end
+
+ def create(atts = {})
+ self.new(atts).save
+ end
+ end
+
+ attr_reader :attributes
+
+ def initialize(atts = {})
+ @attributes = {}.with_indifferent_access
+ @attributes.replace(atts)
+ @new_record = true
+ end
+
+ # Override __id__
+ def id
+ @attributes[:id] || __id__
+ end
+
+ def update(atts)
+ run_callbacks(:before_save)
+ run_callbacks(:before_update)
+ attributes.merge!(atts)
+ run_callbacks(:after_save)
+ run_callbacks(:after_update)
+ true
+ end
+
+ def save
+ run_callbacks(:before_save)
+ if @new_record
+ run_callbacks(:before_create)
+ @@records << self
+ run_callbacks(:after_create)
+ @new_record = false
+ end
+ run_callbacks(:after_save)
+ true
+ end
+
+ def destroy
+ run_callbacks(:before_destroy)
+ @@records.delete(self)
+ run_callbacks(:after_destroy)
+ true
+ end
+
+ def method_missing(method_symbol, *arguments) #:nodoc:
+ method_name = method_symbol.to_s
+
+ case method_name.last
+ when "="
+ attributes[method_name.first(-1)] = arguments.first
+ when "?"
+ attributes[method_name.first(-1)]
+ else
+ attributes.has_key?(method_name) ? attributes[method_name] : super
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/bowline/logging.rb b/lib/bowline/logging.rb
new file mode 100644
index 0000000..ccabc18
--- /dev/null
+++ b/lib/bowline/logging.rb
@@ -0,0 +1,54 @@
+module Bowline
+ # To be included in classes to allow some basic logging
+ # that can be silenced (Logging.silent=) or made
+ # more verbose.
+ # Logging.debug=: log all error backtrace and messages
+ # logged with +debug+.
+ # Logging.trace=: log all raw request and response and
+ # messages logged with +trace+.
+ module Logging
+ class << self
+ attr_writer :trace, :debug, :silent
+
+ def trace?; !@silent && @trace end
+ def debug?; !@silent && @debug end
+ def silent?; @silent end
+ end
+
+ # Global silencer methods
+ def silent
+ Logging.silent?
+ end
+ def silent=(value)
+ Logging.silent = value
+ end
+
+ # Log a message to the console
+ def log(msg)
+ Kernel.puts msg unless Logging.silent?
+ end
+ module_function :log
+ public :log
+
+ # Log a message to the console if tracing is activated
+ def trace(msg=nil)
+ log msg || yield if Logging.trace?
+ end
+ module_function :trace
+ public :trace
+
+ # Log a message to the console if debugging is activated
+ def debug(msg=nil)
+ log msg || yield if Logging.debug?
+ end
+ module_function :debug
+ public :debug
+
+ # Log an error backtrace if debugging is activated
+ def log_error(e=$!)
+ debug "#{e}\n\t" + e.backtrace.join("\n\t")
+ end
+ module_function :log_error
+ public :log_error
+ end
+end
diff --git a/lib/bowline/observer.rb b/lib/bowline/observer.rb
deleted file mode 100644
index 7b57dfc..0000000
--- a/lib/bowline/observer.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# o = Observable.new
-# o.append('greet') {
-# puts 'hi'
-# }
-# o.call('greet')
-#
-# def greet(who)
-# puts "Hi #{who}"
-# end
-# event = o.append('greet', method(:greet), 'Alex')
-# o.call('greet')
-# event.remove
-
-module Bowline
- class Observable
- class Event
- attr_reader :type, :callback
- def initialize(observable, type, callback = nil)
- @observable = observable
- @type = type
- @callback = callback
- end
-
- def call(*args)
- @callback.call(*args)
- end
-
- def remove
- @observable.remove(self)
- end
- end
-
- def initialize
- @listeners = {}
- end
-
- def append(event, method = nil, &block)
- event = Event.new(self, event, method||block)
- (@listeners[event] ||= []) << event
- event
- end
-
- def call(event, *args)
- return unless @listeners[event]
- @listeners[event].each do |callback|
- callback.call(*args)
- end
- end
-
- def remove(event, value=nil)
- return unless @listeners[event]
- if value
- @listeners[event].delete(value)
- if @listeners[event].empty?
- @listeners.delete(event)
- end
- else
- @listeners.delete(event)
- end
- end
-
- def clear
- @listeners = {}
- end
- end
-end
\ No newline at end of file
diff --git a/lib/bowline/tasks/app.rake b/lib/bowline/tasks/app.rake
index a2970d7..16c3f13 100755
--- a/lib/bowline/tasks/app.rake
+++ b/lib/bowline/tasks/app.rake
@@ -1,42 +1,14 @@
+# TODO - major work
+
require 'fileutils'
namespace :app do
task :configure => :environment do
config_path = File.join(APP_ROOT, 'config')
conf = Bowline.configuration
- tiversion = conf.titanium_version
-
- # Titanium complains about whitespace
- manifest = <<-EOF
-#appname:#{conf.name}
-#appid:#{conf.id}
-#publisher:#{conf.publisher}
-#image:public/logo.png
-#url:#{conf.url}
-#guid:0e70684a-dd4b-4d97-9396-6bc01ba10a4e
-#desc:#{conf.description}
-#type:desktop
-runtime:#{tiversion}
-api:#{tiversion}
-tiapp:#{tiversion}
-tifilesystem:#{tiversion}
-tiplatform:#{tiversion}
-tiui:#{tiversion}
-javascript:#{tiversion}
-ruby:#{tiversion}
-tidatabase:#{tiversion}
-tidesktop:#{tiversion}
-tigrowl:#{tiversion}
-timedia:#{tiversion}
-timonkey:#{tiversion}
-tinetwork:#{tiversion}
-tinotification:#{tiversion}
-tiprocess:#{tiversion}
- EOF
-
tiapp = <<-EOF
-
+#{conf.id}#{conf.name}#{conf.version}
@@ -61,12 +33,11 @@ tiprocess:#{tiversion}
truetrue
-
+
EOF
FileUtils.cd(config_path) do
- File.open('manifest', 'w+') {|f| f.write manifest }
- File.open('tiapp.xml', 'w+') {|f| f.write tiapp }
+ File.open('bowline.xml', 'w+') {|f| f.write tiapp }
end
end
diff --git a/lib/bowline/version.rb b/lib/bowline/version.rb
index 671e4d6..3db6863 100644
--- a/lib/bowline/version.rb
+++ b/lib/bowline/version.rb
@@ -1,8 +1,8 @@
module Bowline
module Version #:nodoc:
MAJOR = 0
- MINOR = 4
- TINY = 8
+ MINOR = 5
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
diff --git a/lib/bowline/watcher.rb b/lib/bowline/watcher.rb
new file mode 100644
index 0000000..66d13e1
--- /dev/null
+++ b/lib/bowline/watcher.rb
@@ -0,0 +1,101 @@
+# Callbacks on steroids.
+# Add callbacks as class methods, or instance ones.
+#
+# class MyClass
+# include Bowline::Watcher::Base
+# watch :update, :create
+#
+# def self.update
+# watcher.call(:update)
+# end
+#
+# def create
+# watcher.call(:create)
+# end
+# end
+#
+# MyClass.on_update { puts 'update' }
+# MyClass.new.on_create { puts 'create' }
+
+module Bowline
+ class Watcher
+ module Base
+ def self.extended(base)
+ base.send :include, InstanceMethods
+ end
+
+ def watch(*names)
+ names.each do |name|
+ # Because define_method only takes a block,
+ # which doesn't accept multiple arguments
+ script = <<-RUBY
+ def on_#{name}(*args, &block)
+ watcher.append(:#{name}, *args, &block)
+ end
+ RUBY
+ instance_eval script
+ class_eval script
+ end
+ end
+
+ def watcher
+ @@watcher ||= Watcher.new
+ end
+
+ module InstanceMethods
+ def watcher
+ @watcher ||= Watcher.new
+ end
+ end
+ end
+
+ class Callback
+ attr_reader :event, :prok
+
+ def initialize(watcher, event, prok)
+ @watcher, @event, @prok = watcher, event, prok
+ end
+
+ def call(*args)
+ @prok.call(*args)
+ end
+
+ def remove
+ @watcher.remove(@event, @prok)
+ end
+ end
+
+ def initialize
+ @listeners = {}
+ end
+
+ def append(event, method = nil, &block)
+ callback = Callback.new(self, event, method||block)
+ (@listeners[event] ||= []) << callback
+ callback
+ end
+
+ def call(event, *args)
+ return unless @listeners[event]
+ @listeners[event].each do |callback|
+ callback.call(*args)
+ end
+ end
+
+ def remove(event, value=nil)
+ return unless @listeners[event]
+ if value
+ @listeners[event].delete(value)
+ if @listeners[event].empty?
+ @listeners.delete(event)
+ end
+ else
+ @listeners.delete(event)
+ end
+ end
+
+ def clear
+ @listeners = {}
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/bowline/window.rb b/lib/bowline/window.rb
deleted file mode 100644
index d4e3bb7..0000000
--- a/lib/bowline/window.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Bowline
- class Window
- def self.window
- defined?(Titanium) && Titanium.UI.mainWindow.window
- end
-
- def initialize(obj = self.class.window)
- @object = obj
- end
-
- def method_missing(*args)
- Bowline.logger.info "Sending to Window: #{args.inspect}"
- if defined?(Titanium)
- @object = @object.send(*args)
- end
- self
- end
- end
-end
\ No newline at end of file
diff --git a/templates/binder.rb b/templates/binder.rb
index 988c724..7fda322 100644
--- a/templates/binder.rb
+++ b/templates/binder.rb
@@ -1,9 +1,7 @@
-<%- with_modules(modules) do -%>
class <%= class_name %>
class << self
def index
end
end
-end
-<%- end -%>
\ No newline at end of file
+end
\ No newline at end of file
diff --git a/templates/model.rb b/templates/model.rb
index 70cd535..cc29152 100644
--- a/templates/model.rb
+++ b/templates/model.rb
@@ -1,4 +1,4 @@
<%- with_modules(modules) do -%>
-class <%= class_name %> < ActiveRecord::Base
+class <%= class_name %>
end
<%- end -%>
\ No newline at end of file
diff --git a/templates/public/index.html b/templates/public/index.html
index 8ed2d68..c8aa910 100644
--- a/templates/public/index.html
+++ b/templates/public/index.html
@@ -6,7 +6,8 @@
-
+
+