[JavaScript] Tab-based selectie optimaliseren

Pagina: 1
Acties:

  • Yoozer
  • Registratie: Februari 2001
  • Laatst online: 20-01 22:02

Yoozer

minimoog

Topicstarter
Zo, 't is al weer een tijdje geleden dat ik hier echt een topic heb gestart :).

Introductie
Voor een site waar ik nu mee bezig ben wil ik een fatsoenlijk tab-script schrijven. Het bestaande script is compleet inline, werkt niet elegant, gebruikt een tabel met een behoorlijk ranzige deel-afstand-door-tabs factor voor de width voor de tabs te maken, en highlight de actieve tabs niet deugdelijk.

Poging Een
A List Apart geeft een methode. Deze verwacht echter dat je naar een andere pagina gaat en dat er in de tussentijd een refresh optreedt. Ik wil dit allemaal in de pagina zelf geregeld hebben zonder dat er refresh optreedt. Dus, werken met display en zo.

Ik kwam terecht bij Bobby van der Sluis. Wat ik moest hebben was in feite wat hier stond. Natuurlijk ging ik even aan de slag met de code maar

- een standaard copy + paste met de nodige aanpassingen werkt niet
- iets in de rest van de site (waar ik de luxe niet voor had om 't te gaan opzoeken omdat 't een beetje een zooitje is) zorgde ervoor dat het niet werkte.

Poging Twee
Na Quirksmode en Unobtrusive JS een aantal keren bestudeerd te hebben (en jammerlijk falende resultaten te krijgen omdat 't principe niet gelijk duidelijk was omdat ik er toen dus nog geen zak van snapte) ben ik maar zelf aan het prutsen gegaan, en er kwam uiteindelijk iets uitrollen dat... naja, werkt.

Hou s.v.p. in het achterhoofd dat ik onderstaande van vrijwel compleet nul af heb moeten bouwen in een dag of drie inclusief het lees- en leerwerk, dus sorry voor eventueel vieze code en rare verzinsels.

De pagina zelf

HTML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="tabmenu">
    <div id="menuwrapper">
        <ul>
            <li>Tab 1</li>  
            <li>Tab 2</li>  
            <li>Tab 3</li>  
            <li>Tab 4</li>  
        </ul>
    </div>
    <div id="contentwrapper" style="background:#bca; border:1px dashed #bef">
        <div id="tab0">these are the contents of tab one</div>
        <div id="tab1">these are the contents of tab two</div>
        <div id="tab2">these are the contents of tab three</div>
        <div id="tab3">these are the contents of tab four</div>
    </div>
</div>


Opmerking : ik ben er niet dol op dat ik de content id's moet meegeven. Ik zou dit liever ID-loos opgelost willen hebben, daadwerkelijk dynamisch, zodat de persoon die de content aanlevert zich alleen maar hoeft druk te maken om de <div> tags. Ik weet in dit geval helaas niet wat de best practice is maar ik heb het liever niet dat er in de database informatie komt te staan over id's en zo.

Verder weet ik niet of een list de beste oplossing is. Lists voor navigatie zijn leuk en praktisch, maar ik nam aan dat het dan daadwerkelijk een partij links betrof en niet een trucje met hekjes en zo.

CSS
Cascading Stylesheet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
body    {
font-family:"Trebuchet MS", Verdana, Tahoma, sans-serif;
font-size:12px;
}

#menuwrapper ul {
margin-left: 0;
padding-left: 0;
display: inline;
} 

#menuwrapper ul li {
margin-left: 0;
margin-bottom: 0;
list-style: none;
display: inline;
background:#abc;
padding:0 40px 0 40px;
border-left:1px solid #acb;
border-right:1px solid #acb;
}

#menuwrapper ul li.here {
background:#cba;
list-style: none;
display: inline;
background:#bac;
padding:0 40px 0 40px;
border-left:1px solid #acb;
border-right:1px solid #acb;
}

