1/** 2 * automatically structure content with a tab panel 3 * searches for .vxJS_tabThis elements 4 * .section elements within .vxJS_tabThis form the views 5 * the first h2 element of a .section is used as tab label 6 * tabs are labeled with text content of their respective h2 elements 7 * 8 * pushState() is used when config.setHash is set and browser support suffices 9 * 10 * @version 0.6.0 2017-07-05 11 * @author Gregor Kofler 12 * 13 * @param [{Object} HTMLElement]: 14 * root elemement searched for tabs, defaults to document 15 * @param [{Object} config]: 16 * tabWrap: {String | Array} nodeName(s) of extra container element(s) for each tab 17 * shortenLabelsTo: {Number} shorten labels to the given number of chars or preceeding word boundary 18 * setHash: {Boolean} when true, tab ids will modify URI hash 19 * spacersTop: {Object} additional DOM element(s) inserted before the ul element 20 * spacersBottom: {Object} additional DOM element(s) inserted after the ul element 21 * 22 * served events: "beforeTabClick", "afterTabClick" 23 */ 24 25/*jslint browser: true, plusplus: true, vars: true, white: true */ 26 27vxJS.widget.simpleTabs = (function() { 28 29 "use strict"; 30 31 var conf = {}, tabBars = [], 32 supportsHistory = vxJS.hasHostMethods(window.history, ["pushState", "replaceState"]); 33 34 /** 35 * Represents a single tab. 36 * @constructor 37 * @param {TabBar} tabBar - the bar, the tab belongs to 38 * @param {string} label - label on tab 39 * @param {HTMLElement} page - the "content" of a tab page 40 * @param {string} [id] - id of tab 41 */ 42 var Tab = function(tabBar, label, page, id) { 43 this.tabBar = tabBar; 44 this.label = label; 45 this.page = page; 46 this.id = id; 47 this.render(); 48 }; 49 50 Tab.prototype = { 51 52 /** 53 * create element of a single tab element, populates Tab.element 54 * @returns {Tab} 55 */ 56 render: function() { 57 58 var li, a, l, 59 w = conf.tabWrap, 60 label = conf.shortenLabelsTo ? this.label.shortenToLen(conf.shortenLabelsTo) : this.label, 61 title = ""; 62 63 if(label.length !== this.label.length) { 64 label += "..."; 65 title = this.label; 66 } 67 68 a = this.id ? "a".setProp("href", conf.setHash ? "#" + this.id : "").create(label) : document.createTextNode(label); 69 70 li = "li".setProp("title", title).create(); 71 72 if(!w) { 73 li.appendChild(a); 74 } 75 else if(typeof w === "string") { 76 li.appendChild(w.create(a)); 77 } 78 else { 79 l = w.length; 80 while(l--) { 81 a = w[l].create(a); 82 } 83 li.appendChild(a); 84 } 85 86 this.element = li; 87 return this; 88 }, 89 90 /** 91 * focus A element in tab, when present; does not show/hide pages 92 * @returns {Tab} 93 */ 94 focus: function() { 95 var a = this.element.getElementsByTagName("a"); 96 if(a && a[0]) { 97 a[0].focus(); 98 } 99 return this; 100 }, 101 102 /** 103 * show page of tab, does not hide previously shown page 104 * @returns {Tab} 105 */ 106 show: function() { 107 this.visibilty = true; 108 vxJS.dom.addClassName(this.element, "shown"); 109 this.page.style.display = ""; 110 return this; 111 }, 112 113 /** 114 * hide page of tab 115 * @returns {Tab} 116 */ 117 hide: function() { 118 this.visibilty = false; 119 vxJS.dom.removeClassName(this.element, "shown"); 120 this.page.style.display = "none"; 121 return this; 122 }, 123 124 /** 125 * enable tab 126 * @returns {Tab} 127 */ 128 enable: function() { 129 this.inactive = false; 130 vxJS.dom.removeClassName(this.element, "disabled"); 131 return this; 132 }, 133 134 /** 135 * disable tab 136 * @returns {Tab} 137 */ 138 disable: function() { 139 this.inactive = true; 140 vxJS.dom.addClassName(this.element, "disabled"); 141 return this; 142 } 143 144 }; 145 146 /** 147 * represents a tab bar containing tabs 148 * @constructor 149 * @param {HTMLElement} container - element holding the tab bar 150 */ 151 var TabBar = function(container) { 152 this.tabs = []; 153 this.container = container; 154 }; 155 156 TabBar.prototype = { 157 158 enable: function() { 159 this.inactive = false; 160 vxJS.dom.removeClassName(this.element, "disabled"); 161 return this; 162 }, 163 164 disable: function() { 165 this.inactive = true; 166 vxJS.dom.addClassName(this.element, "disabled"); 167 return this; 168 }, 169 170 getTabByElement: function(elem) { 171 var l = this.tabs.length; 172 while(l--) { 173 if(this.tabs[l].element === elem) { 174 return this.tabs[l]; 175 } 176 } 177 }, 178 179 getTabByNdx: function(ndx) { 180 return this.tabs[ndx]; 181 }, 182 183 getTabById: function(id) { 184 var l = this.tabs.length; 185 while(l--) { 186 if(this.tabs[l].id === id) { 187 return this.tabs[l]; 188 } 189 } 190 }, 191 192 getTabNdx: function(tab) { 193 var l = this.tabs.length; 194 while(l--) { 195 if(this.tabs[l] === tab) { 196 return l; 197 } 198 } 199 }, 200 201 gotoTab: function(tab, pushState) { 202 var l = this.last; 203 204 if(!tab || this.tabs.indexOf(tab) === -1 || l === tab) { 205 return; 206 } 207 208 l.hide(); 209 tab.show(); 210 211 this.last = tab; 212 213 if(pushState) { 214 if(tab.id && conf.setHash) { 215 if(supportsHistory) { 216 window.history.pushState(tab.id, "", "#" + tab.id); 217 } 218 else { 219 window.location = "#" + tab.id; 220 } 221 } 222 } 223 224 return this; 225 }, 226 227 gotoPrevTab: function(pushState) { 228 return this.gotoTab(this.last.prevTab, pushState); 229 }, 230 231 gotoNextTab: function(pushState) { 232 return this.gotoTab(this.last.nextTab, pushState); 233 }, 234 235 focus: function(tab, pushState) { 236 this.gotoTab(tab, pushState); 237 tab.focus(); 238 return this; 239 }, 240 241 /** 242 * create and insert a new tab 243 * 244 * page: HTMLElement 245 * label: String 246 * id: String (optional) 247 * 248 * @param {Object} tabData - { page: {HTMLElement}, label: {string}, [id : {string}] } 249 * @param {Tab} [insertBefore] - tab before which new tab will be inserted; when omitted, tab will be appended 250 * @returns {TabBar} 251 */ 252 insertTab: function(tabData, insertBefore) { 253 254 var tab = new Tab(this, tabData.label, tabData.page, tabData.id), prevTab; 255 256 tab.hide(); 257 258 if(!insertBefore) { 259 prevTab = this.tabs[this.tabs.length - 1]; 260 prevTab.nextTab = tab; 261 tab.nextTab = null; 262 tab.prevTab = prevTab; 263 this.tabs.push(tab); 264 this.container.insertBefore(tabData.page, prevTab.page.nextSibling); 265 this.ulElement.insertBefore(tab.element, prevTab.element.nextSibling); 266 } 267 else { 268 prevTab = insertBefore.prevTab; 269 if(prevTab) { 270 prevTab.nextTab = tab; 271 } 272 insertBefore.prevTab = tab; 273 tab.prevTab = prevTab; 274 tab.nextTab = insertBefore; 275 this.tabs.splice(this.getTabNdx(insertBefore), 0, tab); 276 this.container.insertBefore(tabData.page, insertBefore.page); 277 this.ulElement.insertBefore(tab.element, insertBefore.element); 278 } 279 280 return this; 281 }, 282 283 /** 284 * remove a tab 285 * 286 * @param {Tab} 287 * @returns {TabBar} 288 */ 289 removeTab: function(tab) { 290 var ndx = this.getTabNdx(tab); 291 292 if(tab.prevTab) { 293 tab.prevTab.nextTab = tab.nextTab; 294 } 295 if(tab.nextTab) { 296 tab.nextTab.prevTab = tab.prevTab; 297 } 298 299 this.container.removeChild(tab.page); 300 this.ulElement.removeChild(tab.element); 301 this.tabs.splice(ndx, 1); 302 303 return this; 304 } 305 }; 306 307 if(supportsHistory) { 308 vxJS.event.addListener(window, "popstate", function(e) { 309 var l = tabBars.length, found; 310 311 if(e.state) { 312 while(l--) { 313 if(found = tabBars[l].getTabById(e.state)) { 314 break; 315 } 316 } 317 } 318 if(found) { 319 tabBars[l].gotoTab(found, false); 320 } 321 }); 322 } 323 324 return function(root, config) { 325 var tabbed = vxJS.dom.getElementsByClassName("vxJS_tabThis", root), tbCount = tabbed.length, hash = window.location.hash.slice(1) || ""; 326 327 conf = config || {}; 328 329 var clickListener = function(e, bar) { 330 var tab = bar.getTabByElement((!this.nodeName || this.nodeName.toUpperCase() !== "LI") ? vxJS.dom.getParentElement(this, "li") : this); 331 332 if(!tab) { 333 return; 334 } 335 if(!bar.inactive && !tab.inactive) { 336 vxJS.event.serve(bar, "beforeTabClick"); 337 bar.gotoTab(tab, true); 338 vxJS.event.serve(bar, "afterTabClick"); 339 } 340 341 vxJS.event.preventDefault(e); 342 }; 343 344 while(tbCount--) { 345 (function(container) { 346 var secs = vxJS.dom.getElementsByClassName("section", container), 347 ctrl = new TabBar(container), h, ul, init, i, l, id, t; 348
349 for(i = 0, l = secs.length; i < l; ++i) { 350 h = secs[i].getElementsByTagName("h2"); 351 352 if(h[0]) { 353 id = h[0].id; 354 t = new Tab(ctrl, vxJS.dom.concatText(h[0]), secs[i], id); 355 t.hide(); 356 h[0].parentNode.removeChild(h[0]); 357 ctrl.tabs.push(t); 358 359 if((id && id === hash) || (!init && vxJS.dom.hasClassName(secs[i], "default"))) { 360 init = t; 361 } 362 } 363 } 364 365 if(!(l = ctrl.tabs.length)) { 366 return; 367 } 368 if(!init) { 369 init = ctrl.tabs[0]; 370 } 371 372 ul = "ul".create(); 373 374 for(i = 0; i < l; ++i) { 375 t = ctrl.tabs; 376 377 t[i].nextTab = t[i + 1] || null; 378 t[i].prevTab = i - 1 >= 0 ? t[i - 1] : null; 379 380 ul.appendChild(t[i].element); 381 382 } 383 384 init.show(); 385 386 ctrl.last = init; 387 ctrl.ulElement = ul; 388 ctrl.element = "div".setProp("class", "vxJS_tabBar").create([conf.spacersTop, ul, conf.spacersBottom]); 389 390 container.insertBefore(ctrl.element, container.firstChild); 391 392 vxJS.event.addListener(ctrl, "click", clickListener); 393 tabBars.push(ctrl); 394 395 if(supportsHistory && init.id && conf.setHash) { 396 window.history.replaceState(init.id, "", "#" + init.id); 397 } 398 399 }(tabbed[tbCount])); 400 } 401 402 return tabBars; 403 }; 404}());
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.