*/ ?>

Filtrovanie obsahu dokumentu bez použitia Ajaxu

Prefiltrovanie nejakého dlhého zoznamu. Šlo by to urobiť Ajaxom. Ale načo zaťažovať server v prípade, že je už aj tak celý zoznam zobrazený na stránke? Stačí na to jednoduchý objekt a dokonca je to rýchlejšie než séria ajaxových dotazov.

Čo to má robiť?

Otvor praktickú ukážku

V základnej verzii to vezme zoznam položiek (napríklad odrážkový zoznam UL) a nejaké textové políčko. Pri písaní do textového políčka to prejde celý zoznam a schová všetky položky, ktoré neobsahujú text v políčku. Na schovávanie položiek sa použije nejaká jednoduchá CSS trieda, ktorá bude obsahovať akurát display:none.

Aby to bolo trochu univerzálnejšie, malo by to mať pár konfigurovateľných parametrov:

  • Názov triedy na schovávanie elementov.
  • CSS selector pre výber elementov. Nie vždy to totiž musí byť odrážkový zoznam (t.j. LI). Môže sa to použiť trebárs na riadky tabuľky, alebo rovno na celé bloky s nejakou triedou.
  • Umožnenie definície oblasti v rámci elementu (čiže opäť CSS selector), v ktorom sa má vyhľadávať. To pre prípad, že by som napríklad mal zoznam článkov s titulkami a náhľadmi, pričom filtrovať by som chcel iba podľa titulkov, ale skrývať by som chcel celé bloky.

Čerešnička na torte by bola jednoduchá možnosť prefiltrovať položky manuálne (t.j. zavolaním nejakej metódy a nie písaním do textového políčka).

Kostra objektu

Z požiadavkov na skript sa pekne vylúpne základná kostra:

var ContentFilter = Class.create({

	initialize : function ( element, input, options ) {
		this.element = $( element );
		this.input = $( input );
		this.options = Object.extend( {
			// defaultne hodnoty
		}, options || {} );

		// filter sa spusti pri kazdom stlaceni klavesy v policku,
		// alebo pri zmene jeho hodnoty (napr. pri copy/paste)
		this.input.observe( 'keyup', this.filter.bind( this ) );
		this.input.observe( 'change', this.filter.bind( this ) );
	},

	// pomocna funkcia, ktora sa spusti pri zmene obsahu policka
	filter : function () {
		this.filterBy( this.input.value );
	},

	// tuto funkciu je mozne volat aj manualne
	filterBy : function ( search ) {
		// mechanizmus filtrovania
	}

});

Teraz už stačí len dopísať pár detailov a samotnú funkčnosť.

Konfigurovateľné položky (options)

  • hiddenClass – Názov triedy, ktorá sa pridá všetkým elementom, ktoré neobsahujú hľadaný text. Defaultná hodnota bude “hidden”. Niekde v CSS bude treba zadefinovať .hidden { display: none; }
  • itemSelector – CSS selector, ktorý reprezentuje položky v zozname. Predpokladám že defaultne sa bude skript používať na odrážkové zoznamy, takže tento selector bude LI.
  • searchArea – Opäť CSS selector. Ak bude zadefinovaný, umožní rozlíšiť medzi elementom ktorý sa má zobrazovať/skrývať a medzi elementom, v ktorom sa má hľadať text. Defaultne bude prázdny.
this.options = Object.extend( {
	hiddenClass : 'hidden', // defaultna trieda schovaneho elementu
	itemSelector : 'li', // defaultny CSS selector pre hladanie elementov
	searchArea : false // umozni specifikovat oblast v ramci elementu, kde sa ma hladat
}, options || {} );

Samotné vyhľadávanie

Na vyhľadanie elementov sa použije metóda Element.select(). Tá vráti zbierku elementov, ktoré bude treba prejsť a skontrolovať, či obsahujú daný text.