#tab0{background:#bde; width:64px; height:64px; visibility:hidden;}
#tab1{background:#bed; width:64px; height:64px; visibility:hidden;}
#tab2{background:#deb; width:64px; height:64px; visibility:hidden;}
#tab3{background:#dbe; width:64px; height:64px; visibility:hidden;}


JavaScript
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function findTabMenu()  {
if (document.getElementById)
    {   
        // find the tab menu
        tm = document.getElementById("tabmenu");
        // find the menuwrapper
        mw = tm.getElementsByTagName("div")[0];
        tb = mw.getElementsByTagName('li');
        // loop to all tabs and make them clickable
        for(i=0;i<tb.length;i++)
            {
                tb[i].id = "tb"+(i);
                //add backgroundcolor
                tb[i].style.backgroundColor='cyan';
                //add clicking functionality
                tb[i].onclick=function(){return changeTab(this);}
                //tell the user that this tab is clickable (crossbrowser)
                tb[i].style.cursor='pointer';
                tb[i].style.cursor='hand';
            } 
    }
}

function changeTab(chosenTab)
    {
    chosenTab = Number(chosenTab.id.substr(2));
    
    // find the tab menu
    tm = document.getElementById("tabmenu");
    // find the menuwrapper
    mw = tm.getElementsByTagName("div")[0];
    // in the menuwrapper, highlight the first tab
    tb = mw.getElementsByTagName('li'); 
    tb[chosenTab].style.backgroundColor="magenta";
    
    // find the tab menu
    tm = document.getElementById("tabmenu");
    // find the contentwrapper
    cw = tm.getElementsByTagName('div')[1];
    // pick out the divs in the contentwrapper  
    ct = cw.getElementsByTagName('div');
    // make all divs invisible at first:
    for(i=0;i<ct.length;i++)
        {
            ct[i].style.visibility = 'hidden';
            tb[i].style.backgroundColor='cyan';
        }   
    // make selected tab visible again.
    ct[chosenTab].style.visibility = 'visible';
    tb[chosenTab].style.backgroundColor="magenta";
    }

window.onload=function() {findTabMenu();}


Opmerking: kleurtjes zijn momenteel nog even voor demo-purposes. Ik moet dit nog vervangen. Helaas heb ik wel met flicker te maken omdat er een volledige "refresh" wordt uitgevoerd inplaats van een fatsoenlijke toggle. Ik heb tot nu toe nog maar even een visibility:hidden gebruikt om de 'lege' standaard content te laten zien, maar dit wordt natuurlijk een display:none.

Ik geef aan het content-deel door welke tab er wordt aangeklikt door middel van een gestripte string (niet goed, moet met regex). Ik heb 'm helaas ook nog niet zover dat er een default-tab gekozen wordt (de eerste), maar ik dacht, eerst maar hier vragen voordat ik verder blunder.

Werkende versie online voor even te kijken:
http://synerjee.net/tabmenu.html - getest in Opera 8.5, IE6 en Firefox 1.07.

Verzoek:
Op de volgende vragen wil ik een antwoord vinden.

- is de toegepaste strategie enigszins zinnig (semantisch, het niet willen gebruiken van ID's in de HTML zelf)
- wat kan er weggesnoeid worden?
- wat kan er verbeterd worden?

Bonusvraag:
- hoe kan ik backwards compatibility het best garanderen (voor het intranet waar ik dit ook wil loslaten is het geen probleem, allemaal IE6, maar voor de site zelf wil ik liever niet problemen krijgen met oudere browsers)

Zo.. ik hoop dat deze topicstart een beetje aan de eisen voldoet :)

teveel zooi, te weinig tijd


  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

