這個提案描述了如何在jQuery的核心庫中增加模板支持。更為特別是,這個提案描述了一個新的jQuery方法--名叫render(),該方法可以使你用一段HTML代碼渲染一個Javascript 對象和Javascript數組作為一個模板。
該提案的目的在於使插件開發者可以利用標准的方法聲明和渲染模板。有一個標准的方法聲明和渲染模板對每一個人都很有裨益:
該提案分兩個主要部分。第一部分包含現有模板解決方案簡要調查,第二部分是我們的建議,聲明和渲染一個模板的方法如何添加到jQuery的核心庫中。
現在已經有了許多JavaScript的解決方案模板,從這方面說,標准化的模板解決方案必然是大勢所趨。在本節中,我們向你簡要描述四個最流行最有趣的模板。現有的模板解決方案能解決什麼?那些特色在jQuery核心中有意義。
微模板
John Resig的微型模板引擎非常小(未壓縮僅2KB)。然而,這點小小的代碼已經抓到了渲染一個模板的核心功能。
下面是一個用微模板引擎顯示單個JavaScript產品對象的例子。
<script src="../jquery-1.4.1.js" type="text/javascript">>;/script>;
<script src="MicroTemplating.js" type="text/javascript">;</script>;
<script type="text/javascript">;
var product = { name: "Laptop", price: 788.67 };
$(showProduct);
function showProduct() {
$("#results").html( tmpl("productTemplate", product) );
}
function formatPrice(price) {
return "$" + price;
}
</script>;
tmpl()方法用來從一個product模板和product對象生成一個字符串。其結果分配給一個名叫results的div元素的innerHTML。
product模板在頁面body的SCRIPT中定義。
<script type="text/html" id="productTemplate">
Product Name: <%= name %>
<br />
Product Price: <%= formatPrice(price) %>
</script>
<div id="results"></div>
注意,SCRIPT的type屬性為“text/ html”。用這種方式來聲明web浏覽器會忽略SCRIPT裡的內容,將其內容當作字符串來對待。
注意,模板包含代表產品名稱和價格屬性的表達式。調用JavaScript的formatPrice()方法來格式化產品的價格。在模板裡你可以調用任何JavaScript函數。
這裡是如何渲染一個product對象數組的示例:
function showProducts() {
// parse the template
var template = tmpl("productTemplate");
// loop through the products
var results = '';
for (var i = 0; i < products.length; i++) {
results += template(products[i]);
}
// show the results
$("#results").html(results);
}
tmpl()函數支持currying(關於什麼currying,可以在網上查閱資料)。如果沒有提供數據給tmpl()函數,他將返回一個javascript函數,代表解析的模板。
在上面的代碼中,模板被解析,然後為每一個product調用模板方法生成一個字符串。最後,字符串分配給名叫results的div元素的innerHTML。
jTemplates
jTemplates是一個功能豐富的模板引擎,它作為一個jQuery插件來執行。jTemplates支持很多先進的功能,包括:
下面示例中的代碼演示了如何使用jTemplates顯示產品清單。
<script src="../jquery-1.4.1.js" type="text/javascript"></script>
<script src="jquery-jtemplates_uncompressed.js" type="text/javascript"></script>
<script type="text/javascript">
var data = {
products: [
{ name: "Laptop", price: 788.67 },
{ name: "Comb", price: 2.50 },
{ name: "Pencil", price: 1.99 }
]};
$(showProducts);
function showProducts() {
// attach the template
$("#results").setTemplateElement("template");
// process the template
$("#results").processTemplate(data);
}
function formatPrice(price) {
return "$" + price;
}
</script>
setTemplateElement()方法給HTML元素指定一個模板,processTemplate()方法使用所提供的數據處理模板。
上面的代碼中,加載的模板為名為textarea的元素,下面就是模板在頁面主體中呈現的外觀。
<textarea id="template" style="display:none">
{#foreach $T.products as product}
<div>
Product Name: {$T.product.name}
<br />
Product Price: {formatPrice($T.product.price)}
</div>
{#/for}
</textarea>
注意,一個jTemplate模板可以包含諸如#foreach、#for、and #if的特殊命令。至於調用formatPrice()函數,它表明模板也可以包含任意JavaScript函數的調用。
默認情況下,為了防止JavaScript的插入攻擊,在傳遞給模板的數據中,jTemplate 的HTML編碼了包含的特殊字符。例如,如果一個產品的名稱為<b>Laptop<b>,那麼,名稱將被轉換成<b>Laptop</b> 。
jTemplates使您可以同時從外部URL加載模板和數據。例如,下面的代碼將從一個名為MyTemplates.htm的文件中加在模板,從一個名為MyData.htm文件中加在一系列數據。
function showProducts() {
$.jTemplatesDebugMode(true);
// attach the template
$("#results").setTemplateURL("MyTemplate.htm");
// process the template
$("#results").processTemplateURL("MyData.htm");
}
MyTemplate.htm文件如下所示:
{#foreach $T.products as product}
Product Name: {$T.product.name}
Product Price: {formatPrice($T.product.price)}
{#/for}
jTemplates允許您可以在一個單一文件定義多個模板,雖然在MyTemplate.htm文件沒有演示此功能。
最後,MyData.htm文件如下所示:
{"products": [
{ "name": "Laptop", "price": "788.67" },
{ "name": "Comb", "price": 2.50 },
{ "name": "Pencil", "price": 1.99 }
] }
當然,包含在MyData.htm的內容有數據庫動態生成。
PURE(純粹分離式渲染引擎)
PURE 模板引擎的目的在於使開發人員不使用任何特殊標記來聲明模板。使用PURE有兩種方法:autoRender()方法或render()方法。
使用autoRender()方法時,PURE自動將JSON的屬性名稱映射到css的class上。例如,你可以編寫下面的代碼來顯示單個產品。
$(showProduct);
function showProduct() {
var product = { name: "Laptop", price: 788.67 };
$('div.product').autoRender(product);
}
autoRender()通過將product的name和price 屬性分別映射到class為name和price 的元素上來顯示產品。
<div class="product">
Product Name: <span class="name"></span>
<br />
Product Price: <span class="price"></span>
</div>
請注意,模板中沒有特殊字符,PURE用純粹的HTML完成一切。
autoRender()方法依賴於JSON的屬性名映射到class名的協議,如果你不想依賴於該協議,你可以用render()方法來代替。
function showProduct() {
var product = { name: "Laptop", price: 788.67 };
var directives = { 'span#name' : 'name', 'span#price': 'price'};
$('div.product').render(product, directives);
}
請注意,一系列指令和要顯示的項目傳遞給render()。這些選擇器將選擇器映射到JSON屬性名,第一個指令將Id為name的span元素映射到name屬性,第二個指令將Id為price的span元素映射到price屬性。下面是HTML:
<div class="product">
Product Name: <span id="name"></span>
<br />
Product Price: <span id="price"></span>
</div>
PURE也可以用來渲染一個JavaScript對象數組。例如,下面的代碼渲染一個product數組。
function showProducts() {
var data = { "products": [
{ name: "Laptop", price: 788.67 },
{ name: "Comb", price: 2.50 },
{ name: "Pencil", price: 1.99 }
]};
$('#productTemplate').autoRender(data);
}
由於數組名為product,PURE將每一個product映射到class為product的元素上,以下模板顯示所有的三個product。
<div id="productTemplate">
<div class="products">
Product Name: <span class="name"></span>
<br />
Product Price: <span class="price"></span>
</div>
</div>
ASP.NET Ajax 模板
在 ASP.NET的Ajax庫支持客戶端模板。這個庫支持很多先進的功能:
例如,您可以使用下面的代碼在模板中渲染產品列表:
var products = [
{ name: "Laptop", price: 788.67 },
{ name: "Comb", price: 2.50 },
{ name: "Pencil", price: 1.99 }
];
Sys.require([Sys.components.dataView], function () {
$("#products").dataView(
{
data: products
});
});
DataView()方法用來在一個模板中顯示系列產品。模板包含在名為product的div元素中。
<div id="products" class="sys-template"> <div id="{{ $id('product') }}"> Product Name: {{ name }} <br /> Product Price: {{ formatPrice(price) }} </div> </div>
請注意,ASP.NET Ajax模板只是一個dom元素。因為模板僅僅是一個DOM元素,模板就不需要包裹在SCRIPT或TEXTARE或HTML注釋中。在這種情況下,一個DIV元素為每一個產品創建一個新實例。
此外,請注意$id()偽變量。$id()解決模板和元素id的有關問題。如果你為一個模板添加一個帶有id的元素,模板用來收集選項,結果是將出現重復id。$id()偽變量為每一個模板實例生成唯一的ids,使你避免了這個問題。
ASP.NET Ajax還支持一種所謂的動態模板。想想以下,你已經為每一個新產品創建了一個模板,通常的產品模板看起來是這樣的:
<!-- New template -->
<div id="newTemplate" class="sys-template">
<div>
<img src="new.gif" />
Product Name: {{ name }}
<br />
Product Price: {{ formatPrice(price) }}
</div>
</div>
<!-- Normal template -->
<div id="normalTemplate" class="sys-template">
<div>
Product Name: {{ name }}
<br />
Product Price: {{ formatPrice(price) }}
</div>
</div>
這兩個模板其實是一樣的,除非新的模板顯示一個新的圖像。
你可以在每一個模板顯示之前創建一個itemRendering事件處理程序。在該程序中,你可以指定用來顯示數據項的模板。
function itemRendering(dataView, args) {
// Get the current data item
var product = args.get_dataItem();
// Set the template dynamically
if (product.dateCreated.getFullYear() == 2010) {
args.set_itemTemplate("#newTemplate");
} else {
args.set_itemTemplate("#normalTemplate");
}
}
上面的代碼使用兩個模板中的一個來顯示產品。如果產品是新的(創建於2010年)用newTemplate來顯示,否則用normalTemplate模板。
ASP.NET Ajax還支持一種所謂的動態占位。動態占位使你可以在文檔的不用位置顯示不同項。例如,你可能希望你所有的新產品在你文檔中的“新產品”區域顯示:
<h1>New Products</h1>
<div id="newPlaceholder"></div>
<h1>All Products</h1>
<div id="normalTemplate" class="sys-template">
<div>
Product Name: {{ name }}
<br />
Product Price: {{ formatPrice(price) }}
</div>
</div>
請注意名為newPlaceholder的div元素。你希望所有新產品都出現在這裡。
這裡是你如何編寫itemRendering處理程序在新的占位符中放置新產品--創建於2010的產品。
function itemRendering(dataView, args) {
// Get the current data item
var product = args.get_dataItem();
// Set the template dynamically
if (product.dateCreated.getFullYear() == 2010) {
args.set_itemPlaceholder("#newPlaceholder");
}
}
這部分包含一個提案--給jQuery核心中添加一個聲明和渲染模板的標准方法。這節包括模板API、示例代碼和討論要點。
通過給單個的數據項或一組數據項應用一個模板來生成DOM元素。
jQuery("#template")
.render(arrayOrObject, options)
.appendTo("selector");
參數
示例
下面是一個使用render()方法的簡單例子。render()方法用來以列表的方式顯示系列產品的name和price。
<script type="text/javascript">
jQuery(function(){
var products = [
{ name: "Product 1", price: 12.99},
{ name: "Product 2", price: 9.99},
{ name: "Product 3", price: 35.59}
];
$("#template")
.render(products)
.appendTo("ul");
});
</script>
<script id="template" type="text/html">
<li>{%= name %} - {%= price %}</li>
</script>
<ul></ul>
執行上面的代碼得到的是下面的無需列表。
jQuery DOM操作方法支持聲明一個模板。例如,你可以使用jQuery.fn.append方法:
jQuery("selector")
.append("#template", arrayOrObject, options);
參數
示例
下面的代碼使用append()方法而不是render()方法在一個無需列表中顯示系列產品的name和price。
<script type="text/javascript">
jQuery(function(){
var products = [
{ name: "Product 1", price: 12.99},
{ name: "Product 2", price: 9.99},
{ name: "Product 3", price: 35.59}
];
$("ul").append("#template", products);
});
</script>
<script id="template" type="text/html">
<li>{%= name %} - {%= price %}</li>
</script>
<ul></ul>
你可以指定一個或多個編譯的模板給Query.templates對象設置。當你想給模板一個語義化的名字以便在一個文檔中很方便的多次使用同一模板時,這很有用。
用jQuery.tmpl()函數編譯一個模板
示例
<script type="text/javascript">
// Assign compiled template
jQuery.templates.foo = jQuery.tmpl("<li>{%=name%}</li>");
// use name foo as template in append() method:
jQuery("#container").append("foo", products);
</script>
在一個模板實例中,你可以使用兩個內置的函數text()和html()渲染一個數據項。你可以通過給jQuery.tmpFn對象指定一個新的函數在模板實例中擴展一組可用的函數。
下面的代碼向你演示了如何創建even()函數,該函數在輪替模板時返回true。在下面的示例中,even()函數用來在模板中交替呈現粗體行。
<script type="text/javascript">
$(function() {
var products = [
{ name: "Product 1", price: 12.99 },
{ name: "Product 2", price: 9.99 },
{ name: "Product 3", price: 35.59 }
];
$.tmplFn.even = function() {
var context = jQuery._.context;
return (context.index % 2 === 0);
};
$("div").append("#template", products);
});
</script>
<script id="template" type="text/html">
<div>
{% if (even()) { %}
<b> {%= name %} </b>
{% } else { %}
{%= name %}
{% }; %}
</div>
</script>
<div>
</div>
表達式可以用{%= ... %} 的語法形式插入,這個分隔符最大限度的減少了編碼標記的機會,同時避免與現有的服務器端和客戶端的擴展標記相沖突(例如:{%=...%}可能與ASP和 ASP.NET相沖突)。
示例
簡單的數據插入:
<script type="text/html" id="tmp1">
<li>{%= last %}, {%= first %}</li>
</script>
表達式為javascript,你可以調用任何JavaScript函數用或使用更復雜的表達式。但是,注意,保持模板盡可能的簡單是首選,後面要描述的兩個回調有助於理解這些。
<script type="text/html" id="tmp1">
<li>{%= last + " " + first %}</li>
</script>
默認情況下,數據項在模板中被渲染時不會被HTML編碼。如果你在一個模板中顯示用戶提交的數據,那麼惡意用戶就能夠執行跨站點腳本攻擊(XSS)。
注意下面代碼中第一個產品的名字,第一個產品包含一個沒有任何惡意的onClick事件處理程序,當數據項顯示,有人點擊了產品名字,JavaScript得到執行。
<script type="text/javascript">
jQuery(function(){
var products = [
{ name: "<a onclick='alert(\"do evil\")'>click here</a>", price: 12.99},
{ name: "Product 2", price: 9.99},
{ name: "Product 3", price: 35.59}
];
$("ul").append("#template", products);
});
</script>
<script id="template" type="text/html">
<li>{%= name %} - {%= price %}</li>
</script>
<ul></ul>
為了很容易的在一個模板中給要顯示的數據進行HTML編碼,使你免遭這種XSS的進攻,可以使用一個名為text()的內置函數。text()函數將一個數據項轉換成文本節點。這裡告訴你如何使用text()函數。
<script type="text/javascript">
jQuery(function(){
var products = [
{ name: "<a onclick='alert(\"do evil\")'>click here</a>", price: 12.99},
{ name: "Product 2", price: 9.99},
{ name: "Product 3", price: 35.59}
];
$("ul").append("#template", products);
});
</script>
<script id="template" type="text/html">
<li>{% text(name) %} - {%= price %}</li>
</script>
<ul></ul>
除了表達式,你可以在模板中插入代碼執行自定義邏輯、條件或循環。代碼塊用{%....%}語法來分隔(沒有=)。
示例
這個例子顯示系列產品的name,如果有可用的“specials”則顯示。
<script type="text/html" id="tmp1">
<li>
{%= name %}
{% if (specials.length) { %}
<ul>
{% for (var i = 0, l = specials.length; i < l; i++) { %}
<li>{%= specials[i].details %}</li>
{% } %}
</ul>
{% } %}
</li>
</script>
在模板中,你可以使用一個名叫$context 的特殊變量,這與提案後面要描述的rendering和rendered回調是同一個對象。$context具有以下屬性:
你對$context 變量的修改在模板中將保持一致。例如,你可以在render()函數中給$context 變量添加計算字段。
function rendering(context) {
context.tax = context.dataItem.price * 0.23;
}
你可以在模板中使用tax計算字段,保持模板簡單,避免需要調試的內嵌表達式。
<script type="text/html" id="tmp1">
The product with tax costs {%= $context.tax %}
</script>
你可以使用$context.options變量指向任何需要傳遞給render()和append()函數的選項。下面的示例演示了如何根據傳遞給append()函數的showSalePrice 參數值來顯示正常價格或零售價格。
<script type="text/javascript">
jQuery(function() {
var products = [
{ name: "Product 1", price: 12.99, salePrice: 10.99 },
{ name: "Product 2", price: 9.99, salePrice: 7.99 },
{ name: "Product 3", price: 35.59, salePrice: 33.59 }
];
$("ul").append("#template", products, { showSalePrice: true });
});
</script>
<script id="template" type="text/html">
<li>
{%= name %} -
{%= $context.options.showSalePrice ? salePrice : price %}
</li>
</script>
<ul></ul>
注意如何在模板中根據$ context.options.showSalePrice屬性來顯示正常價格或零售價格。
你可以利用傳遞給render()函數或append()的options參數來指定模板回調函數。有兩個回調函數:
以下幾節討論使用rendering和rendered函數的幾種情況:
想象以下你要計算在模板中顯示的每個product的稅率,但你又不想計算稅率的邏輯出現在模板自身中。此時,你可以像這樣在rendering函數中執行稅率計算:
<script type="text/javascript">
jQuery(function() {
var products = [
{ name: "Product 1", price: 12.99 },
{ name: "Product 2", price: 9.99 },
{ name: "Product 3", price: 35.59 }
];
$("ul").append("#template", products, { rendering: rendering });
function rendering(context) {
var item = context.dataItem;
// setup additional information to be used more clearly within the template
// (avoids complex expressions)
item.tax = Math.floor(item.price * 8.75) / 100;
}
});
</script>
<script id="template" type="text/html">
<li>{%= name %} - price with tax {%= price + tax %} </li>
</script>
<ul></ul>
你可以利用rendering()回調取消特定模板實例的渲染。下面的代碼演示了如何僅渲染標了刪除標記的產品。
<script type="text/javascript">
jQuery(function() {
var products = [
{ name: "Product 1", price: 12.99 },
{ name: "Product 2", price: 9.99, deleted: true },
{ name: "Product 3", price: 35.59 }
];
$("ul").append("#template", products, { rendering: rendering });
function rendering(context) {
if (context.dataItem.deleted) {
return false;
}
}
});
</script>
<script id="template" type="text/html">
<li>{%= name %} - {%= price %}</li>
</script>
<ul></ul>
當rendering()返回false時,模板不會顯示。在上面的示例代碼中,當產品標上刪除標記時,模板不會渲染。
利用回調函數,你可以創建其嵌套模板。例如,下面代碼顯示了如何顯示聯系人名單,每個聯系人有一個或多個電話號碼。contactTemplate 用來顯示聯系人名單,phoneTemplate 用來顯示每個電話號碼。
<script type="text/javascript">
$(function() {
var contacts = [
{ name: "Dave Reed", phones: ["209-989-8888", "209-800-9090"] },
{ name: "Stephen Walther", phones: ["206-999-8888"] },
{ name: "Boris Moore", phones: ["415-999-2545"] }
];
$("div").append("#contactTemplate", contacts, {rendered:rendered});
});
function rendered(context, dom) {
$("div.phones", dom)
.append("#phoneTemplate", context.dataItem.phones);
}
</script>
<script id="contactTemplate" type="text/html">
<div>
{%= name %}
<div class="phones"></div>
</div>
</script>
<script id="phoneTemplate" type="text/html">
<div>
Phone: {%= $context.dataItem %}
</div>
</script>
<div></div>
假設你想在模板中使用插件--如jQuery UI Datepicker。此外,想象以下,你希望插件顯示數據項的值。例如,你希望DatePicker 默認顯示數據項的一個date屬性。在這種情況下,你需要利用rendered()回調創建插件並用數據項的值初始化插件。
例如,下面的代碼顯示聯系人的名單,rendered() 用來創建一個jQuery UI Datepicker插件,並在模板中的一個input元素中附加插件。DataPicker 的默認值為聯系人的生日。
<script type="text/javascript">
$(function() {
var contacts = [
{ name: "Dave Reed", birthdate: new Date("12/25/1980") },
{ name: "Stephen Walther", birthdate: new Date("12/25/1977") },
{ name: "Boris Moore", birthdate: new Date("12/25/1934") },
{ name: "Eilon Lipton", birthdate: new Date("12/25/2004") },
{ name: "Assad Safiullah", birthdate: new Date("12/25/1954") }
];
$("div").append("#template", contacts, { rendered: rendered });
});
function rendered(context, dom) {
$("input", dom)
.datepicker({ defaultDate: context.dataItem.birthdate })
}
</script>
<script id="template" type="text/html">
<div>
{%= name %}
<br />
birthdate: <input />
</div>
</script>
<div></div>
如果你不想任何諸如{%=....%}的表達式出現在模板中,你可以在模板實例中使用rendered函數動態的為一個元素賦值。例如,下面模板顯示產品清單。注意,模板只包含HTML標記,沒有表達式。
這些都是在論壇討論中提出的問題。
在目前的提案中,數據項默認是不被編碼的。這意味著,除非你采取額外的行為,用模板顯示用戶提交的數據的文檔將為跨站點腳本(XSS)攻擊大開門戶。在早期的提案中,我們討論為HTML編碼內容創建一個諸如{!productName }特別分隔符。在目前的提案中,我們建議使用text()模板函數,而不是用{%= text(productName) %} 。
在目前的提案中,我們建議在<script id="template" type="text/html"></script>元素中包裹模板。有人提議,較好的選擇是用<style id="template" type="text/html"></style>元素,因為樣式更好的體現了模板表象的性質。
樣式元素的缺點在於不允許出現在文檔的body中,如果你不能修改head元素的內容--例如,你正在使用內容管理系統(CMS)工作,你就無法創建模板。
從技術上講,當前的提案與用SCRIPT或STYLE包裹模板是兼容的。如果你願意,你可以使用Style元素並替換type屬性。
只有幾個人建議模板應代表真正的DOM元素,這也與ASP . NET Ajax 庫創建模板的方法相似。換句話說,給模板使用標准的DOM元素,用display:none隱藏模板。
使用真正的DOM元素模板的主要缺點在於使用真正的DOM元素會導致不良副作用。例如,考慮下面的代碼:
<div id="template" style="display:none">
<img src="{%= imageUrl %}" />
<br /> {% imageTitle %}
</div>
在這種情況下,浏覽器會嘗試加載位於{%= imageUrl } 地址的圖片,這並不是你向發生的。像這樣的例子有很多,一個表達式會導致意想不到的副作用或無效的 HTML。例如,如果是在form中,模板中的input可能post一個實際的值。另一個很好的例子是在<div id="{ = foo %}"/>其中的id屬性包含無效的值。
如有翻譯欠妥之處,請發郵件(dexibe@gmail.com)聯系本人
譯者:Denis deng
譯者網站:Denis'Blog
原文地址:jQuery Templates Proposal
<script type="text/javascript">
$(function() {
var products = [
{ name: "Product 1", price: 12.99 },
{ name: "Product 2", price: 9.99 },
{ name: "Product 3", price: 35.59 }
];
$("ul").append("#template", products, { rendered: rendered });
function rendered(context, dom) {
$("span.name", dom).html(context.dataItem.name);
$("span.price", dom).html(context.dataItem.price);
}
});
</script>
<script id="template" type="text/html">
<li><span class="name"></span> - <span class="price"></span></li>
</script>
<ul></ul>