V předchozím článku o simulaci Windows nápovědy pomocí Java Scriptu jsme si ukázali, jak vytvoříme seznam témat a „podtémat“ v rozbalovacím stromu a zobrazení odpovídajících textů nápovědy. Dnes tento skript rozšíříme o další význačný rys Windows nápovědy – rejstřík.
Protože celý skript je dosti rozsáhlý, omezíme se v tomto článku jen na popis změn oproti předchozímu článku, ale i tak jich bude více než dost, snad to nebude vadit. Nejprve ale opět malá ukázka, jak rejstřík funguje (data, tedy texty nápovědy, jsou stejná jako minule):
Po kliknutí na položku rejstříku v nejlevějším formulářovém poli je do prostředního pole vypsán seznam témat, v nichž bylo dané slovo nalezeno, po výběru tématu v prostředním poli je poté do nejpravějšího z trojice polí vypsán text příslušné nápovědy. Určitě jste postřehli jednu odlišnost oproti standardní Windows nápovědě, totiž střední okno, které je zde tak trochu navíc – ve Windows je místo toho, je-li nalezených témat k danému slovu víc, zobrazeno malé „popup“ okénko se seznamem témat. Důvody, které vedly k této odlišnosti, jsou dva: prvním je podle mého názoru uživatelsky trochu nepohodlá manipulace se dvěma okny, druhým důvodem je podobné „nepohodlí“ z hlediska obsluhy JavaScriptem (snad si pamatujete na problémy s dvojklikem pod Netscape, popsané v minulém článku).
Pojďme k samotnému kódu. První změnu musíme učinit už v přidávání textů nápovědy do instance objektu MeHelp – je totiž potřeba nějak označit, které slovo v textu se má zařadit do rejstříku. Zde je k ohraničení slova použit znak dvojité mřížky alias plotu (pod českou klávesnicí Ctrl+Alt+X):
var h1 = new MeHelp() u1 = h1.Add( 0, "Všechny kategorie", "Kliknutím na nadpis\nv levém okně zobrazíte\npopis" ); u2 = h1.Add( u1, "ASP", "Všechno o #ASP# technologii…" ); h1.Add( u2, "ASP a databáze", "Technologie #ASP# se často používá s #databáze#mi" ); h1.Add( u2, "ASP a Session object", "Užitečným objektem je objekt #Session#" ); u3 = h1.Add( u1, "JavaScript", "Vsechno o #JavaSkript#u" ); h1.Add( u3, "JS a formuláře", "Pro ověřování formulářů používejte #JavaScript#" ); h1.Add( u3, "JS a cookies", "#Cookies# jsou dalším zajímavým využitím #JavaScript#u." ); h1.Add( u3, "Další informace", "Další #informace# o JS najdete na\nwww.interval.cz!" ); |
|
Tato změna nás nutí k úpravě metody Add objektu MeHelp – upravená metoda musí zajistit přidání všech „oplocených“ slov do rejstříku (volání this.index.Add…) a také to, aby text nápovědy ukládaný do interních struktur byl očištěn od tohoto pomocného znaku:
this.Add = function( parent, name, txt ) { this.iLength++; indexChar = "#" pomstr = txt for( i=0,fi=-1; ((i=pomstr.indexOf(indexChar,i)) != -1); ) { pomstr = pomstr.slice(0,i) + pomstr.slice(i+1) if(fi==-1) fi=i else { this.Index.Add(pomstr.slice(fi,i),this.iLength) fi=-1 } } this.arrNames[this.iLength] = name this.arrTexts[this.iLength] = pomstr this.arrNodes[this.iLength] = parent this.arrExtracted[this.iLength] = false return this.iLength } |
|
Pochopitelně, členská proměnná Index musí být předem nainicializována, což zajistíme v konstruktoru přidáním následujícího řádku – ano, členská proměnná Index je také objekt, tentokrát instance nové třídy MeHelpIndex:
this.Index = new MeHelpIndex(this) |
|
Než si ukážeme kód objektu MeHelpIndex, dokončíme nutné úpravy v objektu MeHelp – zbývají nám dvě, první se týká úpravy metody SetForm, kdy objekt MeHelp prolinkováváme i na třetí (prostřední) pole HTML formuláře, zde nazvané subfield, druhá úprava se rovněž týká tohoto formulářového pole – jde o novou metodu DeleteSubField, která má za úkol vyčistit toto pole podobně, jako to s polem selectfield minule činila metoda DeleteSelect:
this.SetForm = function( selectfield, textfield, subfield ) { this.selectfield = selectfield this.textfield = textfield this.subfield = subfield } this.DeleteSubField = function() { while(this.subfield.length > 0 ) { this.subfield.options[0] = null; } } |
|
Objekt MeHelpIndex
Stejně jako minule si delší kód objektu uvedeme po částech. Začneme hlavičkou objektu a inicializací jeho interních struktur:
function MeHelpIndex(parent) { this.iLength = 0 this.parent = parent this.arrNames = new Array() this.arrNodes = new Array() |
|
Členská proměnná parent obsahuje odkaz na nadřízený objekt MeHelp; arrNames je pole jednotlivých slov – položek rejstříku, arrNodes obsahuje odkazy na ty položky nadřízeného objektu MeHelp, v nichž se dané slovo vyskytuje – uloženy jsou ve formě řetězce s čísly oddělenými středníky, např: „5;7;12;21;35;“ atd.
Metoda Add, jak již název napovídá, přidává slovo do rejstříku. Pokud je slovo nové, vytvoří pro něj nový záznam v poli arrNames, pokud již v rejstříku existuje, přidá pouze „odkaz“ na položku z nadřízeného objektu MeHelp do pole arrNodes:
this.Add = function( name, node ) { for(i=1;i<=this.iLength;i++) { if(this.arrNames[i]==name) { this.arrNodes[i] += ("" + node + ";") return } } this.iLength++ this.arrNames[this.iLength] = name this.arrNodes[this.iLength] = "" + node + ";" this.Sort() } |
|
V závěru metody Add jsou obě pole přetřiďována metodou Sort – jedná se o obyčejný BubbleSort, k tomu ne příliš efektivně napsaný – pro potencionálně velké nápovědy by určitě stálo zato nahradit jej lepším způsobem třídění:
this.Sort = function() { for(var i=1;i<=this.iLength;i++) { for(var j=i-1;j>0;j–) { if( this.arrNames[j].toLowerCase() > this.arrNames[j+1].toLowerCase() ) { var pomstr = this.arrNames[j] this.arrNames[j] = this.arrNames[j+1] this.arrNames[j+1] = pomstr pomstr = this.arrNodes[j] this.arrNodes[j] = this.arrNodes[j+1] this.arrNodes[j+1] = pomstr } } } } |
|
Metoda WriteIndex pouze vypíše do nejlevějšího pole formuláře všechny položky rejstříku – zde by neměla číhat žádná záludnost:
this.WriteIndex = function() { field = this.parent.selectfield for(i=1;i<=this.iLength;i++) { field.options[field.options.length] = new Option(this.arrNames[i]); field.options[field.options.length-1].value = i } } |
|
Metoda WriteSubIndex musí (při výběru odpovídajícího slova z levého formulářového pole) naplnit prostřední pole seznamem témat, v nichž se dané rejstříkové slovo nachází – a před tím si samozřejmě přislušné pole musí vyčistit. Poslední řádek funkce (this.parent.textfield…) slouží k vyčištění pravého pole formuláře při změně výběru v levém poli – záleží na vámi požadovaném chování nápovědy, zda jej v kódu ponecháte či nikoliv:
this.WriteSubIndex = function() { if( (i=this.parent.selectfield.selectedIndex+1) != 0 ) { field = this.parent.subfield while(field.length > 0 ) { field.options[0] = null; } pomstr = this.arrNodes[i] for( ; ((j=pomstr.indexOf(";")) != -1); ) { k = Math.ceil(pomstr.slice(0,j)) pomstr = pomstr.slice(j+1) field.options[field.options.length] = new Option(this.parent.arrNames[k]); field.options[field.options.length-1].value = k } this.parent.textfield.value = "" } } |
|
Konečně metoda WriteTxt, volaná po klepnutí na položku v prostředním poli, je opět prostinká – má za úkol pouze vypsat kýžený text nápovědy do pravého pole. Opět zde máme jednu pravou závorku navíc jako ukončení definice celého objektu MeHelpIndex:
this.WriteTxt = function() { if( (selindex=this.parent.subfield.selectedIndex) != -1 ) { index = this.parent.subfield.options[selindex].value this.parent.textfield.value = this.parent.arrTexts[index]; } else { this.parent.textfield.value = "" } } } |
|
HTML kód formuláře je trochu odlišný – přibylo nám jedno (prostřední) pole SELECT, naopak ubyl skript rozlišující prohlížeče MSIE a NN. Z handlerů událostí onChange voláme tentokrát odpovídající metody objektu MeHelpIndex, ukrytého v nadřízené instanci objektu MeHelp:
<form name="menu"> <table border="0" cellpadding="0" cellspacing="0" width="200" bgcolor="#C0C0C0"> <tr> <td align="left" colspan="4" bgcolor="#000080"><font color="#FFFFFF"><strong>JS Nápověda</strong></font></td> </tr> <tr> <td align="left" valign="top"> <select name="links" size="10" onChange="h1.Index.WriteSubIndex()"> <option value="0">************************</option> </select> </td> <td align="left" valign="top"> <select name="sublinks" size="10" onChange="h1.Index.WriteTxt()"> <option value="0">************************</option> </select> </td> <td align="left" valign="top"> </td> <td align="left" valign="top"> <textarea rows="9" name="popis" cols="28"> </textarea> </td> </tr></table></form> |
|
A konečně, krátký JS kód za formulářem, který nám tak jako minule „připraví“ formulář na spolupráci s objektem nápovědy, je také jiný:
h1.SetForm( document.menu.links, document.menu.popis, document.menu.sublinks ); h1.DeleteSelect(); h1.DeleteSubField(); h1.Index.WriteIndex(); |
|
Na závěr jedna stručná poznámka k přepínání mezi nápovědou ve stylu „Obsah“ a „Rejstřík“ – ačkoliv JavaScript kód vzniklý sloučením tohoto a předchozího článku zvládá obě úlohy, je pro každou z nich potřeba trochu jiný formulář – nejjednodušší cestou je nesnažit se dostat oba formuláře na jednu stránku (a přepínat např. nastavováním vlastnosti visible), ale umístit kód, potažmo inicializaci nápovědy do sdíleného .js> souboru a každý z obou pohledů umístit na samostatnou stránku, mezi nimiž se přepneme jednoduchým odkazem.
Příště nás čeká další funkční vylepšení JavaScriptové nápovědy – tentokrát půjde o vyhledávání.
Přeji vám příjemný den.