2020-04-07 13:03:04 +00:00
/ * o f f s i d e - j s 1 . 3 . 1 2 2 - 0 5 - 2 0 1 6
* 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 .
* /
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 ,
2022-10-27 11:14:35 +00:00
beforeOpen : function ( ) {
2023-03-29 18:20:22 +00:00
document . querySelector ( '.slideout-navigation' ) . classList . add ( 'slideout-transition' ) ;
2022-10-27 11:14:35 +00:00
} ,
2020-04-07 13:03:04 +00:00
afterOpen : function ( ) {
2023-03-29 18:20:22 +00:00
setTimeout ( function ( ) {
document . querySelector ( '.slideout-navigation' ) . classList . remove ( 'slideout-transition' ) ;
} , 10 ) ;
2020-04-07 13:03:04 +00:00
document . documentElement . classList . add ( 'slide-opened' ) ;
document . body . classList . add ( 'slide-opened' ) ;
2022-10-27 11:14:35 +00:00
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' ) ;
2020-04-07 13:03:04 +00:00
2022-10-27 11:14:35 +00:00
if ( spanToggle ) {
spanToggle . setAttribute ( 'tabindex' , 0 ) ;
spanToggle . setAttribute ( 'role' , 'button' ) ;
spanToggle . setAttribute ( 'aria-expanded' , true ) ;
}
2020-04-07 13:03:04 +00:00
}
}
2022-10-27 11:14:35 +00:00
// 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 ) ;
}
2020-04-07 13:03:04 +00:00
} ,
2023-03-29 18:20:22 +00:00
beforeClose : function ( ) {
document . querySelector ( '.slideout-navigation' ) . classList . add ( 'slideout-transition' ) ;
} ,
2020-04-07 13:03:04 +00:00
afterClose : function ( ) {
2023-03-29 18:20:22 +00:00
setTimeout ( function ( ) {
document . querySelector ( '.slideout-navigation' ) . classList . remove ( 'slideout-transition' ) ;
} , 500 ) ;
2020-04-07 13:03:04 +00:00
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' ) ) {
2022-10-27 11:14:35 +00:00
var dropdownItems = document . querySelector ( '.main-navigation:not(.slideout-navigation):not(.mobile-menu-control-wrapper)' ) . querySelectorAll ( 'li.menu-item-has-children' ) ;
2020-04-07 13:03:04 +00:00
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' ) ;
}
}
}
}
} ) ;
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 ;
2022-10-27 11:14:35 +00:00
2020-04-07 13:03:04 +00:00
if ( e . keyCode == 27 ) {
generateOffside . close ( ) ;
2022-12-19 23:08:15 +00:00
var body = document . body ;
// 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 ( ) ;
}
} ) ;
}
}
2020-04-07 13:03:04 +00:00
}
}
} ) ;
2023-03-29 18:20:22 +00:00
var toggles = document . querySelectorAll ( '.slideout-toggle a' ) ;
for ( var i = 0 ; i < toggles . length ; i ++ ) {
toggles [ i ] . addEventListener ( 'keypress' , function ( e ) {
if ( ' ' === e . key ) {
e . preventDefault ( ) ;
generateOffside . open ( ) ;
}
} ) ;
} ;