laipower/wp-content/plugins/gp-premium/menu-plus/functions/js/offside.js

637 lines
24 KiB
JavaScript

/* offside-js 1.3.1 22-05-2016
* Minimal JavaScript kit without library dependencies to push things off-canvas using just class manipulation
* https://github.com/toomuchdesign/offside.git
*
* by Andrea Carraro
* Available under the MIT license
*/
;(function ( window, document, undefined ) {
/*
* The first time Offside is called, it creates a singleton-factory object
* and place it into "window.offside.factory".
*
* Offside factory serves the following purposes:
* - DOM initialization
* - Centralized Offside instances management and initialization
*/
'use strict';
// Self-invoking function returning the object which contains
// the "getInstance" method used for initializing
// the Offside sigleton factory
var offside = (function () {
// Global Offside singleton-factory constructor
function initOffsideFactory( options ) {
// Utility functions
// Shared among factory and Offside instances, too
// Close all open Offsides.
// If an Offside instance id is provided, it just closes the matching instance instead
var closeAll = function( offsideId ) {
// Look for an open Offside id
if ( openOffsidesId.length > 0 ) {
// Close matching Offside instance if an ID is provided
if ( !isNaN( offsideId ) ) {
instantiatedOffsides[ offsideId ].close();
} else {
// Close all Offside instances
openOffsidesId.forEach( function( offsideId ) {
instantiatedOffsides[ offsideId ].close();
});
}
}
},
// Append a class to body in order to turn on elements CSS 3D transitions
// only when happens the first interation with an Offside instance.
// Otherwise we would see Offside instances being smoothly pushed
// out of the screen during DOM initialization.
turnOnCssTransitions = function() {
addClass( body, transitionsClass );
},
addClass = function( el, c ) {
if ( el.classList ) {
el.classList.add(c);
} else {
el.className = ( el.className + ' ' + c ).trim();
}
},
removeClass = function( el, c ) {
if ( el.classList ) {
el.classList.remove(c);
} else {
el.className = el.className.replace( new RegExp( '(^|\\b)' + c.split(' ').join('|') + '(\\b|$)', 'gi' ), ' ' );
}
},
addEvent = function( el, eventName, eventHandler ) {
el.addEventListener( eventName, eventHandler );
},
removeEvent = function( el, eventName, eventHandler ) {
el.removeEventListener( eventName, eventHandler );
},
// Return a collection (array) of DOM elements from:
// - A DOM element
// - An array of DOM elements
// - A string selector
getDomElements = function( els ) {
// "els" is a DOM element
// http://stackoverflow.com/a/120275/2902821
if( els instanceof HTMLElement ){
return [ els ];
}
// "els" is an array
else if( Array.isArray( els ) ) {
return els;
}
// "els" is a string
else if( typeof els === 'string' ) {
// Convert Nodelist into an array
// http://www.jstips.co/en/converting-a-node-list-to-an-array/
return Array.apply( null, document.querySelectorAll( els ) );
}
return false;
},
// Check if a value exists in an array. Returns:
// - array index if value exists
// - "false" if value is not found
// See: http://stackoverflow.com/a/5767357
isInArray = function( arr, value ) {
var index = arr.indexOf( value );
return index > -1 ? index : false;
};
// Offside.js factory initialization
var i,
factorySettings; // Offside factory private settings
// Default factory settings
factorySettings = {
slidingElementsSelector: '.offside-sliding-element', // String: Default sliding elements selectors ('#foo, #bar')
disableCss3dTransforms: false, // Disable CSS 3d Transforms support (for testing purposes)
debug: false, // Boolean: If true, print errors in console
};
// User defined factory settings
for ( i in options ) {
if ( factorySettings.hasOwnProperty( i ) ) {
factorySettings[i] = options[i];
}
}
// Private factory properties
var globalClass = 'offside-js', // Global Offside classes namespace
initClass = globalClass + '--init', // Class appended to body when Offside is intialized
slidingElementsClass = 'offside-sliding-element', // Class appended to sliding elements
transitionsClass = globalClass + '--interact', // Class appended to body when ready to turn on Offside CSS transitions (Added when first menu interaction happens)
instantiatedOffsides = [], // Array containing all instantiated offside elements
firstInteraction = true, // Keep track of first Offside interaction
has3d = factorySettings.disableCss3dTransforms ? false : _has3d(), // Browser supports CSS 3d Transforms
openOffsidesId = [], // Tracks opened Offside instances id's
body = document.body,
slidingElements = getDomElements( factorySettings.slidingElementsSelector ), // Sliding elements
debug = factorySettings.debug;
// Offside singleton-factory Dom initialization
// It's called just once on Offside singleton-factory init.
function _factoryDomInit() {
// Add class to sliding elements
slidingElements.forEach( function( item ) {
addClass( item, slidingElementsClass );
});
// DOM Fallbacks when CSS transform 3d not available
if ( !has3d ) {
// No CSS 3d Transform fallback
addClass( document.documentElement, 'no-csstransforms3d' ); //Adds Modernizr-like class to HTML element when CSS 3D Transforms not available
}
// Add init class to body
addClass( body, initClass );
}
// Private Offside factory methods
// Testing for CSS 3D Transform Support
// https://gist.github.com/lorenzopolidori/3794226
function _has3d() {
if ( !window.getComputedStyle ) {
return false;
}
var el = document.createElement('p'),
has3d,
transforms = {
'webkitTransform':'-webkit-transform',
'OTransform':'-o-transform',
'msTransform':'-ms-transform',
'MozTransform':'-moz-transform',
'transform':'transform'
};
// Add it to the body to get the computed style
document.body.insertBefore(el, null);
for( var t in transforms ){
if( el.style[t] !== undefined ){
el.style[t] = 'translate3d(1px,1px,1px)';
has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]);
}
}
document.body.removeChild(el);
return ( has3d !== undefined && has3d.length > 0 && has3d !== 'none' );
}
// Offside constructor wrapper
// It supplies a wrapper around OffsideInstance constructor
// to prevent instance initialization when casted on a non-existing DOM element
// See: http://stackoverflow.com/a/8618792
function createOffsideInstance( el, options, offsideId ) {
// Check if provided element exists before using it to instantiate an Offside instance
var domEl = el !== undefined ?
getDomElements( el ) :
getDomElements( '.offside' );
// If provided el exists initialize an Offside instance, else return null
return domEl !== false ?
new OffsideInstance( domEl[0], options, offsideId ) :
null;
}
// Offside constructor
// Set up and initialize a new Offside instance
// Called by Offside factory "getOffsideInstance()" method
function OffsideInstance( domEl, options, offsideId ) {
var i,
offsideSettings;
// Default Offside instance settings
offsideSettings = {
buttonsSelector: '', // String: Offside toggle buttons selectors ('#foo, #bar')
slidingSide: 'left', // String: Offside element pushed on left or right
init: function(){}, // Function: After init callback
beforeOpen: function(){}, // Function: Before open callback
afterOpen: function(){}, // Function: After open callback
beforeClose: function(){}, // Function: Before close callback
afterClose: function(){}, // Function: After close callback
beforeDestroy: function(){}, // Function: After destroy callback
afterDestroy: function(){}, // Function: After destroy callback
};
// User defined Offside instance settings
for ( i in options ) {
if ( offsideSettings.hasOwnProperty( i ) ) {
offsideSettings[i] = options[i];
}
}
// Offside instance private properties
var offside = domEl, // Hello, I'm the Offside instance
offsideButtons = getDomElements( offsideSettings.buttonsSelector ), // Offside toggle buttons
slidingSide = offsideSettings.slidingSide,
offsideClass = 'offside', // Class added to Offside instance when initialized
offsideSideClass = offsideClass + '--' + slidingSide, // Class added to Offside instance when initialized (eg. offside offside--left)
offsideOpenClass = 'is-open', // Class appended to Offside instance when open
offsideBodyOpenClass = globalClass + '--' + 'is-open', // Class appended to body when an Offside instance is open (offside-js--is-open)
offsideBodyOpenSideClass = globalClass + '--is-' + slidingSide, // Class appended to body when Offside instance is open (eg. offside-js--is-left / offside-js--is-open)
id = offsideId || 0; // Offside instance id
// Offside instance private methods
var _toggleOffside = function() {
// Premise: Just 1 Offside instance at time can be open.
// Check currently toggling Offside status
isInArray( openOffsidesId, id ) === false ? _openOffside() : _closeOffside();
},
_openOffside = function() {
// beforeOpen callback
offsideSettings.beforeOpen();
// Turn on CSS transitions on first interaction with an Offside instance
if ( firstInteraction ) {
firstInteraction = false;
turnOnCssTransitions();
}
// If another Offside instance is already open,
// close it before going on
closeAll();
// Set global body active class for current Offside instance
addClass( body, offsideBodyOpenClass );
addClass( body, offsideBodyOpenSideClass );
// Add Offside instance open class
addClass( offside, offsideOpenClass );
// Update open Offside instances tracker
openOffsidesId.push( id );
// afterOpen callback
offsideSettings.afterOpen();
},
_closeOffside = function() {
// Proceed with closing stuff only if
// current Offside instance is listed among openOffsidesId array
var index = isInArray( openOffsidesId, id );
if ( index !== false ) {
// beforeClose callback
offsideSettings.beforeClose();
// Remove global body active class for current Offside instance
removeClass( body, offsideBodyOpenClass );
removeClass( body, offsideBodyOpenSideClass );
// Remove Offside instance open class
removeClass( offside, offsideOpenClass );
// Update open Offside instances tracker
openOffsidesId.splice( index, 1 );
// afterClose callback
offsideSettings.afterClose();
}
},
_closeAll = function() {
closeAll();
},
// Offside buttons click handler
_onButtonClick = function( e ) {
e.preventDefault();
_toggleOffside();
},
/*
// Get Offside instance unique ID
_getId = function() {
return id;
}
*/
// Set up and initialize a new Offside instance
_initOffside = function() {
if ( debug ) {
_checkElements();
}
// Append classes to Offside instance (.offside and .offside{slidingSide})
addClass( offside, offsideClass );
addClass( offside, offsideSideClass );
// Toggle Offside on click event
offsideButtons.forEach( function( item ) {
addEvent( item, 'click', _onButtonClick );
});
// Init callback
offsideSettings.init();
},
_destroyOffside = function() {
// beforeDestroy callback
offsideSettings.beforeDestroy();
// Close Offside intance before destroy
_closeOffside();
// Remove click event from Offside buttons
offsideButtons.forEach( function( item ) {
removeEvent( item, 'click', _onButtonClick );
});
// Remove classes appended on init phase
removeClass( offside, offsideClass );
removeClass( offside, offsideSideClass );
// Destroy Offside instance
delete instantiatedOffsides[id];
// afterDestroy callback
offsideSettings.afterDestroy();
},
// Fire console errors if DOM elements are missing
_checkElements = function() {
if ( !offside ) {
console.error( 'Offside alert: "offside" selector could not match any element' );
}
if ( !offsideButtons.length ) {
console.error( 'Offside alert: "buttonsSelector" selector could not match any element' );
}
};
// Offside instances public methods
this.toggle = function() {
_toggleOffside();
};
this.open = function() {
_openOffside();
};
this.close = function() {
_closeOffside();
};
this.closeAll = function() {
_closeAll();
};
this.destroy = function() {
_destroyOffside();
};
// Ok, init Offside instance
_initOffside();
} // OffsideInstance constructor end
// DOM initialization
_factoryDomInit();
// This is the actual returned Offside factory
return {
//Offside factory public methods
closeOpenOffside: function() {
closeAll();
},
// This is the method responsible for creating a new Offside instance
// and register it into "instantiatedOffsides" array
getOffsideInstance: function( el, options ) {
// Get length of instantiated Offsides array
var offsideId = instantiatedOffsides.length || 0,
// Instantiate new Offside instance
offsideInstance = createOffsideInstance( el, options, offsideId );
// If Offside instance is sccessfully created
if ( offsideInstance !== null ) {
// Push new instance into "instantiatedOffsides" array and return it
/*jshint -W093 */
return instantiatedOffsides[ offsideId ] = offsideInstance;
/*jshint +W093 */
}
}
};
} // initOffsideFactory() end
return {
// Get the Singleton instance if one exists
// or create one if it doesn't
getInstance: function ( el, options ) {
if ( !window.offside.factory ) {
window.offside.factory = initOffsideFactory( options );
}
return window.offside.factory.getOffsideInstance( el, options );
}
};
})();
// Store in window a reference to the Offside singleton factory
if ( typeof module !== 'undefined' && module.exports ) {
module.exports = offside.getInstance;
} else {
window.offside = offside.getInstance;
}
})( window, document );
/**
* Start GP.
*/
document.addEventListener( 'DOMContentLoaded', function() {
document.querySelector( '.slideout-navigation' ).style.display = '';
} );
var generateOffside = offside( '.slideout-navigation', {
slidingElementsSelector:'#slideout-container',
buttonsSelector: '.slideout-mobile .main-navigation .menu-toggle, .slideout-both .main-navigation .menu-toggle, .slideout-both .slideout-toggle, .slideout-desktop .slideout-toggle',
slidingSide: offSide.side,
beforeOpen: function() {
// Turn on visibility so we can transition nicely.
document.querySelector( '.slideout-navigation' ).style.visibility = 'visible';
},
afterOpen: function() {
document.documentElement.classList.add( 'slide-opened' );
document.body.classList.add( 'slide-opened' );
if ( document.body.classList.contains( 'dropdown-hover' ) ) {
var dropdownItems = document.querySelector( '.slideout-navigation' ).querySelectorAll( 'li.menu-item-has-children' );
for ( var i = 0; i < dropdownItems.length; i++ ) {
var spanToggle = dropdownItems[i].querySelector( 'span.dropdown-menu-toggle' );
if ( spanToggle ) {
spanToggle.setAttribute( 'tabindex', 0 );
spanToggle.setAttribute( 'role', 'button' );
spanToggle.setAttribute( 'aria-expanded', true );
}
}
}
document.querySelector( '.slideout-navigation' ).removeAttribute( 'aria-hidden' );
// Focus the first focusable element.
var focusable = document.querySelector( '.slideout-navigation' ).querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' );
if ( focusable ) {
setTimeout( function() {
focusable[0].focus();
}, 200 );
}
},
afterClose: function() {
var body = document.body,
nav = document.querySelectorAll( '.main-navigation' );
for ( var i = 0; i < nav.length; i++ ) {
if ( nav[i].classList.contains( 'toggled' ) ) {
nav[i].classList.remove( 'toggled' );
}
};
document.documentElement.classList.remove( 'slide-opened' );
body.classList.remove( 'slide-opened' );
if ( 'true' === document.querySelector( '.main-navigation .menu-toggle' ).getAttribute( 'aria-expanded' ) ) {
document.querySelector( '.main-navigation .menu-toggle' ).setAttribute( 'aria-expanded', false );
}
if ( body.classList.contains( 'dropdown-hover' ) ) {
var dropdownItems = document.querySelector( '.main-navigation:not(.slideout-navigation):not(.mobile-menu-control-wrapper)' ).querySelectorAll( 'li.menu-item-has-children' );
for ( var i = 0; i < dropdownItems.length; i++ ) {
var spanToggle = dropdownItems[i].querySelector( 'span.dropdown-menu-toggle' );
if ( spanToggle ) {
spanToggle.removeAttribute( 'tabindex' );
spanToggle.setAttribute( 'role', 'presentation' );
spanToggle.removeAttribute( 'aria-expanded' );
}
}
}
// Turn off visibility.
setTimeout( function() {
document.querySelector( '.slideout-navigation:not(.is-open)' ).style.visibility = '';
document.querySelector( '.slideout-navigation:not(.is-open)' ).setAttribute( 'aria-hidden', 'true' );
}, 500 );
// Focus our slideout toggle.
if ( window.document.documentElement.clientWidth <= 768 ) {
if ( body.classList.contains( 'slideout-mobile' ) || body.classList.contains( 'slideout-both' ) ) {
document.querySelectorAll( '.main-navigation:not(.slideout-navigation)' ).forEach( function( navigation ) {
if ( navigation && navigation.style.display !== 'none' ) {
navigation.querySelector( '.menu-toggle' ).focus();
}
} );
}
} else {
if ( body.classList.contains( 'slideout-desktop' ) || body.classList.contains( 'slideout-both' ) ) {
document.querySelectorAll( '.main-navigation:not(.slideout-navigation)' ).forEach( function( navigation ) {
if ( navigation && navigation.style.display !== 'none' ) {
navigation.querySelector( '.slideout-toggle a' ).focus();
}
} );
}
}
}
} );
var closeElements = document.querySelectorAll( '.slideout-overlay, .slideout-exit, .slider-exit a' );
for ( var i = 0; i < closeElements.length; i++ ) {
closeElements[i].addEventListener( 'click', function( e ) {
e.preventDefault();
generateOffside.close();
} );
};
/**
* Closes the slideout navigation if the link does something.
* eg. Goes to an anchor link
*
* @since 1.7
*/
var slideoutLinks = document.querySelectorAll( '.slideout-navigation ul a' );
var closeOffsideOnAction = function() {
var url = this.getAttribute( 'href' );
if ( '#' !== url && '' !== url && ! navigator.userAgent.match( /iemobile/i ) ) {
setTimeout( function() {
generateOffside.close();
}, 200 );
}
};
for ( var i = 0; i < slideoutLinks.length; i++ ) {
slideoutLinks[i].addEventListener( 'click', closeOffsideOnAction, false );
};
document.addEventListener( 'keyup', function( e ) {
if ( document.body.classList.contains( 'slide-opened' ) ) {
e = e || window.event;
if ( e.keyCode == 27 ) {
generateOffside.close();
}
}
} );