Ajax: jak zrobić dynamiczne zakładki?

Autor: Arkadiusz Tobiasz 30 czerwca 2009

Ajax jest coraz częściej wykorzystywaną technologią na dużych portal, ale również na tych mniejszych. W tym tutorialu dowiesz się w jaki sposób zrobić efektowne, dynamiczne zakładki, które będą mogły posłużyć na twoim portalu jako np. menu czy prezentację newsów. Wykorzystam skrypt pobrany ze strony www.dynamicdrive.com.

W tym celu utworzymy dwa pliki. Pierwszy o nazwie ajaxtabs.css będzie odpowiedzialny za styl naszych zakładek. Poniżej możecie zobaczyć jego strukturę:

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
90
91
92
93
94
95
.shadetabs{
padding: 3px 0;
margin-left: 0;
margin-top: 1px;
margin-bottom: 0;
font: bold 12px Verdana;
list-style-type: none;
text-align: left; /*set to left, center, or right to align the menu as desired*/
}

.shadetabs li{
display: inline;
margin: 0;
}

.shadetabs li a{
text-decoration: none;
position: relative;
z-index: 1;
padding: 3px 7px;
margin-right: 3px;
border: 1px solid #778;
color: #2d2b2b;
background: white url(shade.gif) top left repeat-x;
}

.shadetabs li a:visited{
color: #2d2b2b;
}

.shadetabs li a:hover{
text-decoration: underline;
color: #2d2b2b;
}

.shadetabs li a.selected{ /*selected main tab style */
position: relative;
top: 1px;
}

.shadetabs li a.selected{ /*selected main tab style */
background-image: url(shadeactive.gif);
border-bottom-color: white;
}

.shadetabs li a.selected:hover{ /*selected main tab style */
text-decoration: none;
}

/* ######### CSS for Inverted Modern Bricks II Tabs. Remove if not using ######### */

.modernbricksmenu2{
padding: 0;
width: 362px;
border-top: 5px solid #D25A0B; /*Brown color theme*/
background: transparent;
voice-family: "\"}\"";
voice-family: inherit;
}

.modernbricksmenu2 ul{
margin:0;
margin-left: 10px; /*margin between first menu item and left browser edge*/
padding: 0;
list-style: none;
}

.modernbricksmenu2 li{
display: inline;
margin: 0 2px 0 0;
padding: 0;
text-transform:uppercase;
}

.modernbricksmenu2 a{
float: left;
display: block;
font: bold 11px Arial;
color: white;
text-decoration: none;
margin: 0 1px 0 0; /*Margin between each menu item*/
padding: 5px 10px;
background-color: black; /*Brown color theme*/
border-top: 1px solid white;
}

.modernbricksmenu2 a:hover{
background-color: #D25A0B; /*Brown color theme*/
color: white;
}

