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