(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]
}
endUsing 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