この文書は,António Afonso]による,Markuper: The Opera Unite Service template library の邦訳です.
This article is licensed under a Creative Commons Attribution, Non Commercial - Share Alike 2.5 license
初めに
Markuper は Unite アプリの開発を補助するテンプレートライブラリです.
通常,Unite アプリを開発するとき,すべてのコンテンツを WebServerResponse.write* 関数を使って書き出さなくてはなりません.これでは,例えばデザイナがページのレイアウトを変えたいと思ったときなど,コンテンツを書き換えるのが面倒です.また,ビジネスロジックのレイヤーと見た目のレイヤーを分離してつくらないとごっちゃになってしまいます.
Markuper テンプレートライブラリは JavaScript のコードと HTML 文書とを結ぶための書式を導入することで,これらの問題の解決を目指しています.この文書では,Markuper ライブラリの主要な機能の使い方について紹介します.
文書の構成は以下のようになっています.
初めてのテンプレート
まずは,ライブラリの挙動の説明として簡単な HTML ファイルを出力させてみます.この程度なら WebServerResponse.writeFile, を直接呼ぶのと大差ありませんが,デモとしてみてください.
Unite アプリへのライブラリの組込み方法
テンプレートを使うコードに取り掛かる前に,Unite アプリに Markuper ライブラリを組み込みましょう.エントリポイントの index.html
ファイルか,もしくは config.xml
の widgetfile
でエントリポイントとして指定したファイルに script 要素として template.js
を追加します.ここまでの下ごしらえしたサンプルをダウンロードすることもできます.
Markuper ライブラリは File I/O API を使っているので,下記のように config.xml
に feature
要素を追加しておく必要があります.
<feature name="http://xmlns.opera.com/fileio"></feature>
単純な HTML ファイルの出力
まずは下記のような出力用の単純な HTML ファイルを作り,Unite アプリのトップの templates/
ディレクトリに保存します.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
Markuper Tutorial
</body>
</html>
Unite アプリへのリクエストを処理するために,opera.io.webserver
が _request
イベントを受理するようにしておきます.前掲のサンプルアプリのコードでは,scripts/main.js
ファイルでその処理をしています.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var template = new Markuper( 'templates/tutorial.html' );
response.write( template.html() );
response.close();
}
Markuper オブジェクトのコンストラクタの引数として,出力させるファイルのパスを渡します.Markuper オブジェクトの html
関数はテンプレートファイルを文字列化して返すので,response
オブジェクトに渡して出力させます.図 1 に出力結果を示します.
図 1: 単純なテンプレートの出力
JavaScript の変数をテンプレートで使う
ただの HTML ファイルを出力させるだけならテンプレートライブラリを使う意味はほとんどないでしょう.テンプレートライブラリの真価は JavaScript の変数と組み合わせて使うことにあります.
Markuper オブジェクトのコンストラクタは,テンプレートファイルのパスのほかに,第2引数としてテンプレートで置き換える値を持たせたオブジェクトを渡すことができます.この第2引数に渡すオブジェクトは JSON 形式のオブジェクトなので,階層構造を持たせることもできます.
テンプレート内では,引数のオブジェクトのプロパティ名を {{path.to.variable}}
のように波かっこ二つで囲って表します.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
<h1>{{name}} Tutorial</h1>
<p>
This variable is further down the data object hierarchy:
'{{further.down.the.hierarchy}}'
</p>
</body>
</html>
上にあげた例では,name
と further.down.the.hierarchy
の二つの JavaScript の変数をテンプレートに埋め込んでいます.二つの埋め込まれた文字列は,Markuper オブジェクトのコンストラクタの第2引数で渡されたオブジェクトの対応する変数の値で置き換えられます.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var data =
{
name : 'Template',
further :
{
down :
{
the :
{
hierarchy: 'yes it is!'
}
}
}
};
var template = new Markuper( 'templates/tutorial.html', data );
response.write( template.parse().html() );
response.close();
}
JavaScript のファイル内では,テンプレートで参照するデータオブジェクトを作り,適切な値をセットします.
JSON オブジェクトとしてコンストラクタに渡される data
変数に,二つのプロパティ name
, further.down.the.hierachy
を定義してそれぞれ文字列 Template
と yes it is!
をセットしています.
HTML での出力を得る前に,明示的に parse()
関数を呼んで,テンプレートの値を置換えます.
出力結果は以下のようになります.
<!doctype html>
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<TITLE>Tutorial</TITLE>
</HEAD>
<BODY>
<H1>Template Tutorial</H1>
<P>
This variable is further down the data object hierarchy:
'yes it is!'
</P>
</BODY>
</HTML>
Markuper と DOM
Markuper テンプレートエンジンは DOM をベースにしているため,通常の Web ページの制作と同様に DOM API を利用する jQuery や YUI のようなライブラリやコードを用いてテンプレートを操作することができます.
テンプレート内の Elemenet
を操作する関数として,xpath
と select
関数の二つがあります.操作するノードの取得に xpath 関数では XPath を用い,select関数ではCSS 3 セレクタを用います.
サンプルコード
まず,今回の HTML テンプレートファイルは templates/tutorial.html
とします.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
<h1>{{name}} Tutorial</h1>
<div id="div1">
This is going to be <span>removed</span>
</div>
</body>
</html>
XPath
XPath を用いた要素の選択は次のようにします.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var data =
{
name : 'Template'
};
var template = new Markuper( 'templates/tutorial.html', data );
var span = template.xpath( "//div[@id='div1']/span[1]" )[0];
span.parentNode.removeChild( span );
response.write( template.parse().html() );
response.close();
}
CSS セレクタ
CSS セレクタでの要素の選択は次のようにします.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var data =
{
name : 'Template'
};
var template = new Markuper( 'templates/tutorial.html', data );
var span = template.select( "#div1 > span" )[0];
span.parentNode.removeChild( span );
response.write( template.parse().html() );
response.close();
}
出力結果は以下のようになります:
<!doctype html>
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<TITLE>Tutorial</TITLE>
</HEAD>
<BODY>
<H1>{{name}} Tutorial</H1>
<DIV id="div1">
This is going to be
</DIV>
</BODY>
</HTML>
HTML を操作する
プレゼンテーションロジック
ここまで,ロジックレイヤーをプレゼンテーションのレイヤーから効率的に切り離す方法を見てきました.JavaScript にすべてのロジックを実装し,テンプレートファイルに生成した値を埋め込むという手法です.
さらに,ロジックレイヤー自体もビジネスとプレゼンテーションの二つの領域に分けることができます.ビジネスロジックでは解こうとしている問題や特定の領域のモデルのルールを扱い,一方のプレゼンテーションロジックでは,ビジネスロジックで生成したデータをユーザにたいしてどのように提示するかについて扱います.
ここで言う「プレゼンテーション」とは,「 HTML は構造を,CSS は見た目を」という意味の「見た目」ではなく,デスクトップアプリやあるいは本格的なウェブアプリの開発における「プレゼンテーション層」を意味しています.デスクトップアプリのモデルでは,プレゼンテーション,アプリケーション,ストレージの三層に分けられ,HTML や CSS, JavaScript はプレゼンテーションレイヤーに位置付けられます.詳細は Webアプリに関する Wikipedia 英語版の記事を参照してください.
HTML と JavaScript の関数を結びつける
プレゼンテーションロジックのレイヤーを実現するために Markuper ライブラリはあらゆる HTML の要素を JavaScript のコードから操作するための仕組みを実装しています.どの JavaScript の関数がどの要素をコントロールするかをその要素の属性に結びつけます.
data-*
属性
HTML の要素と JavaScript の関数とをバインドするためには,まずテンプレートオブジェクトの registerDataAttribute
関数を,data-*
属性の名前と,その属性を持つ要素に結び付けるコールバック関数とを引数にして呼び出します.
登録するコールバック関数には次の四つの引数が渡されます.
node
: 指定した data 属性を持つ要素data
: テンプレートオブジェクトのコンストラクタに与えられたデータオブジェクトkey
: 指定した data 属性の値(文字列)value
:key
の値が,data
オブジェクトのインデックスになっているとき,その値.
HTML を文字列に変える
このサンプルでは ソースコードを表示する機能のように,指定した HTML の要素の内容を文字列に変換します.
まず,JavaScript 側 ( scripts/main.js
) は以下のようになります.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var data =
{
name : 'Template',
};
var template = new Markuper( 'templates/tutorial.html', data );
template.registerDataAttribute( 'show-html', function( node, data, key )
{
if( key == 'true' )
{
node.textContent = node.innerHTML;
}
});
response.write( template.parse().html() );
response.close();
}
テンプレートファイル ( templates/tutorial.html
) は以下のようになります.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
<h1>{{name}} Tutorial</h1>
<pre data-show-html="true">
<div id="header"></div>
<div id="content">
<p>paragraph</p>
</div>
<div id="footer"></div>
</pre>
</body>
</html>
最終的な出力は次のようになります.
<!doctype html>
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<TITLE>Tutorial</TITLE>
</HEAD>
<BODY>
<H1>Markuper Tutorial</H1>
<PRE data-show-html="true">
<DIV id="header"></DIV>
<DIV id="content">
<P>paragraph</P>
</DIV>
<DIV id="footer"></DIV>
</PRE>
</BODY>
</HTML>
ソースコードのシンタックスハイライト
次は,要素の内容を書き換えるもう少し複雑なサンプルとして,JavaScript の関数を toString()
関数で文字列化して,シンタックスハイライトを施すことにします.
まず今回のサンプルコードは scripts/main.js
になります.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var data =
{
name : 'Template',
func : function foo()
{
var baz = 3;
return 'bar';
}
};
var template = new Markuper( 'templates/tutorial.html', data );
template.registerDataAttribute( 'list-code', function( node, data, key, value )
{
var keywords = ['function', 'var', 'return'];
var regexp = new RegExp( keywords.join('|'), 'g' );
value = value.toString().replace( regexp, function( keyword )
{
return '<span style="color: blue">' + keyword + '</span>';
});
node.innerHTML = value;
});
response.write( template.parse().html() );
response.close();
}
表示用のテンプレートは templates/tutorial.html
になります.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
<h1>{{name}} Tutorial</h1>
<pre data-list-code="func"></pre>
</body>
</html>
出力結果は次のようになります.
<!doctype html>
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<TITLE>Tutorial</TITLE>
</HEAD>
<BODY>
<H1>Markuper Tutorial</H1>
<PRE data-list-code="func"><SPAN style="color: blue">function</SPAN> foo()
{
<SPAN style="color: blue">var</SPAN> baz = 3;
<SPAN style="color: blue">return</SPAN> 'bar';
}</PRE>
</BODY>
</HTML>
HTML の要素の追加と削除
このサンプルでは,あるノードの data-handler
属性に LaTeX のような値を与えて,その値から HTML の要素を生成し,最終的にそのノード自体を削除するというものです.
まず,JavaScript のコードは scripts/main.js
になります.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var data =
{
name : 'Template',
};
var template = new Markuper( 'templates/tutorial.html', data );
template.registerDataAttribute( 'header', function( node, data, key )
{
var types =
{
'section' : 'h1',
'subsection' : 'h2',
'subsubsection' : 'h3',
'paragraph' : 'h4',
'subparagraph' : 'h5'
}
var header = document.createElement( types[key] );
header.textContent = node.textContent;
node.parentNode.insertBefore( header, node );
node.parentNode.removeChild( node );
});
response.write( template.parse().html() );
response.close();
}
それから,テンプレートファイルは templates/tutorial.html
になります.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
<h1>{{name}} Tutorial</h1>
<p data-header="section">Section</p>
<p>This is a section</p>
<p data-header="subsection">SubSection</p>
<p>This is a subsection</p>
<p data-header="paragraph">Paragraph</p>
<p>This is a paragraph</p>
</body>
</html>
すると,出力結果は次のようになります.
<!doctype html>
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<TITLE>Tutorial</TITLE>
</HEAD>
<BODY>
<H1>Markuper Tutorial</H1>
<H1>Section</H1>
<P>This is a section</P>
<H2>SubSection</H2>
<P>This is a subsection</P>
<H4>Paragraph</H4>
<P>This is a paragraph</P>
</BODY>
</HTML>
組込の data-*
属性
Markuper ライブラリにはいくつか組込の data-*
属性があり,配列やオブジェクトの操作,ノードの削除やほかのテンプレートの取り込みなど典型的な処理に用いられます.
配列やオブジェクトの繰り返し処理 ( data-list
属性 )
data-list
属性のつけられたノードは,指定された値に応じて繰り返し処理されます.JavaScript の配列から HTML のリストを作るのに使えます.
data-list
属性に指定された値が配列だったときは,配列の要素の数だけノードが作られます.指定された値がオブジェクトだったときは,オブジェクトのプロパティの数だけノードが作られます.
data-list
属性を用いた繰り返し処理では,テンプレートに <data-list>[]
と呼ぶ特別なフィールドを使うことができ,処理している配列の個々の要素やオブジェクトにアクセスすることができます.
例えば,data-list="cities"
が付加されたノード内では,cities[]
という名前で配列の個々の要素にアクセスすることができます.
data-list
属性の値が Object
を指定している場合,data-list[]
フィールドは 元々のオブジェクトのプロパティに対応する key
と value
というプロパティを持つ Object
になります.
サンプルコードでみていきましょう.まずはテンプレートファイル ( templates/tutorial.html
) です.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
<h1>{{name}} Tutorial</h1>
<ul>
<li data-list="cities">
{{cities[].city}}: {{cities[].temperature}} degrees
</li>
</ul>
</body>
</html>
つぎに,JavaScript ファイル ( scripts/main.js
) です.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var data =
{
name : 'Template',
cities :
[
{city: 'Lisbon', temperature: 20},
{city: 'Oslo' , temperature: -2}
]
};
var template = new Markuper( 'templates/tutorial.html', data );
response.write( template.parse().html() );
response.close();
}
そして,出力結果は以下のようになります.
<!doctype html>
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<TITLE>Tutorial</TITLE>
</HEAD>
<BODY>
<H1>Markuper Tutorial</H1>
<UL>
<LI>
Lisbon: 20 degrees
</LI>
<LI>
Oslo: -2 degrees
</LI>
</UL>
</BODY>
</HTML>
要素の削除 ( data-remove/keep-if
属性 )
条件によって要素を消したり残したりすることもできます.属性の値として真偽値か, &&
か ||
を使った論理演算を指定します.data-remove-if
属性が付加されているとその値によってノードを消すかどうかが決まり,data-keep-if
属性が付加されているとその真偽値によって残すかどうかが決まります.
まず,JavaScript コードは scripts/main.js
になります.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var isAdmin = true;
var readAccess = false;
var data =
{
name : 'Template',
isAdmin : false,
hasReadAccess : true
};
var template = new Markuper( 'templates/tutorial.html', data );
response.write( template.parse().html() );
response.close();
}
次に,テンプレートファイルは templates/tutorial.html
になります.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
<h1>{{name}} Tutorial</h1>
<h1 data-remove-if="false">DRAFT</h1>
<p data-keep-if="isAdmin">Admin Eyes Only</p>
<p data-keep-if="hasReadAccess || isAdmin">very important info</p>
</body>
</html>
出力結果は次のようになります.
<!doctype html>
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<TITLE>Tutorial</TITLE>
</HEAD>
<BODY>
<H1>Markuper Tutorial</H1>
<H1>DRAFT</H1>
<P>very important info</P>
</BODY>
</HTML>
ほかのテンプレートの取込 ( data-import
属性 )
大事なことを言い忘れてましたが,Markuper ライブラリではほかのテンプレートを取り込んで指定した要素に追加することができます.
まずは,JavaScript ファイル scripts/main.js
をみてみましょう.
opera.io.webserver.addEventListener( '_request', handleRequest, false );
function handleRequest( event )
{
var response = event.connection.response;
var data =
{
name : 'Template'
};
var template = new Markuper( 'templates/tutorial.html', data );
response.write( template.parse().html() );
response.close();
}
それから,テンプレートファイル templates/tutorial.html
をみてみます.
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
<h1>{{name}} Tutorial</h1>
<div data-import="templates/import.html"></div>
</body>
</html>
このサンプルでは,次のような別のテンプレートファイル templates/import.html
を取り込んでいます.
yay! I was imported from {{name}}!!
出力結果は次のようになります.
<!doctype html>
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<TITLE>Tutorial</TITLE>
</HEAD>
<BODY>
<H1>Markuper Tutorial</H1>
<DIV>yay! I was imported from Template!!</DIV>
</BODY>
</HTML>