Na kontrolu sa použije jednoduchý regulárny výraz metódou RegExp.test(). Pozor! Pri generovaní regulárneho výrazu si treba dať pozor, aby to nehádzalo chyby kvôli nevalidnému reťazcu.

Samotný textový obsah elementu sa jednoducho získa odstránením tagov z jeho innerHTML pomocou String.stripTags().

filterBy : function ( search ) {

	// osetrenie pripadu, kedy uzivatel vlozi do inputu string ktory rozhodi regexp
	try { var re = new RegExp( search, 'i' ); }
	catch ( err ) { var re = new RegExp(); }

	var items = this.element.select( this.options.itemSelector );

	for ( var i = 0, totalItems = items.length; i < totalItems; i++ ) {
		var item = items[i];
		if ( re.test( item.innerHTML.stripTags() ) ) {
			item.removeClassName( this.options.hiddenClass );
		} else {
			item.addClassName( this.options.hiddenClass );
		}
	}
}

Priestor pre optimalizáciu výkonu

V skripte sú teraz dve miesta v hodné k optimalizácii.

V prvom rade je tu situácia, kedy bude hľadaný výraz prázdny (napr. keď vo filtrovacom políčku nič nebude). V takom prípade je jasné, že ani jeden element nemá mať hiddenClass a teda nemá zmysel ich po jednom prechádzať a kontrolovať regulárnym výrazom. Stačí zavolať:

items.invoke( 'removeClassName', this.options.hiddenClass );

V druhom rade je tu neustále zisťovanie zoznamu elementov (items) pri každom novom cykle. To je nutné robiť v prípade, že ten zoznam je dynamický a jeho obsah sa môže meniť. Ak sa však s takou situáciou nepočíta, výkonu by rozhodne pomohlo, keby skript tento zoznam získal už pri inicializácii a zapamätal si ho napríklad v premennej this.items.

searchArea

Ostáva doplniť poslednú drobnosť. Ak je zadefinovaná searchArea, treba text hľadať v nej a nie v celom elemente. Záležitosť jednej jednoduchej podmienky:

// ak nie je specifikovana searchArea, pouzije sa cely element
var searchArea = ( this.options.searchArea ) ? item.down( this.options.searchArea ) : item;

if ( re.test( searchArea.innerHTML.stripTags() ) ) {
	item.removeClassName( this.options.hiddenClass );
} else {
	item.addClassName( this.options.hiddenClass );
}

Výsledný skript

Celý objekt bude vyzerať asi takto:

var ContentFilter = Class.create({

	initialize : function ( element, input, options ) {
		this.element = $( element );
		this.input = $( input );
		this.options = Object.extend( {
			hiddenClass : 'hidden', // defaultna trieda schovaneho elementu
			itemSelector : 'li', // defaultny CSS selector pre hladanie elementov
			searchArea : false // umozni specifikovat oblast v ramci elementu, kde sa ma hladat
		}, options || {} );

		// filter sa spusti pri kazdom stlaceni klavesy v policku,
		// alebo pri zmene jeho hodnoty (napr. pri copy/paste)
		this.input.observe( 'keyup', this.filter.bind( this ) );
		this.input.observe( 'change', this.filter.bind( this ) );
	},

	// pomocna funkcia, ktora sa spusti pri zmene obsahu policka
	filter : function () {
		this.filterBy( this.input.value );
	},

	// tuto funkciu je mozne volat aj manualne
	filterBy : function ( search ) {

		// ak sa nepredpoklada ze by sa obsah zoznamu dynamicky menil, je mozne kvoli optimalizacii
		// vykonu zoznam elementov ziskat uz pri inicializacii namiesto kontroly pri kazdom volani filtru
		var items = this.element.select( this.options.itemSelector );

		if ( search ) {

			// osetrenie pripadu, kedy uzivatel vlozi do inputu string ktory rozhodi regexp
			try { var re = new RegExp( search, 'i' ); }
			catch ( err ) { var re = new RegExp(); }

			// prejde textovy obsah vsetkych elementov a zisti ci obsahuju hladany vyraz
			// na zaklade toho prida alebo odoberie triedu
			for ( var i = 0, totalItems = items.length; i < totalItems; i++ ) {

				var item = items[i];

				// ak nie je specifikovana searchArea, pouzije sa cely element
				var searchArea = ( this.options.searchArea ) ? item.down( this.options.searchArea ) : item;

				if ( re.test( searchArea.innerHTML.stripTags() ) ) {
					item.removeClassName( this.options.hiddenClass );
				} else {
					item.addClassName( this.options.hiddenClass );
				}
			}
		} else {

			// optimalizacia vykonu: ak je hladany vyraz prazdny, je jasne ze vyhovuju vsetky elementy
			items.invoke( 'removeClassName', this.options.hiddenClass );

		}
	}

});

