Szép ez a kép itt bal oldalt. Nemde? Talán nem, de nem is az a lényeg, hanem a funkció. Lehet nem tűnik értelmesnek egy html listában az elemeket össze vissza mozgatni. De ha például ezekben a listákban egy űrlap szövegmezői vannak, amiknek számít a sorrendje ( tömbös megvalósítás ), akkor máris értelmét nyeri. De most a cikkben a mozgatás megvalósítására fogok koncentrálni.
JavaScript DOM manipuláló metódusokkal lehet mozgatni különböző csomópontokat szinte bárhova. De arra, hogy több lista elemet két irányba is mozgathatóvá tegyünk, két lehetőség adódna.
- Egy függvényt írunk, ami paraméterként kapja meg a mozgatandó elem azonosítóját és a mozgási irányokat.
- Módosítjuk HTMLLIElement osztályt. Konkrétan felveszünk moveUp() és moveDown() metódusokat a html "li" elemekhez.
{
//ide a műveletek
}
Én az utóbbit választottam. Előnye, hogy onnantól kezdve bármilyen lista elemet az adott listában könnyedén egy metódushívással eggyel lejjebb, vagy feljebb lehet mozgatni. A megoldásomban csak 1 lépést, de módosítható volna, hogy paraméterként átadható legyen a lépések száma. Ezt már az olvasóra bízom. Többszöri hívással is előidézhető ugyanez.
Csak a megoldás érdekel
Mutasd a demot!
Hogyan is lehet módosítani egy javascript osztályt?
A prototype tulajdonságán keresztül. Amit a prototype tulajdonsághoz hozzáadunk függvényeket, változókat, azokat örökli minden példány. Vagyis tartalmazni fogja.
{
//ide a műveletek
}
Ugyanígy lehet módosítani a DOM osztályokat is, csak egyre kell figyelni. Opera, Firefox és Chrome böngészőkkel a következő probléma nem áll fenn, de Internet Explorer 8-ban, ha a html dokumentum DOCTYPE -ja nincs megadva, vagy HTML 4 -es van megadva, akkor semmilyen DOM osztályt nem fog tudni JavaScriptből felismerni. Amivel működik:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Vagy
<!DOCTYPE html>
Na most egy lista valahogy így nézne ki.
<li>
Első
<a href="#">Fel</a>
<a href="#">Le</a>
</li>
<li>
Második
<a href="#">Fel</a>
<a href="#">Le</a>
</li>
</ol>
Na most kizárólag a szebb megjelenés miatt itt a fel, le navigáló linkeket én beteszem egy span-be. Így majd CSS-el jobbra lehet igazítani a két linket külön. Tehát így fog kinézni a lista
<li>
Első
<span>
<a href="#">Fel</a>
<a href="#">Le</a>
</span>
</li>
<li>
Második
<span>
<a href="#">Fel</a>
<a href="#">Le</a>
</span>
</li>
</ol>
Na persze ahhoz, hogy bármi is történjen a linkekre kattintva, kell nekik egy onclick eseményt definiálni. És ugye milyen egyszerű volna, hogy ha abban az eseményben semmi más dolgunk nem volna, mint egy függvényt meghívni, ami végzi a dolgát. Mindenféle paraméterátadogatás nélkül. Természetesen a lista elemet el kell érni, így navigálni kell a DOM fában, de az mindegyik eseményben ugyanaz lesz.
Hogyan kell navigálni?
Minden eseményben a this szócska jelöli azt az objektumot, html csomópontot, ahol az esemény definiálva volt.
<a href="#" onclick="this.style.color='red'; return false;">Katt és vörös leszek</a>
Ez nem tesz mást, mint a linkre kattintva megváltoztatja a saját színét vörösre. a return false a végén azért kell, hogy a kattintás hatására ne akarjon elugrani a böngésző.
Minden csomópont ismeri a saját szülőjét is. Szülőnek az számít, amibe közvetlenül tartozik. Példámban ez a span tag-et jelenti. A szülőre a parentNode tulajdonsággal lehet hivatkozni. Mivel itt a li tag még egy szinttel feljebb van, kétszer kell alkalmazni.
this.parentNode.parentNode
Itt kellene a végén meghívni a moveUp() és moveDown() metódusokat. Amik még nem léteznek. De rögtön mutatom azt is. Tehát a lista html része a következő formában fog kinézni most már.
<li>
Első
<span>
<a href="#" onclick="this.parentNode.parentNode.moveUp(); return false;">Fel</a>
<a href="#" onclick="this.parentNode.parentNode.moveDown(); return false;">Le</a>
</span>
</li>
<li>
Második
<span>
<a href="#" onclick="this.parentNode.parentNode.moveUp(); return false;">Fel</a>
<a href="#" onclick="this.parentNode.parentNode.moveDown(); return false;">Le</a>
</span>
</li>
</ol>
Annyit beszéltem már, jöjjön a JavaScript rész
Először is hozzunk létre egy HTMLLIElement.js nevű fájlt. Abban pedig szükség lesz moveUp és moveDown függvények kifejtésére. Előbb nézzük, miből is áll majd.
Megkeresni a következő (vagy előző) lista elemet (ha van) és a következő után ( vagy előző elé ) be kell tenni az aktuális lista elemet. A keresésre külön metódus is van. nextSibling és previousSibling nevűek. Egyetlen bajuk, hogy a lista elemek közötti üres szóközök és enterek összességében egy "Siblinget" (testvér csomópont) alkotnak. Ami nem biztos, hogy ott van. Ezért célszerűbb a keresésre is írni egy nextLi és egy prevLi metódust.
Ezeknek meg kell nézniük, hogy van-e előző, vagy következő elem. A HTMLLIElement is mindig tudja, ki a szülője, így elérhető maga a lista csomópont. Legyen az rendezett (ol), vagy rendezetlen (ul). Le kell kérdezni belőle az elemeket, és megnézni, hogy az aktuális elem az utolsó, vagy éppen első. Ha nincs előző, akkor a prevLi adjon vissza null értéket. Egyébként magát a következő objektumot. Illetve ha egyáltalán nincs egyetlen elem sem, akkor - bár ilyenkor nincsenek linkek se - akkor is adjon vissza null értéket. Ehhez hasonlóan működik a nextLi is. Lássuk:
{
var ul = this.parentNode;
var nodes = ul.getElementsByTagName('li');
if (!nodes.length)
{
return null;
}
if (nodes[nodes.length-1] != this)
{
var next = this;
do
{
next = next.nextSibling;
}
while (next.nodeName.toLowerCase() != 'li');
return next;
}
return null;
}
HTMLLIElement.prototype.prevLi = function()
{
var ul = this.parentNode;
var nodes = ul.getElementsByTagName('li');
if (!nodes.length)
{
return null;
}
if (nodes[0] != this)
{
var prev = this;
do
{
prev = prev.previousSibling;
}
while (prev.nodeName.toLowerCase() != 'li');
return prev;
}
return null;
}
Itt látható, hogy ciklusban határozza meg a következő elemet. Erre nem lenne nagy szükség, mivel két "li" között jó esetben maximum white-space karakterek vannak, és együttesen egy elemnek számítanának, de jobb a biztonság. Ezek után már szinte gyerekjáték megírni a moveUp() és moveDown() metódusokat.
{
var ul = this.parentNode;
var next = this.nextLi();
if (next != null)
{
ul.insertBefore(next,this);
}
}
HTMLLIElement.prototype.moveUp = function()
{
var ul = this.parentNode;
var prev = this.prevLi();
if (prev != null)
{
ul.insertBefore(this,prev);
}
}
Az insertBefore metódus egy csomópont elé szúr be egy másik csomópontot. És ha az már el volt helyezve valahol a dokumentumban, akkor onnan kiemeli. Nem másolja. Tehát gyakorlatilag áthelyezésként funkcionál most. Mivel insertAfter metódus JavaScriptben nincs (Osztálykönyvtárak bővítéseit nem számítva), ezért a moveDown() metódus tulajdonképpen a következő elemet helyezi az aktuális elé. A hatás ugyanaz.
A teljes megoldás kompletten:
index.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>HTML lista újrarendezése javascripttel</title>
<script type="text/javascript" src="HTMLLIElement.js"></script>
<link rel="stylesheet" type="text/css" href="css.css" />
</head>
<body>
<ol class="XLlistItems">
<li>
Első
<span>
<a href="#" onclick="this.parentNode.parentNode.moveUp(); return false;">Fel</a>
<a href="#" onclick="this.parentNode.parentNode.moveDown(); return false;">Le</a>
</span>
</li>
<li>
Második
<span>
<a href="#" onclick="this.parentNode.parentNode.moveUp(); return false;">Fel</a>
<a href="#" onclick="this.parentNode.parentNode.moveDown(); return false;">Le</a>
</span>
</li>
<li>
Harmadik
<span>
<a href="#" onclick="this.parentNode.parentNode.moveUp(); return false;">Fel</a>
<a href="#" onclick="this.parentNode.parentNode.moveDown(); return false;">Le</a>
</span>
</li>
<li>
Negyedik
<span>
<a href="#" onclick="this.parentNode.parentNode.moveUp(); return false;">Fel</a>
<a href="#" onclick="this.parentNode.parentNode.moveDown(); return false;">Le</a>
</span>
</li>
</ol>
</body>
</html>
HTMLLIElement.js
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
HTMLLIElement.prototype.nextLi = function()
{
var ul = this.parentNode;
var nodes = ul.getElementsByTagName('li');
if (!nodes.length)
{
return null;
}
if (nodes[nodes.length-1] != this)
{
var next = this;
do
{
next = next.nextSibling;
}
while (next.nodeName.toLowerCase() != 'li');
return next;
}
return null;
}
HTMLLIElement.prototype.prevLi = function()
{
var ul = this.parentNode;
var nodes = ul.getElementsByTagName('li');
if (!nodes.length)
{
return null;
}
if (nodes[0] != this)
{
var prev = this;
do
{
prev = prev.previousSibling;
}
while (prev.nodeName.toLowerCase() != 'li');
return prev;
}
return null;
}
HTMLLIElement.prototype.moveDown = function()
{
var ul = this.parentNode;
var next = this.nextLi();
if (next != null)
{
ul.insertBefore(next,this);
}
}
HTMLLIElement.prototype.moveUp = function()
{
var ul = this.parentNode;
var prev = this.prevLi();
if (prev != null)
{
ul.insertBefore(this,prev);
}
}
css.css
Document : css.css
Created on : 2010.07.19., 13:05:05
Author : rimelek
Description:
Purpose of the stylesheet follows.
*/
.XLlistItems
{
width: 150px;
}
.XLlistItems li
{
text-align: left;
position: relative;
border-bottom: 1px dotted gray;
}
.XLlistItems li span
{
position: absolute;
right: 0px;
}
DEMO