Problembeschreibung
English Version
Zur Sortierung von Elementen in einem Webfrontend kann man sich verschiedene Realisierungen vorstellen. Beispielsweise könnte man für jedes Element eine Textbox bereitstellen, in denen der Benutzer den Index innerhalb der Sortierreihenfolge definiert. Das ist zwar sehr barrierefrei, aber auch unhandlich, wenn man sich vorstellt, dass in einer bestehenden Sortierung das letzte Element weit nach oben verschoben werden soll, da dann viele Indizes angepasst werden müssen. Eine andere Realisierung wären Links mit denen die Elemente jeweils eine Position nach oben oder unten verschoben werden können. Das erfordert jedoch häufiges Neuladen der Seite und ist für den Benutzer nicht sonderlich ästhetisch anzusehen.
Daher wurde für eine konkrete Realisierung des Problems in FAU.ORG auf die wunderbare Welt der AJAX/Javascript Komponenten von jQuery zurückgegriffen. Konkret auf die Komponenten von jQuery UI. Diese Komponentensammlung umfasst sowohl Interactions (also Elemente wie Draggable, Droppable, Sortable, etc.) als auch Widgets (Datepicker, Progressbar, etc.). Die Sortable-Komponente entsprach genau den Anforderungen des oben beschriebenen Problems.
Integration von jQuery:Sortable in Tapestry
Zunächst müssen die notwendigen Javascript-Bibliotheken eingebunden werden:
[html]<link type=”text/css” href=”${asset:jqueryui/themes/base/jquery.ui.all.css}” rel=”stylesheet” />
<script type=”text/javascript” src=”${asset:scripts/jquery-1.4.1.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.core.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.widget.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.mouse.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.sortable.js}”></script>[/html]
Optional kann man die sortierbaren Elemente (die als unordered List gerendert werden) als Kästen formatieren:
[html]<style type=”text/css”>
#sortable { list-style-type: none; margin: 0; padding: 0; float: left; margin-right: 10px; }
#sortable li { margin: 0 5px 5px 5px; padding: 5px; font-size: 1.0em; width: 350px; }
</style>[/html]
Danach wird die Liste um Sortierfähigkeit erweitert und ein Handler entwickelt, der die Sortierergebnisse speichert:
[javascript]<script type=”text/javascript”>
$(function() {
$(“#sortable”).sortable();
$(“#sortable”).disableSelection();
});
function storeOrder() {
var result = $(‘#sortable’).sortable(‘toArray’);
var context = “”;
for (var id in result) {
if (context == “”) {
context = result[id];
} else {
context = context + “_” + result[id];
}
}
window.location = “/${pageName}” + “.save/” + context;
}
</script>
[/javascript]
Schließlich wird die Liste selbst noch eingebunden und ein Speichernlink bereitgestellt:
[html]<ul id=”sortable”>
<li t:type=”loop” source=”elements” value=”element” id=”${element.id}”>${element.name}</li>
</ul>
<a href=”#” id=”prepare” onClick=”storeOrder()”>${message:save}</a>
<t:actionlink t:id=”save” context=”literal:dummy”></t:actionlink>[/html]
Die einzelnen Listenelemente werden durch invisible Instrumentation der Loop-Komponente von Tapestry erzeugt. Die erste Funktion im Javascript-Block sorgt für die Verschiebbarkeit per Maus der Elemente innerhalb der Liste und verbietet deren Selektierbarkeit.
Beim Klicken auf den HTML-Link wird lediglich die Javascript-Funktion storeOrder() aufgerufen. Diese erzeugt ein Array der IDs der Elemente in ihrer vor dem Klicken aktuellen Reihenfolge. Da diese Liste per ActivationContext an einen Actionhandler auf Tapestry-Serverseite übergeben werden soll, kommaseparierte Liste in diesem Kontext jedoch nicht zugelassen sind, wird die Liste in einen String umgewandelt, in dem die Element-IDs per Unterstrich voneinander getrennt vorkommen. Dies kann auf Serverseite durch ein simples String.split() wieder zerlegt werden. Man beachte, dass dafür ebenfalls ein Actionhandler auf Serverseite bereitgestellt werden muss. Dafür wurde der pseudo-Actionlink erzeugt. Der Redirect der storeOrder()-Funktion ruft den Actionhandler mit dem vorbereiteten ActivationContext schließlich manuell auf.
Demo
Eine Demo der Sortable-Komponente von jQuery UI ist hier zu finden.
Sortable Elements in Tapestry with jQuery UI
Problem description
There are many ways to realize sorting elements in a web frontend. One could provide a textbox for every element, which holds the index within the sorting order. This is barrier free to the fullest, but also very inconvenient. Imagine a ordered list, where the last element should be moved to the very top. With this technique almost all element indexes must be adopted. Another realization would be two links on every element. One to move itself upwards and one downwards. This requires a lot of page reloading and is not really state of the art.
To solve the concrete problem within the FAU.ORG project the wonderful AJAX/Javascript components of jQuery were used. More specific the components of jQuery UI. This collection of components includes Interactions (elements like Draggable, Droppable, Sortable, etc.) and Widgets (Datepicker, Progressbar, etc.). The sortable component matched exactly the requirements of the above mentioned problem.
Integration of jQuery:Sortable in Tapestry
First of all one has to include the required Javascript-libraries:
[html]<link type=”text/css” href=”${asset:jqueryui/themes/base/jquery.ui.all.css}” rel=”stylesheet” />
<script type=”text/javascript” src=”${asset:scripts/jquery-1.4.1.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.core.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.widget.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.mouse.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.sortable.js}”></script>[/html]
The next step comprises optional formatting of the elements (which are rendered as an unordered list):
[html]<style type=”text/css”>
#sortable { list-style-type: none; margin: 0; padding: 0; float: left; margin-right: 10px; }
#sortable li { margin: 0 5px 5px 5px; padding: 5px; font-size: 1.0em; width: 350px; }
</style>[/html]
Now the list is enriched to support sortability and a handler is developed, which stores the order of elements:
[javascript]<script type=”text/javascript”>
$(function() {
$(“#sortable”).sortable();
$(“#sortable”).disableSelection();
});
function storeOrder() {
var result = $(‘#sortable’).sortable(‘toArray’);
var context = “”;
for (var id in result) {
if (context == “”) {
context = result[id];
} else {
context = context + “_” + result[id];
}
}
window.location = “/${pageName}” + “.save/” + context;
}
</script>[/javascript]
Finally the list itself must be included and a link to store the changes provided:
[html]<ul id=”sortable”>
<li t:type=”loop” source=”elements” value=”element” id=”${element.id}”>${element.name}</li>
</ul>
<a href=”#” id=”prepare” onClick=”storeOrder()”>${message:save}</a>
<t:actionlink t:id=”save” context=”literal:dummy”></t:actionlink>[/html]
Every list element is created with invisible instrumentation of the Loop-component of Tapestry. The first function in the Javascript-block enables moving elements by mouse within the list and disables selections.
When clicking the HTML-link the storeOrder() Javascript-function is called. This function creates an array of the element-IDs in the most recent defined order. As the list will be handled over to an ActionHandler-function by ActivationContext on Tapestry server-side and comma-separated lists are forbidden within this context, the array is converted to a single string, where the IDs are separated by underscore. This string can easily be converted to an array again with the String.split() Java-function. Be aware, that therefore an ActionHandler has to be provided on server-side. To accomplish this a pseudo ActionLink is created within the Tapestry-Template. The redirect of the storeOrder()-function calls the ActionHandler with the given context manually.
Demo
A demo of the Sortable-component of jQuery UI can be found here.