Vanuit markup-oogpunt is het gebruik van id's voor de verschillende tabcontents juist handig; zonder styling en scripting kan je menu dan ook echt als navigatie dienen met links naar de specifieke content per item:
HTML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<h1>tabjes</h1>
<div class="tab">
    <div class="tabmenuwrapper">
        <ul>
            <li><a href="#tab1">Tab 1</a></li>
            <li><a href="#tab2">Tab 2</a></li>
            <li><a href="#tab3">Tab 3</a></li>
            <li><a href="#tab4">Tab 4</a></li>
        </ul>
    </div>
    <div class="tabcontentwrapper">
        <div id="tab1">
            <h2>Tab 1</h2>
            <p>these are the contents of tab one</p>
        </div>
        <div id="tab2">
            <h2>Tab 2</h2>
            <p>these are the contents of tab two</p>
        </div>
        <div id="tab3">
            <h2>Tab 3</h2>
            <p>these are the contents of tab three</p>
        </div>
        <div id="tab4">
            <h2>Tab 4</h2>
            <p>these are the contents of tab four</p>
        </div>
    </div>
</div>


Een andere approach is dat je enkel content hebt wrapped in individuele divs, en dat je daar doorheen loopt, id's toekent en de hele list on-the-fly aanmaakt.

[ Voor 8% gewijzigd door crisp op 14-10-2005 11:20 ]

Intentionally left blank


  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

q&d voorbeeldje van mijn laatste suggestie (list on-the-fly aanmaken op basis van de content):
HTML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<script type="text/javascript">

var selectedTabs = [];
function generateTabs(tabID, selectedTab)
{
    var tab = document.getElementById(tabID);
    var tabmenu = document.createElement('div');
    tabmenu.className = 'tabmenuwrapper';
    var ul = document.createElement('ul');
    var li, a;
    if (tab)
    {
        if (!selectedTab) selectedTab = 1;
        selectedTabs[tabID] = null;
        var sections = tab.childNodes, i = 0, j = sections.length;
        var tabnumber = 1;
        var header;
        while (i < j)
        {
            if (sections[i].nodeType == 1 && sections[i].tagName.toLowerCase() == 'div')
            {
                sections[i].id = tabID + '_' + tabnumber;
                li = document.createElement('li');
                a = document.createElement('a');
                a.setAttribute('href', '#' + tabID + '_' + tabnumber);
                a.onclick = switchTab;
                header = sections[i].getElementsByTagName('h2');
                if (header.length) a.appendChild(header[0].firstChild.cloneNode(false));
                else a.appendChild(document.createTextNode('Tab ' + tabnumber));
                a.relatedTarget = sections[i];
                if (tabnumber == selectedTab)
                {
                    a.className = 'selected';
                    selectedTabs[tabID] = a;
                }
                else
                {
                    sections[i].style.display = 'none';
                }
                li.appendChild(a);
                ul.appendChild(li);
                tabnumber++;
            }
            i++;
        }
        tabmenu.appendChild(ul);
        tab.insertBefore(tabmenu, tab.firstChild);
    }
}

function switchTab()
{
    var href = this.getAttribute('href');
    var tabID = href.substr(1, href.indexOf('_')-1);
    if (selectedTabs[tabID])
    {
        selectedTabs[tabID].relatedTarget.style.display = 'none';
        selectedTabs[tabID].className = '';
    }
    this.className = 'selected';
    this.relatedTarget.style.display = '';
    selectedTabs[tabID] = this;
    return false;
}

</script>
<style type="text/css">
.tabmenuwrapper .selected { font-weight: bold; }
</style>
<h1>tabjes</h1>
<div id="tab1">
    <div>
        <h2>Tab 1</h2>
        <p>these are the contents of tab one</p>
    </div>
    <div>
        <h2>Tab 2</h2>
        <p>these are the contents of tab two</p>
    </div>
    <div>
        <h2>Tab 3</h2>
        <p>these are the contents of tab three</p>
    </div>
    <div>
        <h2>Tab 4</h2>
        <p>these are the contents of tab four</p>
    </div>
</div>
<script type="text/javascript">generateTabs('tab1');</script>

Intentionally left blank


  • Yoozer
  • Registratie: Februari 2001
  • Laatst online: 20-01 22:02

Yoozer

minimoog

Topicstarter
Ik heb even met je oplossing gespeeld en... wow. Dit is inderdaad duidelijker dankzij die anchors en eleganter.

Bedankt!

teveel zooi, te weinig tijd