(caution: This is a experimental feature and not fixed)
Amrita2 supports JavaScripte Template.
Basically, it is a translator from HTML to JavaScript function running on Ruby. And the API and result is very simmilar to Amrita2 Ruby feature.
This is a sample code.
require 'amrita2/template' require 'amrita2/js' js = JS::CodeGenerator.new t = TemplateText.new '<x><em id="aaa">xxx</em></x>' js.define_function(:sample) do t.js_template('t', js) @js.code <<-END var tmpl = new t() ; var e = document.createElement('div') ; tmpl.expand(e, { 'aaa': '12' }) ; // e is '<div><x><EM id=aaa>12</EM></x></div>' return e ; END end print js.results.join(NL)
The result is a JavaScript code.
function sample() { function t() { // .. template code for <x><em id="aaa">xxx</em></x> } var e = document.createElement('div') ; var tmpl = new t() ; tmpl.expand(outer, { 'aaa': '12' }) ; // e is '<div><x><EM id=aaa>12</EM></x></div>' return e ; } t.js_template('t', js)
makes a JavaScript function named 't'. It is very similar to Amrita2 Ruby Template Object.
tmpl.expand(outer, { 'aaa': '12' }) ;
returns a DOM Element object which is expanded from template and PO (Presentation Object).
In this case, it is
<div><x><EM id=aaa>12</EM></x></div>
In every case, details of UI is a HTML text. Your JavaScript code dose not handle DOM functions or HTML tags. Only you should do is pass a PO object like this
{ 'aaa': '12' }
to the template object.
I'd like to make this feature full compatible with Amrita2 Ruby template system. And I think I can make it because JavaScript has enough feature for Amrita2 Template.
But I don't know JavaScript Language well now. So the current version is very restricted and buggy.
--- ../sample/js/hello.rb --- |
require "amrita2/template" require "amrita2/js" include Amrita2 t = JS::TemplateText.new <<-END <div> <h1 id='title'>title will be inserted here</h1> <p>This paragraph is static part</p> <p id='body'>body text will be inserted here</p> <ul id='list'><li id='item' /></ul> <table id='table' border='1'> <tr id='detail'><td id='a' /><td id='b' /></tr> </table> <p id='active'>For detail see<a id='link'></a></p> </div> END js = JS::CodeGenerator.new t.js_template('tmpl', js) jssrc = Amrita2::JS::RUNTIME_SRC + js.results.join("\n") tmpl = TemplateText.new <<END <html> <head> <title>Amrita2 JS Hello</title> <script id='script' language="JavaScript" type="text/javascript"></script> <script language="JavaScript" type="text/javascript"> function debug(s) { } function main() { var e = document.getElementById('body') ; var t = new tmpl() ; var data = { 'title': 'Hello JavaScript', 'body': 'This paragraph is dynamic parta and it was generated by amrita2 javascript template', 'list': { 'item': [1,2,3] }, 'table': { 'detail': [ { 'a':11 , 'b': 12 }, { 'a':21 , 'b': 22 } ] }, 'active': function(t) { t.link('Amrita2 Hope Page', { 'href': 'http://amrita2.rubyforge.org/'} ) ; } } ; t.expand(e, data) ; } </script> </head> <body onload='main()'> <div id='body' /> </body> </html> END tmpl.expand(STDOUT, :script => Amrita2::SanitizedString[jssrc], :body=>true) |
--- ../sample/js/json_ajax.rb --- |
require "amrita2/template" require "amrita2/js" include Amrita2 # template for the JavaScript t = JS::TemplateText.new <<-END <div> <h1 id='title'>title will be inserted here</h1> <p>This paragraph is static part</p> <p id='body'>body text will be inserted here</p> <p id='now'></p> </div> END # compiling the template to JavaScript functions js = JS::CodeGenerator.new t.js_template('tmpl', js) jssrc = Amrita2::JS::RUNTIME_SRC + js.results.join("\n") # template for the document tmpl = TemplateText.new <<END <html> <head> <title>Amrita2 JS Hello</title> <script id='script' language="JavaScript" type="text/javascript"></script> <script language="JavaScript" type="text/javascript"> // This will be removed until release function debug(s) { } // HTTP accessing function /* from http://www.peej.co.uk/articles/rich-user-experience.html */ function getHTTPObject() { var xmlhttp; try { xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (E) { xmlhttp = false; } } if (!xmlhttp) { if(typeof XMLHttpRequest != 'undefined') { try { xmlhttp = new XMLHttpRequest(); } catch (e) { xmlhttp = false; } } } return xmlhttp; } // Use the function compiled from from the template function expand_template(datasrc) { // get the element to which the template will be inserted var e = document.getElementById('output') ; while (e.hasChildNodes()) { e.removeChild(e.firstChild) ; } // make a template object var t = new tmpl() ; // get data eval(datasrc) ; // add changing data for demo var d = new Date() data.now = d.getMinutes() + ":" + d.getSeconds() ; // mix the data and the template t.expand(e, data) ; } /* json_ajax_data.txt is a file contains a JavaScript source generating a data. data = { 'title': 'Ajax with Amrita2 ', 'body': 'This is a text from the data source.' } */ function getData() { var http = getHTTPObject(); http.open("GET", "json_ajax_data.txt", true); http.onreadystatechange = function() { if (http.readyState == 4) { expand_template(http.responseText); setTimeout("getData()", 3000) ; } } http.send(null); } </script> </head> <body onload='getData()'> <div id='output' /> </body> </html> END # expand the document statically tmpl.expand(STDOUT, :script => Amrita2::SanitizedString[jssrc], :output=>true) |
sample/rails/todoajax/ contains a Ajax version of Rails Tutorial
Function and appearance is same but there is a important difference: this version refreshes every 5 seconds the TODO list asynchronously. If you typed some text for a new TODO, the text field dose not lose foucus and the text you typed will be kept when refreshing is done.
The view related code of this sample is here.
def json_list_script tmpl = Amrita2::JS::TemplateText.new <<-END <div> Not done: <div id='not_done'> <input checked="checked" id="item_done" name="item[done]" type="checkbox" value="0" /> <span> </span> <span id='description' /> <span> </span> <a id='edit_link' href="/todo/edit/">Edit</a> ..... END # compiling the template to JavaScript functions js = Amrita2::JS::CodeGenerator.new tmpl.js_template('tmpl', js) Amrita2::JS::RUNTIME_SRC + js.results.join("\n") end def json_list_po { :output=>true, :script=>Amrita2::SanitizedString[json_list_script] } end
Using this template, the static JavaScript code below display the page.
function display_item(t, done, item) { var onclick = 'document.location.href="/todo/toggle_check/' ; onclick += item.id + '"' ; var edit_url = "/todo/edit/" + item.id ; var destroy_url = "/todo/destroy/" + item.id ; var destroy_confirm = 'return confirm("Are you sure you want to delete this entry: ' + item.description + '")' ; var f = function(t) { if (! ie_flag) { t.item_done('',{'onclick': onclick }) ; } t.description(item.description) ; t.edit_link('Edit', { 'href' : edit_url } ) ; t.destroy_link('Destroy', { 'href' : destroy_url, 'onclick': destroy_confirm } ) ; } if (done) { t.done(f) ; } else { t.not_done(f) ; } } // Use the function compiled from from the template function expand_template(data) { // get the element to which the template will be inserted var e = document.getElementById('output') ; while (e.hasChildNodes()) { e.removeChild(e.firstChild) ; } // make a template object var t = new tmpl() ; // add changing data for demo var d = new Date() data.now = d.getMinutes() + ":" + d.getSeconds() ; // mix the data and the template t.expand(e, function(t) { var not_done = data.not_done ; for (var i = 0 ; i < not_done.length ; i++) { var item = not_done[i] ; display_item(t, false, item) ; } var done = data.done ; for (var i = 0 ; i < done.length ; i++) { var item = done[i] ; display_item(t, true, item) ; } } ) ; }
And data for refreshing is sent in JSON format. This is very simple with ruby-json library.
def json_data not_done = Todo.find_not_done done = Todo.find_done data = { :not_done => not_done.collect do |r| { :description=>r.description, :item_done=>r.done, :id=> r.id } end, :done => done.collect do |r| { :description=>r.description, :item_done=>r.done, :id=> r.id } end } json_response = { :ret => 200, :body => data } headers["Content-Type"] = 'text/javascript' render_text('json_response = ' + json_response.to_json) end