Sablonkezelés jQuery alatt
Szinte minden webes nyelv rendelkezik valamilyen sablonkezelő rendszerrel (template system), amivel a egyes blokkokat fel tudjuk tölteni változó tartalommal.
PHP-s körökben az egyik legelterjedtebb a Smarty, valamint a PHPTAL, Ruby esetén a szabványos könyvtár része az ERB csomag. A többi nyelvhez is elérhető, vagy már az alap függvénytár része a sablonok kezelése több kevesebb hozzáadott szolgáltatással.
Az alapvető követelmény minden esetben, hogy a sablonmotor a kapott sablonba beírja az átadott változókat. A változók helyének meghatározása és a behelyettesítés módja a sablonmotortól függ, ugyanakkor léteznek erre kvázi szabványok, ahogyan meg lehet határozni egy változó helyét. A JSP (Java Server Pages) JSTL sablonkezelője a ${változonév}
formát preferálja, a perles Template Toolkit például a [% változónév %]
, a Smarty a {$változónév}
formát és így tovább. Amiben hasonlítanak, hogy van valami határoló karaktersorozat és közöttük a változó neve.
JavaScript
Kedvenc nyelvünk, a JavaScript esetén sincs ez másként, itt is léteznek sablonkezelő motorok speciálisan egy adott keretrendszer köré csoportosítva. Ext JS esetén az Ext.Template szolgál segítségül, használata viszonylag egyszerű.
Ennél valamivel kifinomultabb a MooTools Mooml sablonkezelője, ahol JavaScripttel is lehet generálni a HTML sablont, és közvetlenül a DOM-ban is elhelyezhető <script type="text/mooml" name="sablon-neve">Sablon</script>
formában. Természetesen a többi keretrendszer is kínál vagy már a rendszer részeként, vagy külső modulként sablonkezelőt.
jQuery sablonkezelők
jQuery esetén is elérhető rengeteg bővítmény. Ezek közül is fontosnak tartom azt, melyet John Resig, a jQuery megalkotója JavaScript Micro-Templating néven tett közzé; ezen alapszik a jQuery Micro Template plugin.
JavaScript Micro-Templating
A rendszer sokmindenre képes, viszont ezt érdekes megvalósítással teszi, ami nem feltétlenül szimpatikus több fejlesztő számára, akik úgy gondolják hogy az eval
és társai maga a gonosz. Ugyanis a rendszer lényege, hogy new Function
híváson keresztül a sablonból JavaScript kódot generál, aminek lehet biztonsági kockázata, viszont ha a sablon elkészült, akkor nagyon gyors, és a sablonban levő JavaScript kódot is képes lefuttatni. Álljon itt egy példa egy majdnem statikus sablonra:
<div id="<%=id%>" class="<%=(i % 2 == 1 ? " even" : "odd")%>">
<div class="alpha">
<img src="<%=profile_image_url%>"/>
</div>
<div class="omega contents">
<p><b><a href="/<%=from_user%>"><%=from_user%></a>:</b> <%=text%></p>
</div>
</div>
Mint látható, az első sorban a <div>
id
tulajdonsága a majdan átadott id
változó értéke lesz, az osztálya pedig attól függően, hogy i
páros-e vagy páratlan, even
illetve odd
értéket vehet fel. A nyelv szabályai viszonlag egyszerűek. A <%
jelzi, hogy JavaScript kód következik, ha ezt =
jel követi, akkor egy értéket fog beírni, egyébként pedig az azt követő JavaScript utasítássorozat fog lefutni, például:
<% for (var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
Amennyiben a sablonunkat egy HTML elemben helyeztük el, akkor hivatkozhatunk rá annak azonosítójával, vagy átadhatjuk magát a sablont is. Például:
<script type="text/html" id="link_tmpl">
<a href="<%=link%>"><%=title%></a>
</script>
Ezen HTML kód esetén a használat:
tmpl('link_tmpl', {'link': 'http://weblabor.hu', 'title': 'Weblabor'});
Az ehhez kapcsolódó jQuery bővítmény már nem rendelkezik ilyen szép kóddal, és igazából csak röviden szeretném bemutatni, mivel ugyanazt tudja mint az eredeti, csak szerintem rosszabbul.
<div id="link_tmpl">
<a href="<:=link:>"><%=title%></a>
</div>
A kapcsolódó JavaScript kód pedig:
$('#link_tmpl').drink({'link': 'http://weblabor.hu', 'title': 'Weblabor'});
jQuery Templates
A jQuery Templates kicsit más alapokon nyugszik. A lényeg, hogy itt is reguláris kifejezésekkel zajlik a cserélés, de csak abban az esetben történik eval
hívás, ha le is fordítjuk a cserélési procedúrát, ami sokat tud gyorsítani a sablon alkalmazása esetén, ugyanakkor nem tud vezérlési szerkezeteket.
Ami mégis előnye szerintem, hogy a változókon végrehajthatunk a sablonban függvényeket. Ezekkel testre szabhatjuk, hogy a változó milyen formán jelenjen meg, nem kell minden formáját előkészíteni, valamint a sablon építőjének sem kell ismernie a JavaScript sajátosságait, csak azokat a függvényeket, amiket alkalmaz.
Ezt demonstrálandó készítettem is egy kisebb mintaalkalmazást, ami több JavaScript „szépséget” is tartalmaz.
Flickr képkereső
Az alkalmazásunk egy keresést indít a Flickr azon képeire, melyek rendelkeznek egy megadott kulcsszóval. Magának a keresésnek a lebonyolításához jelen esetben Yahoo! Query Language-t fogunk használni, ezzel annak lehetőségeit is demonstrálva. A YQL eredményhalmazából egy listát fogunk képezni, amit majd jQuery Templates sablonok használatával fogunk a felhasználónak megmutatni.
Yahoo! Query Language
A YQL egy URL alapú lekérdező szolgáltatás, amivel a Yahoo!, és más szervezetek – akik YQL formában is elérhetővé tették adataikat –, adatbázisában kereshetünk. A Yahoo! esetében ez lehet maga a Web, a Yahoo! saját motorját használva, vagy más szolgáltatása, példánk kedvéért a Flickr.
A lekérdezésünk ebben az esetben a következőképpen néz ki:
SELECT * FROM flickr.photos.info WHERE photo_id IN (SELECT id FROM flickr.photos.search(1, 10) WHERE tags = "cimke")
Ezzel kikeressük azon Flickr fényképek információit, amik azonosítója megtalálható azon 1-től 10-ig terjedő fotó között, amelyek rendelkeznek a „cimke” címkével.
Az 1-től 10-ig azért lett felhasználva, hogy ha később például lapozót szeretnénk készíteni, könnyedén ki lehessen bővíteni az alkalmazásunkat, ne kelljen a lekérdezésünket is újraírni.
A lekérdezést a YQL-nek átadva egy XML dokumentumot vagy JSON-t kapunk, melyet feldolgozunk az alkalazásunk igényeinek megfelelően. A lekérdezés URL-je a következőképpen néz ki:
http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20flickr.photos.info%20where%20photo_id%20in%20(select%20id%20from%20flickr.photos.search(1,10)%20where%20tags%3D%22cimke%22)&format=json&callback=cbfunc
A q
paraméter tartalmazza a lekérdezésünket, természetesen megfelelően kódolva, a format
a visszatérés formáját (xml
vagy json
), valamint megadhatunk egy opcionális callback
paramétert, ami JSONP esetén lesz hasznos.
Sablonok
Alkalmazásunk váza a következőképp néz ki:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jQuery template</title>
</head>
<body>
<!-- Űrlap, ami majd a keresést végzi -->
<form method="get" action="#">
<label>Kulcsszó: <input type="text" /></label>
<input type="submit" value="Keresés" />
</form>
<div id="results" class="section">
<!-- Ide kerülnek majd az eredmények -->
</div>
<!-- Eredménylista sablon -->
<script type="text/html" id="template-results">
<![CDATA[
<h3>Eredmények</h3>
<ul class="hfeed"></ul>
]]>
</script>
<!-- "Nincs eredmény" sablon -->
<script type="text/html" id="template-no-result">
<![CDATA[
<h3>Nincs eredmény</h3>
]]>
</script>
<!-- Sablon az egyes képekhez -->
<script type="text/html" id="template-image">
<![CDATA[
<li class="hentry">
<h3 class="entry-title">
<a href="${link:checkPlain}" title="${title:checkPlain}">${title:truncate(40,1)}</a>
</h3>
<p class="vcard">
<a class="url fn" href="${authorURI:checkPlain}">${authorName}</a>
@
<span class="published">${date}</span>
</p>
<div class="entry-content">
<a href="${link:checkPlain}">
<img src="${thumbnail:checkPlain}" alt="${title:checkPlain}" height="80" longdesc="${link:checkPlain}" />
</a>
<div class="description" title="${description:checkPlain}">
${description:truncate(100,1)}
</div>
</div>
</li>
]]>
</script>
<!-- Szükség van jQuery-re -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<!-- Szükség van jQuery Templates-re -->
<script type="text/javascript" src="http://bitbucket.org/stanlemon/jquery-templates/raw/abe394368c73/jquery.template.js"></script>
</body>
</html>
Mint látható, a HTML dokumentumban létrehoztunk egy <form>
-ot a keresésre, valamint deklaráltuk a sablonokat <script type="text/html" id="sablon-azonosito"></script>
formában. Mivel a böngésző nem tud mit kezdeni a text/html
formájú <script>
-tel, ezért figyelmen kívül hagyja. A CDATA részt az érvényes HTML forma miatt raktam bele, igazából elhagyható, és csak XHTML formátum esetén van értelme.
A sablonokat ezek a CDATA
részek tartalmazzák, és akkor térjünk is rá, hogyan kell a sablonokat értelmezni.
<li class="hentry">
<h3 class="entry-title">
<a href="${link:checkPlain}" title="${title:checkPlain}">${title:truncate(40,1)}</a>
</h3>
<p class="vcard">
<a class="url fn" href="${authorURI:checkPlain}">${authorName}</a>
@
<span class="published">${date}</span>
</p>
<div class="entry-content">
<a href="${link:checkPlain}">
<img src="${thumbnail:checkPlain}" alt="${title:checkPlain}" height="80" longdesc="${link:checkPlain}" />
</a>
<div class="description" title="${description:checkPlain}">
${description:truncate(100,1)}
</div>
</div>
</li>
Ez egy hagyományos HTML, ugyanakkor vannak benne változók és azon végzett műveletek. A későbbiekben majd részletezem a műveleteket, a használt formátum ugyanakkor a következő: ${valtozonev[:muvelet[([paraméterek])]]}
.
Azaz tartalmaz egy változónevet, amit majd a motor be fog helyettesíteni, opcionálisan azon végrehajthat egy műveletet, aminek opcionálisan átadhatunk paramétereket.
Legegyszerűbb forma tehát például a ${date}
, ahol egyszerűen behelyettesítésre kerül majd a date
változó. A ${link:checkPlain}
esetében a link
változón fogja végrehajtani a checkPlain
műveletet, aminek nem adunk át paramétereket. Legbonyolultabb forma a ${description:truncate(100,1)}
ahol a description
változón lefut a truncate
függvény, aminek átadunk két paramétert, a 100-at és az 1-et.
Fontos hangsúlyozni, hogy az átadott paraméterek sztringként fognak a függvényhez jutni, ezért előbb ét kell alakítani azokat, amennyiben nem sztringre van szükségünk.
Ahhoz, hogy kivegyük a sablont a HTML elemből, írtam egy kisebb jQuery plugint.
$.fn.extend({'parseTemplate': function () {
var templates = [];
if (this.length) {
this.each(function () {
templates.push(this.innerHTML.replace(/^[\n\r\t ]*<!\[CDATA\[([^\f]*)\]\]>[\n\r\t ]*$/, '$1'));
});
return this.length == 1 ? templates[0] : templates;
}
return '';
}});
Igazából nem csinál sokat, csak kiveszi az elem tartalmát és amennyiben a tartalom CDATA
-ban van, akkor kiveszi a CDATA
tartalmát. Ha több elem van, akkor tömbként adja vissza a template-eket, ha csak egy elemet választottunk ki jQuery-vel, akkor pedig csak azt az egy sablont adja vissza sztringként. Használata egyszerűen: $('#template-image').parseTemplate();
.
Változó műveletek
Korábban már említettem, hogy a sablonban levő változókon műveleteket lehet végrehajtani. A jQuery Templates plugin alapból csak egy substr()
függvényt tartalmaz, de általában ennél többre van szükségünk. Ezért deklaráljuk a sablonunkban előforduló checkPlain()
és a truncate()
függvényünket. Mivel a jQuery Templates függvénytár kiterjeszthető, igen egyszerű dolgunk van.
$.extend($.template.helpers, {
/**
* Segédfüggvény, szöveget alakít Bool értékké.
*/
_boolValue: function(value) {
switch (value) {
case 'false':
case '0':
return false;
case 'true':
case '1':
return true;
default:
return !!value;
}
},
/**
* Megfelelő hosszúságúra vág egy szöveget.
*
* @param value
* A szöveg, amit vágni akarunk.
* @param length
* A méret amire vágni szeretnénk.
* @param wordSafe
* Szóhatáron vágjon-e.
* (Használjunk Bool-ra hasonlító értékeket 0 / 1 vagy true / false).
* @return
* A méretre vágott szöveg.
*/
truncate: function (value, length, wordSafe) {
var text = String(value);
wordSafe = this._boolValue(wordSafe);
if (text.length > length) {
if (wordSafe) {
var lastSpace = text.lastIndexOf(' ', length);
// létezik a space ÉS nem a 0. pozícióban
text = (lastSpace > 0) ? text.substring(0, lastSpace) : text.substring(0, length);
} else {
text = text.substring(0, length);
}
text += '…';
}
return text;
},
/**
* HTML szöveg speciális karaktereit kódolja,
* így elemek tulajdonságaiban is szabadon fel lehet használni.
*
* @param value
* A szöveg, amiben kódolni kell a speciális karaktereket.
* @return
* Kódolt szöveg.
*/
checkPlain: function (value) {
return value.replace(/[<>&"']/g, function (match) {
switch (match) {
case '"':
return '"';
case "'":
return ''';
case '&':
return '&';
case '<':
return '<';
case '>':
return '>'
}
return match;
});
},
});
Amint látható, kiterjesztettük a $.template.helpers
objektumot, ahol a jQuery Templates a változó műveleteit tárolja, és kibővítettük pár újabb művelettel.
A truncate()
függvény a szöveget vágja megfelelő hosszúságúra, akár szóhatárt figyelembe véve is, a checkPlain()
pedig a HTML-ben használt karaktereket kódolja.
A _boolValue()
a függvények között igazából kakukktojás, mivel ez egy segédfüggvény, amire a többi sablonműveletünk támaszkodhat, amennyiben Bool
értéket szeretne használni a kapott paraméterek között.
Alkalmazás
A jQuery Templates alkalmazása a következőképpen néz ki:
- Előkészítjük a sablonunkat
var sablon = $.template('sablon', opciok);
formában. - Alkalmazzuk a sablonra a változókat. Ennek több módja létezik:
- Szöveget generálunk a sablonból későbbi felhasználásra a
sablon.apply(valtozok);
formában - Beszúrjuk közvetlenül a HTML-be, akármelyik DOM manipuláló függvény segítségével, például:
$('body').html(sablon, valtozok);
$('body').append(sablon, valtozok);
$('#results').after(sablon, valtozok);
- stb.
- Szöveget generálunk a sablonból későbbi felhasználásra a
A tényleges alkalmazásunk pedig:
(function ($) {
// 'Globális' változók előkészítése
var flickrURL = $.template('http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20flickr.photos.info%20where%20photo_id%20in%20(select%20id%20from%20flickr.photos.search(${start}%2C${to})%20where%20tags%3D%22${query}%22)&format=json&callback=?', {'compile': true}),
parse = function (items) {
// Feldolgozza a Flickr-től kaptott tömböt,
// és a sablonnak szükséges objektumokat gyárt belőle.
return $.map(items, function (entry) {
return {
'authorName': entry.owner.realname || entry.owner.username,
'authorURI': 'http://www.flickr.com/photos/' + entry.owner.nsid,
'title': entry.title || 'N/A',
'link': entry.urls && entry.urls.url && entry.urls.url.content,
'thumbnail': 'http://farm' + entry.farm + '.static.flickr.com/' + entry.server + '/' + entry.id + '_' + entry.secret + '_t.jpg',
'description': entry.description || '',
'date': entry.dates.taken
};
});
};
// Ha készen áll a DOM, indulhat az alkalmazás
$(function () {
var imageTemplate = $.template($('#template-image').parseTemplate(), {'compile': true}),
listTemplate = $('#template-results').parseTemplate();
noListTemplate = $('#template-no-result').parseTemplate();
$('form:first').submit(function (event) {
// Előkészítjük a paramétereket.
var params = {
'start': 1,
'to': 10,
'query': encodeURIComponent($(':text', this).val())
};
// Nem fog lefutni a form alapértelmezett submit eseménye.
event.preventDefault();
// Csinálunk egy JSON(P) lekérdezést YQL-en keresztül a Flick-re,
// és feldolgozzuk az eredményeket.
$.getJSON(flickrURL.apply(params), function (data) {
var items = (
data.query && // Megnézzük,
data.query.results && // hogy van-e eredmény,
data.query.results.photo && // azok fotók-e,
parse(data.query.results.photo) // és feldolgozzuk,
) || [], // vagy üres tömmbel térünk vissza.
results = $('#results').empty(), // Kiürítjük az eredménylistát.
list; // Lista.
// Attól függően, hogy van-e eredmény megfelelő sablont választunk.
results.append(items.length ? listTemplate : noListTemplate);
if (items.length) {
// Vannak eredmények, feltöltjük a listát.
list = results.find('ul:first');
if (list.length) {
// Végigmegyünk az eredényeken, és berakjuk a listába.
$.each(items, function () {
// Mivel átadjuk a sablonnak a változókat,
// ezért az szépen fel lesz töltve.
list.append(imageTemplate, this);
});
}
}
});
});
});
})(jQuery);
A kód azoknak, akik nem szoktak hozza a jQuery-hez kicsit fura lehet, ezért részletezem. A függvényünkben elérhető lesz a jQuery
$
néven. Erre igazából általában nincs szükség, azonban ha több keretrenszerrel dolgozunk egyszerre, akkor összeütközés lehet a változók tekintentében, ezt hivatott elkerülni a (function ($) {})(jQuery)
forma, így a függvényen belül a $
biztosan a jQuery-nek felel meg, valamint a függvényben deklarált változók nem fogják beszennyezni a globális névteret.
Első változónk a flickrURL
már maga is egy sablon, aminek átadhatunk 3 változót: start
, to
és query
, ahol a start
a lista első elemének indexe, a to
az utolsóé, valamint a query
, ami maga a keresés. A callback=?
rész biztosítja, hogy a jQuery AJAX lekérdezés esetén JSONP-ként kezelje a kérést.
A parse
nevű változónk egy függvény, ami a YQL lekérdezés által visszadott JSON tömböt dolgozza fel, és veszi ki változókba az elemeket, melyeket a sablonban majd felhasználunk.
A működés nem túl bonyolult. A <form>
elküldése esetén nem az alapértelmezett művelet fog lefutni, hanem egy AJAX lekérdezés az előbb létrehozott flickrURL
sablon elemeinek kitöltésőből kapott URL segítségével. Amikor visszatér az AJAX kérés a JSON eredénnyel, azt feldolgozzuk, hogy át tudjuk adni az előkészített sablonoknak. Annak függvényében, hogy volt-e találat, megjelenítjük a sablont.
Zárszó
Remélem sikerült rávilágítanom a sablonkezelés rejtelmeire, és arra, hogy a sablonok kezelésének nem csak a szerveroldalon van meg a helye, hanem a mai webes világban elterjedt AJAX-os weboldalak is sokat profitálhatnak belőle. Kezelésük általában nem bonyolult, igazából csak megszokás kérdése, és szerintem kényelmesebb, mint stringeket összefűzni minden egyes eredmény formázásához. A designerek is szabadabban tudnak mozogni, mivel meg tudják tervezni a kinézetet már a böngészőben HTML és CSS segítségével, nem szükséges nekik további JavaScript ismereteket elsajátítani.
■
Nagyon jó!
folytatás?
Folytatás
Addig is a fenti kódot élesben is ki lehet próbálni.
jQuery Template Proposal
http://forum.jquery.com/topic/jquery-templates-proposal
Itt ki is próbálható:
http://github.com/nje/jquery-tmpl