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):

JS Nápověda
 

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">&nbsp;</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.

Žádný příspěvek v diskuzi

Odpovědět