.modernbricksmenu2 a.selected{ /*currently selected tab*/
background-color: #D25A0B; /*Brown color theme*/
color: white;
border-color: #D25A0B; /*Brown color theme*/

Kolejnym krokiem będzie stworzenie skryptu odpowiedzialnego za „przeładowywanie się” zakładek napisanego w technologi AJAX. W tym celu utworzymy plik ajaxtabs.js, w którym umieścimy następujący kod:

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//** Ajax Tabs Content script v2.0- © Dynamic Drive DHTML code library (http://www.dynamicdrive.com)
//** Updated Oct 21st, 07 to version 2.0. Contains numerous improvements
//** Updated Feb 18th, 08 to version 2.1: Adds a public "tabinstance.cycleit(dir)" method to cycle forward or backward between tabs dynamically. Only .js file changed from v2.0.
//** Updated April 8th, 08 to version 2.2:
//   -Adds support for expanding a tab using a URL parameter (ie: http://mysite.com/tabcontent.htm?tabinterfaceid=0)
//   -Modified Ajax routine so testing the script out locally in IE7 now works

var ddajaxtabssettings={}
ddajaxtabssettings.bustcachevar=1  //bust potential caching of external pages after initial request? (1=yes, 0=no)
ddajaxtabssettings.loadstatustext="<img src='ajaxtabs/loading.gif' /> Requesting content..."

////NO NEED TO EDIT BELOW////////////////////////

function ddajaxtabs(tabinterfaceid, contentdivid){
this.tabinterfaceid=tabinterfaceid //ID of Tab Menu main container
this.tabs=document.getElementById(tabinterfaceid).getElementsByTagName("a") //Get all tab links within container
this.enabletabpersistence=true
this.hottabspositions=[] //Array to store position of tabs that have a "rel" attr defined, relative to all tab links, within container
this.currentTabIndex=0 //Index of currently selected hot tab (tab with sub content) within hottabspositions[] array
this.contentdivid=contentdivid
this.defaultHTML=""
this.defaultIframe='<iframe src="about:blank" marginwidth="0" marginheight="0" frameborder="0" vspace="0" hspace="0" style="width:100%; height:auto; min-height: 100px"></iframe>'
this.defaultIframe=this.defaultIframe.replace(/<iframe/i, '<iframe name="'+"_ddajaxtabsiframe-"+contentdivid+'" ')
this.revcontentids=[] //Array to store ids of arbitrary contents to expand/contact as well ("rev" attr values)
this.selectedClassTarget="link" //keyword to indicate which target element to assign "selected" CSS class ("linkparent" or "link")
}

ddajaxtabs.connect=function(pageurl, tabinstance){
var page_request = false
var bustcacheparameter=""
if (window.ActiveXObject){ //Test for support for ActiveXObject in IE first (as XMLHttpRequest in IE7 is broken)
try {
page_request = new ActiveXObject("Msxml2.XMLHTTP")
}
catch (e){
try{
page_request = new ActiveXObject("Microsoft.XMLHTTP")
}
catch (e){}
}
}
else if (window.XMLHttpRequest) // if Mozilla, Safari etc
page_request = new XMLHttpRequest()
else
return false
var ajaxfriendlyurl=pageurl.replace(/^http:\/\/[^\/]+\//i, "http://"+window.location.hostname+"/")
page_request.onreadystatechange=function(){ddajaxtabs.loadpage(page_request, pageurl, tabinstance)}
if (ddajaxtabssettings.bustcachevar) //if bust caching of external page
bustcacheparameter=(ajaxfriendlyurl.indexOf("?")!=-1)? "&"+new Date().getTime() : "?"+new Date().getTime()
page_request.open('GET', ajaxfriendlyurl+bustcacheparameter, true)
page_request.send(null)
}

ddajaxtabs.loadpage=function(page_request, pageurl, tabinstance){
var divId=tabinstance.contentdivid
document.getElementById(divId).innerHTML=ddajaxtabssettings.loadstatustext //Display "fetching page message"
if (page_request.readyState == 4 && (page_request.status==200 || window.location.href.indexOf("http")==-1)){
document.getElementById(divId).innerHTML=page_request.responseText
ddajaxtabs.ajaxpageloadaction(pageurl, tabinstance)
}
}

ddajaxtabs.ajaxpageloadaction=function(pageurl, tabinstance){
tabinstance.onajaxpageload(pageurl) //call user customized onajaxpageload() function when an ajax page is fetched/ loaded
}

ddajaxtabs.getCookie=function(Name){
var re=new RegExp(Name+"=[^;]+", "i"); //construct RE to search for target name/value pair
if (document.cookie.match(re)) //if cookie found
return document.cookie.match(re)[0].split("=")[1] //return its value
return ""
}

ddajaxtabs.setCookie=function(name, value){
document.cookie = name+"="+value+";path=/" //cookie value is domain wide (path=/)
}

ddajaxtabs.prototype={

expandit:function(tabid_or_position){ //PUBLIC function to select a tab either by its ID or position(int) within its peers
this.cancelautorun() //stop auto cycling of tabs (if running)
var tabref=""
try{
if (typeof tabid_or_position=="string" && document.getElementById(tabid_or_position).getAttribute("rel")) //if specified tab contains "rel" attr
tabref=document.getElementById(tabid_or_position)
else if (parseInt(tabid_or_position)!=NaN && this.tabs[tabid_or_position].getAttribute("rel")) //if specified tab contains "rel" attr
tabref=this.tabs[tabid_or_position]
}
catch(err){alert("Invalid Tab ID or position entered!")}
if (tabref!="") //if a valid tab is found based on function parameter
this.expandtab(tabref) //expand this tab
},

cycleit:function(dir, autorun){ //PUBLIC function to move foward or backwards through each hot tab (tabinstance.cycleit('foward/back') )
if (dir=="next"){
var currentTabIndex=(this.currentTabIndex<this.hottabspositions.length-1)? this.currentTabIndex+1 : 0
}
else if (dir=="prev"){
var currentTabIndex=(this.currentTabIndex>0)? this.currentTabIndex-1 : this.hottabspositions.length-1
}
if (typeof autorun=="undefined") //if cycleit() is being called by user, versus autorun() function
this.cancelautorun() //stop auto cycling of tabs (if running)
this.expandtab(this.tabs[this.hottabspositions[currentTabIndex]])
},

setpersist:function(bool){ //PUBLIC function to toggle persistence feature
this.enabletabpersistence=bool
},

loadajaxpage:function(pageurl){ //PUBLIC function to fetch a page via Ajax and display it within the Tab Content instance's container
ddajaxtabs.connect(pageurl, this)
},

loadiframepage:function(pageurl){ //PUBLIC function to fetch a page and load it into the IFRAME of the Tab Content instance's container
this.iframedisplay(pageurl, this.contentdivid)
},

setselectedClassTarget:function(objstr){ //PUBLIC function to set which target element to assign "selected" CSS class ("linkparent" or "link")
this.selectedClassTarget=objstr || "link"
},

getselectedClassTarget:function(tabref){ //Returns target element to assign "selected" CSS class to
return (this.selectedClassTarget==("linkparent".toLowerCase()))? tabref.parentNode : tabref
},

urlparamselect:function(tabinterfaceid){
var result=window.location.search.match(new RegExp(tabinterfaceid+"=(\\d+)", "i")) //check for "?tabinterfaceid=2" in URL
return (result==null)? null : parseInt(RegExp.$1) //returns null or index, where index (int) is the selected tab's index
},

onajaxpageload:function(pageurl){ //PUBLIC Event handler that can invoke custom code whenever an Ajax page has been fetched and displayed
//do nothing by default
},

expandtab:function(tabref){
var relattrvalue=tabref.getAttribute("rel")
//Get "rev" attr as a string of IDs in the format ",john,george,trey,etc," to easy searching through
var associatedrevids=(tabref.getAttribute("rev"))? ","+tabref.getAttribute("rev").replace(/\s+/, "")+"," : ""
if (relattrvalue=="#default")
document.getElementById(this.contentdivid).innerHTML=this.defaultHTML
else if (relattrvalue=="#iframe")
this.iframedisplay(tabref.getAttribute("href"), this.contentdivid)
else
ddajaxtabs.connect(tabref.getAttribute("href"), this)
this.expandrevcontent(associatedrevids)
for (var i=0; i<this.tabs.length; i++){ //Loop through all tabs, and assign only the selected tab the CSS class "selected"
this.getselectedClassTarget(this.tabs[i]).className=(this.tabs[i].getAttribute("href")==tabref.getAttribute("href"))? "selected" : ""
}
if (this.enabletabpersistence) //if persistence enabled, save selected tab position(int) relative to its peers
ddajaxtabs.setCookie(this.tabinterfaceid, tabref.tabposition)
this.setcurrenttabindex(tabref.tabposition) //remember position of selected tab within hottabspositions[] array
},

iframedisplay:function(pageurl, contentdivid){
if (typeof window.frames["_ddajaxtabsiframe-"+contentdivid]!="undefined"){
try{delete window.frames["_ddajaxtabsiframe-"+contentdivid]} //delete iframe within Tab content container if it exists (due to bug in Firefox)
catch(err){}
}
document.getElementById(contentdivid).innerHTML=this.defaultIframe
window.frames["_ddajaxtabsiframe-"+contentdivid].location.replace(pageurl) //load desired page into iframe
},

expandrevcontent:function(associatedrevids){
var allrevids=this.revcontentids
for (var i=0; i<allrevids.length; i++){ //Loop through rev attributes for all tabs in this tab interface
//if any values stored within associatedrevids matches one within allrevids, expand that DIV, otherwise, contract it
document.getElementById(allrevids[i]).style.display=(associatedrevids.indexOf(","+allrevids[i]+",")!=-1)? "block" : "none"
}
},

setcurrenttabindex:function(tabposition){ //store current position of tab (within hottabspositions[] array)
for (var i=0; i<this.hottabspositions.length; i++){
if (tabposition==this.hottabspositions[i]){
this.currentTabIndex=i
break
}
}
},

autorun:function(){ //function to auto cycle through and select tabs based on a set interval
this.cycleit('next', true)
},

cancelautorun:function(){
if (typeof this.autoruntimer!="undefined")
clearInterval(this.autoruntimer)
},

init:function(automodeperiod){
var persistedtab=ddajaxtabs.getCookie(this.tabinterfaceid) //get position of persisted tab (applicable if persistence is enabled)
var selectedtab=-1 //Currently selected tab index (-1 meaning none)
var selectedtabfromurl=this.urlparamselect(this.tabinterfaceid) //returns null or index from: tabcontent.htm?tabinterfaceid=index
this.automodeperiod=automodeperiod || 0
this.defaultHTML=document.getElementById(this.contentdivid).innerHTML
for (var i=0; i<this.tabs.length; i++){
this.tabs[i].tabposition=i //remember position of tab relative to its peers
if (this.tabs[i].getAttribute("rel")){
var tabinstance=this
this.hottabspositions[this.hottabspositions.length]=i //store position of "hot" tab ("rel" attr defined) relative to its peers
this.tabs[i].onclick=function(){
tabinstance.expandtab(this)
tabinstance.cancelautorun() //stop auto cycling of tabs (if running)
return false
}
if (this.tabs[i].getAttribute("rev")){ //if "rev" attr defined, store each value within "rev" as an array element
this.revcontentids=this.revcontentids.concat(this.tabs[i].getAttribute("rev").split(/\s*,\s*/))
}
if (selectedtabfromurl==i || this.enabletabpersistence && selectedtab==-1 && parseInt(persistedtab)==i || !this.enabletabpersistence && selectedtab==-1 && this.getselectedClassTarget(this.tabs[i]).className=="selected"){
selectedtab=i //Selected tab index, if found
}
}
} //END for loop
if (selectedtab!=-1) //if a valid default selected tab index is found
this.expandtab(this.tabs[selectedtab]) //expand selected tab (either from URL parameter, persistent feature, or class)
else //if no valid default selected index found
this.expandtab(this.tabs[this.hottabspositions[0]]) //Just select first tab that contains a "rel" attr
if (parseInt(this.automodeperiod)>500 && this.hottabspositions.length>1){
this.autoruntimer=setInterval(function(){tabinstance.autorun()}, this.automodeperiod)
}
} //END int() function

} //END Prototype assignment

Będąc w katalogu głównym tworzymy katalog ajaxtabs, do którego wędrują stworzone przez nas pliki. Trzeba tam również wrzucić obrazki, które możesz pobrać stąd. Przejdźmy teraz do części najważniejszej czyli implementacji na naszej stronie www. Na początku edytujemy plik, w którym mają wyświetlać się zakładki. Szukamy tam znacznika </head> i przed nim wklejamy ten kod:

1
2
<link rel="stylesheet" type="text/css" href="ajaxtabs/ajaxtabs.css" />
<script type="text/javascript" src="ajaxtabs/ajaxtabs.js"></script>

Następnie w miejscu, w którym mają się wyświetlać zakładki z tekstem wklejamy następujący kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<ul id="countrytabs" class="shadetabs">
<li><a href="#" rel="#default" class="selected">zakładka 1</a></li>
<li><a href="zakladka2.htm" rel="countrycontainer">zakładka 2</a></li>
<li><a href="zakladka3.htm" rel="countrycontainer">zakładka 3</a></li>
<li><a href="zakladka4.htm" rel="#iframe">zakładka 4</a></li>
<li><a href="http://www.tobiasz.org">adres URL</a></li>
</ul>

<div id="countrydivcontainer" style="border:1px solid gray; width:450px; margin-bottom: 1em; padding: 10px">
<p>Tutaj znajduje się treść zakładki 1, która ustawiona jest jako domyślana.</p>
</div>

<script type="text/javascript">

var countries=new ddajaxtabs("countrytabs", "countrydivcontainer")
countries.setpersist(true)
countries.setselectedClassTarget("link") //"link" or "linkparent"
countries.init()

</script>

W katalogu, w którym znajduje się modyfikowany plik muszą być także utworzone pliki zakladka2.htm, zakladak3.htm i zakladka4.htm. W plikach tych wklejamy tylko tekst, który ma się wyświetlać w po wybraniu danej zakładki.

Problem z polskimi znakami. Niestety używając polskich znaków w plikach z zawartością poszczególnych zakładek polskie znaki nie są wyświetlane poprawnie. Jednakże problem ten można łatwo ominąć używając funkcji iconv(). Poniżej przykładowe użycie funkcji w jednej z zakładek:

1
echo"<td style=\"width:115px;\"><b><center>".iconv("ISO-8859-2", "UTF-8","Miejscowo¶ć")."</center></b></td>";

Po zastosowaniu tej funkcji przy przeładowywaniu zakładek wszystkie znaki powinny być wyświetlane w sposób prawidłowy.

W taki oto sposób na naszej stronie możemy wyświetlać dynamiczne zakładki wraz z generowaną przez nie odpowiednią treścią.

Jeden komentarz

  1. Lukasz napisał(a):

    A jak przerobić to żeby zamiast zewnętrznych stron \zakładka2.htm\ wrzucało DIVy?

Odpowiedz

 

Arkadiusz Tobiasz student Akademii Ekonomicznej im. Karola Adamieckiego w Katowicach na specjalnościach informatyka ekonomiczna oraz rachunkowość. Więcej...

jQuery Validation i funkcja remote

Jakiś czas temu zwrócił się do mnie użytkownik z problemem. Chodzi o to, że korzysta on z pluginu walidacji jQuery, […]

Zend Framework: integracja z Uploadify

W tym wpisie postaram się przedstawić Wam w jaki sposób zintegrować skrypt Uploadify z Zend Frameworkiem. Dzięki temu będziemy mogli […]

Javascript: Czasowe wyświetlanie reklamy

Czasami chcemy, aby na pewnym elemencie naszej strony wyświetlała się reklama przez jakiś czas, a następnie zniknęła. W tym wpisie […]

Linux: backup wszystkich baz danych MySQL

Swego czasu pisałem o tym jak z poziomu konsoli można szybko i przyjemnie zrobić backup bazy MySQL. Wszystko jest ładnie […]