PageSourceSearch

http://hairsalon.com.tw/new/scripts/tab/jquery.responsiveTabs.js

js hairsalon.com.tw collected 2026-10-06 06:23:59 UTC 20,784 bytes, 555 lines download raw bytes

1/*
2 *  Project: jquery.responsiveTabs.js
3 *  Description: A plugin that creates responsive tabs, optimized for all devices
4 *  Author: Jelle Kralt ([email protected])
5 *  Version: 1.4.0
6 *  License: MIT
7 */
8
9;(function ( $, window, undefined ) {
10
11    /** Default settings */
12    var defaults = {
13        active: null,
14        disabled: [],
15        collapsible: 'accordion',
16        startCollapsed: false,
17        rotate: false,
18        setHash: false,
19        animation: 'default',
20        duration: 500,
21        activate: function(){},
22        deactivate: function(){},
23        load: function(){},
24        activateState: function(){},
25        classes: {
26            stateDefault: 'r-tabs-state-default',
27            stateActive: 'r-tabs-state-active',
28            stateDisabled: 'r-tabs-state-disabled',
29            stateExcluded: 'r-tabs-state-excluded',
30            tab: 'r-tabs-tab',
31            anchor: 'r-tabs-anchor',
32            panel: 'r-tabs-panel',
33            accordionTitle: 'r-tabs-accordion-title'
34        }
35    };
36
37    /**
38     * Responsive Tabs
39     * @constructor
40     * @param {object} element - The HTML element the validator should be bound to
41     * @param {object} options - An option map
42     */
43    function ResponsiveTabs(element, options) {
44        this.element = element; // Selected DOM element
45        this.$element = $(element); // Selected jQuery element
46
47        this.tabs = []; // Create tabs array
48        this.state = ''; // Define the plugin state (tabs/accordion)
49        this.rotateInterval = 0; // Define rotate interval
50        this.$queue = $({});
51
52        // Extend the defaults with the passed options
53        this.options = $.extend( {}, defaults, options);
54
55        this.init();
56    }
57
58
59    /**
60     * This function initializes the tab plugin
61     */
62    ResponsiveTabs.prototype.init = function () {
63        var _this = this;
64
65        // Load all the elements
66        this.tabs = this._loadElements();
67        this._loadClasses();
68        this._loadEvents();
69
70        // Window resize bind to check state
71        $(window).on('resize', function(e) {
72            _this._setState(e);
73        });
74
75        // Hashchange event
76        $(window).on('hashchange', function(e) {
77            var tabRef = _this._getTabRefBySelector(window.location.hash);
78            var oTab = _this._getTab(tabRef);
79
80            // Check if a tab is found that matches the hash
81            if(tabRef >= 0 && !oTab._ignoreHashChange && !oTab.disabled) {
82                // If so, open the tab and auto close the current one
83                _this._openTab(e, _this._getTab(tabRef), true);
84            }
85        });
86
87        // Start rotate event if rotate option is defined
88        if(this.options.rotate !== false) {
89            this.startRotation();
90        }
91
92        // --------------------
93        // Define plugin events
94        //
95
96        // Activate: this event is called when a tab is selected
97        this.$element.bind('tabs-activate', function(e, oTab) {
98            _this.options.activate.call(this, e, oTab);
99        });
100        // Deactivate: this event is called when a tab is closed
101        this.$element.bind('tabs-deactivate', function(e, oTab) {
102            _this.options.deactivate.call(this, e, oTab);
103        });
104        // Activate State: this event is called when the plugin switches states
105        this.$element.bind('tabs-activate-state', function(e, state) {
106            _this.options.activateState.call(this, e, state);
107        });
108
109        // Load: this event is called when the plugin has been loaded
110        this.$element.bind('tabs-load', function(e) {
111            var tabRef = _this._getTabRefBySelector(window.location.hash);
112            var firstTab;
113
114            _this._setState(e); // Set state
115
116            // Check if the panel should be collaped on load
117            if(_this.options.startCollapsed !== true && !(_this.options.startCollapsed === 'accordion' && _this.state === 'accordion')) {
118
119                // Check if the page has a hash set that is linked to a tab
120                if(tabRef >= 0 && !_this._getTab(tabRef).disabled) {
121                    // If so, set the current tab to the linked tab
122                    firstTab = _this._getTab(tabRef);
123                } else if(_this.options.active > 0 && !_this._getTab(_this.options.active).disabled) {
124                    firstTab = _this._getTab(_this.options.active);
125                } else {
126                    // If not, just get the first one
127                    firstTab = _this._getTab(0);
128                }
129
130                // Open the initial tab
131                _this._openTab(e, firstTab); // Open first tab
132
133                // Call the callback function
134                _this.options.load.call(this, e, firstTab); // Call the load callback
135            }
136        });
137        // Trigger loaded event
138        this.$element.trigger('tabs-load');
139    };
140    
141    //
142    // PRIVATE FUNCTIONS
143    //
144
145    /**
146     * This function loads the tab elements and stores them in an array
147     * @returns {Array} Array of tab elements
148     */
149    ResponsiveTabs.prototype._loadElements = function() {
150        var _this = this;
151        var $ul = this.$element.children('ul');
152        var tabs = [];
153        var id = 0;
154
155        // Add the classes to the basic html elements
156        this.$element.addClass('r-tabs'); // Tab container
157        $ul.addClass('r-tabs-nav'); // List container
158
159        // Get tab buttons and store their data in an array
160        $('li', $ul).each(function() {
161            var $tab = $(this);
162            var isExcluded = $tab.hasClass(_this.options.classes.stateExcluded);
163            var $anchor, $panel, $accordionTab, $accordionAnchor, panelSelector;
164
165            // Check if the tab should be excluded
166            if(!isExcluded) {
167
168                $anchor = $('a', $tab);
169                panelSelector = $anchor.attr('href');
170                $panel = $(panelSelector);
171                $accordionTab = $('<div></div>').insertBefore($panel);
172                $accordionAnchor = $('<a></a>').attr('href', panelSelector).html($anchor.html()).appendTo($accordionTab);
173
174                var oTab = {
175                    _ignoreHashChange: false,
176                    id: id,
177                    disabled: ($.inArray(id, _this.options.disabled) !== -1),
178                    tab: $(this),
179                    anchor: $('a', $tab),
180                    panel: $panel,
181                    selector: panelSelector,
182                    accordionTab: $accordionTab,
183                    accordionAnchor: $accordionAnchor,
184                    active: false
185                };
186
187                // 1up the ID
188                id++;
189                // Add to tab array
190                tabs.push(oTab);
191            }
192        });
193        return tabs;
194    };
195
196
197    /**
198     * This function adds classes to the tab elements based on the options
199     */
200    ResponsiveTabs.prototype._loadClasses = function() {
201        for (var i=0; i<this.tabs.length; i++) {
202            this.tabs[i].tab.addClass(this.options.classes.stateDefault).addClass(this.options.classes.tab);
203            this.tabs[i].anchor.addClass(this.options.classes.anchor);
204            this.tabs[i].panel.addClass(this.options.classes.stateDefault).addClass(this.options.classes.panel);
205            this.tabs[i].accordionTab.addClass(this.options.classes.accordionTitle);
206            this.tabs[i].accordionAnchor.addClass(this.options.classes.anchor);
207            if(this.tabs[i].disabled) {
208                this.tabs[i].tab.removeClass(this.options.classes.stateDefault).addClass(this.options.classes.stateDisabled);
209                this.tabs[i].accordionTab.removeClass(this.options.classes.stateDefault).addClass(this.options.classes.stateDisabled);
210           }
211        }
212    };
213
214    /**
215     * This function adds events to the tab elements
216     */
217    ResponsiveTabs.prototype._loadEvents = function() {
218        var _this = this;
219        // Define click event on a tab element
220        var fClick = function(e) {
221            var current = _this._getCurrentTab(); // Fetch current tab
222            var clickedTab = e.data.tab;
223
224            e.preventDefault();
225
226            // Make sure this tab isn't disabled
227            if(!clickedTab.disabled) {
228
229                // Check if hash has to be set in the URL location
230                if(_this.options.setHash) {
231                    window.location.hash = clickedTab.selector;
232                }
233
234                e.data.tab._ignoreHashChange = true;
235
236                // Check if the clicked tab isnt the current one or if its collapsible. If not, do nothing
237                if(current !== clickedTab || _this._isCollapisble()) {
238                    // The clicked tab is either another tab of the current one. If it's the current tab it is collapsible
239                    // Either way, the current tab can be closed
240                    _this._closeTab(e, current);
241
242                    // Check if the clicked tab isnt the current one or if it isnt collapsible
243                    if(current !== clickedTab || !_this._isCollapisble()) {
244                        _this._openTab(e, clickedTab, false, true);
245                    }
246                }
247            }
248        };
249
250        // Loop tabs
251        for (var i=0; i<this.tabs.length; i++) {
252            // Add click function to the tab and accordion selection element
253            this.tabs[i].anchor.on('click', {tab: _this.tabs[i]}, fClick);
254            this.tabs[i].accordionAnchor.on('click', {tab: _this.tabs[i]}, fClick);
255        }
256    };
257
258    /**
259     * This function sets the current state of the plugin
260     * @param {Event} e - The event that triggers the state change
261     */
262    ResponsiveTabs.prototype._setState = function() {
263        var $ul = $('ul', this.$element);
264        var oldState = this.state;
265
266        // The state is based on the visibility of the tabs list
267        if($ul.is(':visible')){
268            // Tab list is visible, so the state is 'tabs'
269            this.state = 'tabs';
270        } else {
271            // Tab list is invisible, so the state is 'accordion'
272            this.state = 'accordion';
273        }
274
275        // If the new state is different from the old state, the state activate trigger must be called
276        if(this.state !== oldState) {
277            this.$element.trigger('tabs-activate-state', {oldState: oldState, newState: this.state});
278        }
279    };
280
281    /**
282     * This function opens a tab
283     * @param {Event} e - The event that triggers the tab opening
284     * @param {Object} oTab - The tab object that should be opened
285     * @param {Boolean} closeCurrent - Defines if the current tab should be closed
286     * @param {Boolean} stopRotation - Defines if the tab rotation loop should be stopped
287     */
288    ResponsiveTabs.prototype._openTab = function(e, oTab, closeCurrent, stopRotation) {
289        var _this = this;
290
291        // Check if the current tab has to be closed
292        if(closeCurrent) {
293            this._closeTab(e, this._getCurrentTab());
294        }
295
296        // Check if the rotation has to be stopped when activated
297        if(stopRotation && this.rotateInterval > 0) {
298            this.stopRotation();
299        }
300
301        // Set this tab to active
302        oTab.active = true;
303        // Set active classes to the tab button and accordion tab button
304        oTab.tab.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive);
305        oTab.accordionTab.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive);
306
307        // Run panel transiton
308        _this._doTransition(oTab.panel, _this.options.animation, 'open', function() {
309            // When finished, set active class to the panel
310            oTab.panel.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive);
311        });
312
313        this.$element.trigger('tabs-activate', oTab);
314    };
315
316    /**
317     * This function closes a tab
318     * @param {Event} e - The event that is triggered when a tab is closed
319     * @param {Object} oTab - The tab object that should be closed
320     */
321    ResponsiveTabs.prototype._closeTab = function(e, oTab) {
322        var _this = this;
323
324        if(oTab !== undefined) {
325
326            // Deactivate tab
327            oTab.active = false;
328            // Set default class to the tab button
329            oTab.tab.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault);
330
331            // Run panel transition
332            _this._doTransition(oTab.panel, _this.options.animation, 'close', function() {
333                // Set default class to the accordion tab button and tab panel
334                oTab.accordionTab.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault);
335                oTab.panel.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault);
336            }, true);
337
338            this.$element.trigger('tabs-deactivate', oTab);
339        }
340    };
341
342    /**
343     * This function runs an effect on a panel
344     * @param {Element} panel - The HTML element of the tab panel
345     * @param {String} method - The transition method reference
346     * @param {String} state - The state (open/closed) that the panel should transition to
347     * @param {Function} callback - The callback function that is called after the transition
348     * @param {Boolean} dequeue - Defines if the event queue should be dequeued after the transition
349     */
350    ResponsiveTabs.prototype._doTransition = function(panel, method, state, callback, dequeue) {
351        var effect;
352        var _this = this;
353
354        // Get effect based on method
355        switch(method) {
356            case 'slide':
357                effect = (state === 'open') ? 'slideDown' : 'slideUp';
358                break;
359            case 'fade':
360                effect = (state === 'open') ? 'fadeIn' : 'fadeOut';
361                break;
362            default:
363                effect = (state === 'open') ? 'show' : 'hide';
364                // When default is used, set the duration to 0
365                _this.options.duration = 0;
366                break;
367        }
368
369        // Add the transition to a custom queue
370        this.$queue.queue('responsive-tabs',function(next){
371            // Run the transition on the panel
372            panel[effect]({
373                duration: _this.options.duration,
374                complete: function() {
375                    // Call the callback function
376                    callback.call(panel, method, state);
377                    // Run the next function in the queue
378                    next();
379                }
380            });
381        });
382
383        // When the panel is openend, dequeue everything so the animation starts
384        if(state === 'open' || dequeue) {
385            this.$queue.dequeue('responsive-tabs');
386        }
387
388    };
389
390    /**
391     * This function returns the collapsibility of the tab in this state
392     * @returns {Boolean} The collapsibility of the tab
393     */
394    ResponsiveTabs.prototype._isCollapisble = function() {
395        return (typeof this.options.collapsible === 'boolean' && this.options.collapsible) || (typeof this.options.collapsible === 'string' && this.options.collapsible === this.getState());
396    };
397
398    /**
399     * This function returns a tab by numeric reference
400     * @param {Integer} numRef - Numeric tab reference
401     * @returns {Object} Tab object
402     */
403    ResponsiveTabs.prototype._getTab = function(numRef) {
404        return this.tabs[numRef];
405    };
406
407    /**
408     * This function returns the numeric tab reference based on a hash selector
409     * @param {String} selector - Hash selector
410     * @returns {Integer} Numeric tab reference
411     */
412    ResponsiveTabs.prototype._getTabRefBySelector = function(selector) {
413        // Loop all tabs
414        for (var i=0; i<this.tabs.length; i++) {
415            // Check if the hash selector is equal to the tab selector
416            if(this.tabs[i].selector === selector) {
417                return i;
418            }
419        }
420        // If none is found return a negative index
421        return -1;
422    };
423
424    /**
425     * This function returns the current tab element
426     * @returns {Object} Current tab element
427     */
428    ResponsiveTabs.prototype._getCurrentTab = function() {
429        return this._getTab(this._getCurrentTabRef());
430    };
431
432    /**
433     * This function returns the next tab's numeric reference
434     * @param {Integer} currentTabRef - Current numeric tab reference
435     * @returns {Integer} Numeric tab reference
436     */
437    ResponsiveTabs.prototype._getNextTabRef = function(currentTabRef) {
438        var tabRef = (currentTabRef || this._getCurrentTabRef());
439        var nextTabRef = (tabRef === this.tabs.length - 1) ? 0 : tabRef + 1;
440        return (this._getTab(nextTabRef).disabled) ? this._getNextTabRef(nextTabRef) : nextTabRef;
441    };
442
443    /**
444     * This function returns the previous tab's numeric reference
445     * @returns {Integer} Numeric tab reference
446     */
447    ResponsiveTabs.prototype._getPreviousTabRef = function() {
448        return (this._getCurrentTabRef() === 0) ? this.tabs.length - 1 : this._getCurrentTabRef() - 1;
449    };
450
451    /**
452     * This function returns the current tab's numeric reference
453     * @returns {Integer} Numeric tab reference
454     */
455    ResponsiveTabs.prototype._getCurrentTabRef = function() {
456        // Loop all tabs
457        for (var i=0; i<this.tabs.length; i++) {
458            // If this tab is active, return it
459            if(this.tabs[i].active) {
460                return i;
461            }
462        }
463        // No tabs have been found, return negative index
464        return -1;
465    };
466
467    //
468    // PUBLIC FUNCTIONS
469    //
470
471    /**
472     * This function activates a tab
473     * @param {Integer} tabRef - Numeric tab reference
474     * @param {Boolean} stopRotation - Defines if the tab rotation should stop after activation
475     */
476    ResponsiveTabs.prototype.activate = function(tabRef, stopRotation) {
477        var e = jQuery.Event('tabs-activate');
478        var oTab = this._getTab(tabRef);
479        if(!oTab.disabled) {
480            this._openTab(e, oTab, true, stopRotation || true);
481        }
482    };
483
484    /**
485     * This function deactivates a tab
486     * @param {Integer} tabRef - Numeric tab reference
487     */
488    ResponsiveTabs.prototype.deactivate = function(tabRef) {
489        var e = jQuery.Event('tabs-dectivate');
490        var oTab = this._getTab(tabRef);
491        if(!oTab.disabled) {
492            this._closeTab(e, oTab);
493        }
494    };
495
496    /**
497     * This function gets the current state of the plugin
498     * @returns {String} State of the plugin
499     */
500    ResponsiveTabs.prototype.getState = function() {
501        return this.state;
502    };
503
504    /**
505     * This function starts the rotation of the tabs
506     * @param {Integer} speed - The speed of the rotation
507     */
508    ResponsiveTabs.prototype.startRotation = function(speed) {
509        var _this = this;
510        // Make sure not all tabs are disabled
511        if(this.tabs.length > this.options.disabled.length) {
512            this.rotateInterval = setInterval(function(){
513                var e = jQuery.Event('rotate');
514                _this._openTab(e, _this._getTab(_this._getNextTabRef()), true);
515            }, speed || (($.isNumeric(_this.options.rotate)) ? _this.options.rotate : 4000) );
516        } else {
517            throw new Error("Rotation is not possible if all tabs are disabled");
518        }
519    };
520
521    /**
522     * This function stops the rotation of the tabs
523     */
524    ResponsiveTabs.prototype.stopRotation = function() {
525        window.clearInterval(this.rotateInterval);
526        this.rotateInterval = 0;
527    };
528
529    /** jQuery wrapper */
530    $.fn.responsiveTabs = function ( options ) {
531        var args = arguments;
532        if (options === undefined || typeof options === 'object') {
533            return this.each(function () {
534                if (!$.data(this, 'responsivetabs')) {
535                    $.data(this, 'responsivetabs', new ResponsiveTabs( this, options ));
536                }
537            });
538        } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
539            return this.each(function () {
540                var instance = $.data(this, 'responsivetabs');
541
542                if (instance instanceof ResponsiveTabs && typeof instance[options] === 'function') {
543                    instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
544                }
545
546                // Allow instances to be destroyed via the 'destroy' method
547                if (options === 'destroy') {
548                    // TODO: destroy instance classes, etc
549                    $.data(this, 'responsivetabs', null);
550                }
551            });
552        }
553    };
554
555}(jQuery, window));

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.