Nápověda jako vystřižená z Windows – rejstřík

    0

    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