Použitie a praktická ukážka

Otvor praktickú ukážku

V základnej verzii stačí obejektu poslať ID zoznamu a ID vyhľadávacieho políčka:

new ContentFilter( 'idZoznamu', 'idPolicka' );

Ak chceš objekt customizovať:

new ContentFilter( 'idZoznamu', 'idPolicka', {
	hiddenClass : 'alternativnaTrieda',
	itemSelector : 'div.polozka',
	searchArea : 'h2'
} );

A v prípade, že chceš volať filtrovanie manuálne:

var mojFilter = new ContentFilter( 'idZoznamu', 'idPolicka' );
mojFilter.filterBy( 'nejaky text' );
  1. Zvýrazňovanie zaškrtnutých položiek
  2. Zrovnanie výšok elementov
  3. Zmena štandardného správania Enteru vo formulári
  4. Políčka s hintom
  5. Dynamicky generovaný iframe

Zverejnené 29.1.2009
v kategórii JavaScript.

Nálepky:
, ,

Autor článku

Riki Fridrich, http://fczbkk.com

Som taký dobrý, že som sám sebe vzorom.

Komentáre

webios, 21.2.2009, 15:26

Super feature, ale skusal som ju aplikovat na tabulku, zmenil som css selector na ‘tr’, ale akosi som sa nedopracoval k funkcnemu rieseniu. Funguje to takto, alebo je to daky zlozitejsi sposob?

Dakujem.

Riki Fridrich, 24.2.2009, 8:14

WEBIOS: Môžeš mi ukázať ako vyzerá tvoj kód? Mne to u tabuliek funguje úplne v pohode, stačí zmeniť itemSelector na “TR”:

new ContentFilter( ‘regionsTable’, ‘regionsTableFilter’, { itemSelector : ‘tr’ } );

zef, 17.9.2009, 9:15

Skvelé, ďakujem! Použil som to na storiadkovú tabuľku a funguje perfektne.

Jediný problém mám s entitami. Napríklad hľadaný výraz “sp” neodfiltruje, pretože v každom riadku mám nedeliteľnú medzeru  

Skúšal som v skripte zameniť funkciu stripTags za unescapeHTML, ale správa sa to rovnako. Any idea?

Riki Fridrich, 23.9.2009, 7:48

Sú dve možnosti:

Buď si z prehľadávaného textu odfiltruješ všetky entity (napr. pomocu regexpu “&\w+;”). To by som pridal na miesto, kde sa volá stripTags.

Alebo si upravíš regexp ktorý sa generuje z vyhľadávacieho reťazca tak, aby neprehľadával nič medzi znakmi & a ;. (Bude vyzerať veľmi podobne ako hore uvedený regexp.)

zef, 25.9.2009, 13:16

Vďaka. Tú prvú možnosť som nakoniec zvládol :-)

Vyjadri sa

Tvůj komentář se zobrazí, až ho některý z adminů schválí. Zveřejňovat budeme pouze hodnotné komentáře, které se přímo týkají tématu.


O projekte

Tento projekt vznikol, pretože všetky odborné weby sajú a my sme tým pádom nemali kde publikovať svoje články.