/*! bigSlide - v0.11.0 - 2016-04-13 * http://ascott1.github.io/bigSlide.js/ * Copyright (c) 2016 Adam D. Scott; Licensed MIT */ (function (factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); } else if (typeof exports === 'object') { // Node/CommonJS module.exports = factory(require('jquery')); } else { // Browser globals factory(jQuery); } }(function($) { 'use strict'; // where inlineCSS is the string value of an element's style attribute // and toRemove is a string of space-separated CSS properties, // _cleanInlineCSS removes the CSS declaration for each property in toRemove from inlineCSS // and returns the resulting string function _cleanInlineCSS(inlineCSS, toRemove){ var inlineCSSArray = inlineCSS.split(';'); var toRemoveArray = toRemove.split(' '); var cleaned = ''; var keep; for (var i = 0, j = inlineCSSArray.length; i < j; i++) { keep = true; for (var a = 0, b = toRemoveArray.length; a < b; a++) { if (inlineCSSArray[i] === '' || inlineCSSArray[i].indexOf(toRemoveArray[a]) !== -1) { keep = false; } } if(keep) {cleaned += inlineCSSArray[i] + '; ';} } return cleaned; } $.fn.bigSlide = function(options) { // store the menuLink in a way that is globally accessible var menuLink = this; // plugin settings var settings = $.extend({ 'menu': ('#menu'), 'push': ('.push'), 'shrink': ('.shrink'), 'hiddenThin': ('.hiddenThin'), 'side': 'left', 'menuWidth': '250px', 'semiOpenMenuWidth': '4em', 'speed': '500', 'state': 'closed', 'activeBtn': 'active', 'easyClose': true, 'saveState': false, 'semiOpenStatus': false, 'semiOpenScreenWidth': 480, 'beforeOpen': function () {}, 'afterOpen': function() {}, 'beforeClose': function() {}, 'afterClose': function() {} }, options); // CSS properties set by bigSlide.js on all implicated DOM elements var baseCSSDictionary = 'transition -o-transition -ms-transition -moz-transitions webkit-transition ' + settings.side; var model = { //CSS properties set by bigSlide.js on this.$menu menuCSSDictionary: baseCSSDictionary + ' position top bottom height width', //CSS properties set by bigSlide.js on this.$push pushCSSDictionary: baseCSSDictionary, // store the menu's state in the model 'state': settings.state }; // talk back and forth between the view and state var controller = { init: function(){ view.init(); }, // remove bigSlide behavior from the menu _destroy: function(){ view._destroy(); delete menuLink.bigSlideAPI; // return a reference to the DOM selection bigSlide.js was called on // so that the destroy method is chainable return menuLink; }, // update the menu's state changeState: function(){ if (model.state === 'closed') { model.state = 'open' } else { model.state = 'closed' } }, // set the menu's state setState: function(state){ model.state = state; }, // check the menu's state getState: function(){ return model.state; } }; // the view contains all of the visual interactions var view = { init: function(){ // cache DOM values this.$menu = $(settings.menu); this.$push = $(settings.push); this.$shrink = $(settings.shrink); this.$hiddenThin = $(settings.hiddenThin); this.width = settings.menuWidth; this.semiOpenMenuWidth = settings.semiOpenMenuWidth; // CSS for how the menu will be positioned off screen var positionOffScreen = { 'position': 'fixed', 'top': '0', 'bottom': '0', 'height': '100%' }; // css for the sliding animation var animateSlide = { '-webkit-transition': settings.side + ' ' + settings.speed + 'ms ease', '-moz-transition': settings.side + ' ' + settings.speed + 'ms ease', '-ms-transition': settings.side + ' ' + settings.speed + 'ms ease', '-o-transition': settings.side + ' ' + settings.speed + 'ms ease', 'transition': settings.side + ' ' + settings.speed + 'ms ease' }; // css for the shrink animation var animateShrink = { '-webkit-transition': 'all ' + settings.speed + 'ms ease', '-moz-transition': 'all ' + settings.speed + 'ms ease', '-ms-transition': 'all ' + settings.speed + 'ms ease', '-o-transition': 'all ' + settings.speed + 'ms ease', 'transition': 'all ' + settings.speed + 'ms ease' }; // we want to add the css sliding animation when the page is loaded (on the first menu link click) var animationApplied = false; // manually add the settings values positionOffScreen[settings.side] = '-' + settings.menuWidth; positionOffScreen.width = settings.menuWidth; // get the initial state based on the last saved state or on the state option var initialState = 'closed'; if (settings.saveState) { initialState = localStorage.getItem('bigSlide-savedState'); if (!initialState) initialState = settings.state; } else { initialState = settings.state; } // set the initial state on the controller controller.setState(initialState); // add the css values to position things offscreen or inscreen depending on the initial state value this.$menu.css(positionOffScreen); var initialScreenWidth = $(window).width(); if (initialState === 'closed') { if (settings.semiOpenStatus && initialScreenWidth > settings.semiOpenScreenWidth) { this.$hiddenThin.hide(); this.$menu.css(settings.side, '0'); this.$menu.css('width', this.semiOpenMenuWidth); this.$push.css(settings.side, this.semiOpenMenuWidth); this.$shrink.css({ 'width': 'calc(100% - ' + this.semiOpenMenuWidth + ')' }); this.$menu.addClass('semiOpen'); } else { this.$push.css(settings.side, '0'); } } else if (initialState === 'open') { this.$menu.css(settings.side, '0'); this.$push.css(settings.side, this.width); this.$shrink.css({ 'width': 'calc(100% - ' + this.width + ')' }); menuLink.addClass(settings.activeBtn); } var that = this; // register a click listener for desktop & touchstart for mobile menuLink.on('click.bigSlide touchstart.bigSlide', function(e) { // add the animation css if not present if (!animationApplied) { that.$menu.css(animateSlide); that.$push.css(animateSlide); that.$shrink.css(animateShrink); animationApplied = true; } e.preventDefault(); if (controller.getState() === 'open') { view.toggleClose(); } else { view.toggleOpen(); } }); // register a window resize listener for tracking the semi open status states // This could be more efficently or even there are people that could consider it unnecessary. We can think about it if (settings.semiOpenStatus) { $(window).resize(function() { var screenWidth = $(window).width(); if (screenWidth > settings.semiOpenScreenWidth) { if (controller.getState() === 'closed') { that.$hiddenThin.hide(); that.$menu.css({ width: that.semiOpenMenuWidth}); that.$menu.css(settings.side, '0'); that.$push.css(settings.side, that.semiOpenMenuWidth); that.$shrink.css({ 'width': 'calc(100% - ' + that.semiOpenMenuWidth + ')' }); that.$menu.addClass('semiOpen'); } } else { that.$menu.removeClass('semiOpen'); if (controller.getState() === 'closed') { that.$menu.css(settings.side, '-' + that.width).css({width: that.width}); that.$push.css(settings.side, '0'); that.$shrink.css('width', '100%'); that.$hiddenThin.show(); } } }); } // this makes my eyes bleed, but adding it back in as it's a highly requested feature if (settings.easyClose) { $(document).on('click.bigSlide', function(e) { if (!$(e.target).parents().andSelf().is(menuLink) && !$(e.target).closest(settings.menu).length && controller.getState() === 'open') { view.toggleClose(); } }); } }, _destroy: function(){ //remove inline styles generated by bigSlide.js while preserving any other inline styles this.$menu.each(function(){ var $this = $(this); $this.attr( 'style', _cleanInlineCSS($this.attr('style'), model.menuCSSDictionary).trim() ); }); this.$push.each(function(){ var $this = $(this); $this.attr( 'style', _cleanInlineCSS($this.attr('style'), model.pushCSSDictionary).trim() ); }); this.$shrink.each(function(){ var $this = $(this); $this.attr( 'style', _cleanInlineCSS($this.attr('style'), model.pushCSSDictionary).trim() ); }); //remove active class and unbind bigSlide event handlers menuLink .removeClass(settings.activeBtn) .off('click.bigSlide touchstart.bigSlide'); //release DOM references to avoid memory leaks this.$menu = null; this.$push = null; this.$shrink = null; //remove the local storage state localStorage.removeItem('bigSlide-savedState'); }, // toggle the menu open toggleOpen: function() { settings.beforeOpen(); controller.changeState(); view.applyOpenStyles(); menuLink.addClass(settings.activeBtn); settings.afterOpen(); // save the state if (settings.saveState) { localStorage.setItem('bigSlide-savedState', 'open'); } }, // toggle the menu closed toggleClose: function() { settings.beforeClose(); controller.changeState(); view.applyClosedStyles(); menuLink.removeClass(settings.activeBtn); settings.afterClose(); // save the state if (settings.saveState) { localStorage.setItem('bigSlide-savedState', 'closed'); } }, applyOpenStyles: function() { var screenWidth = $(window).width(); if (settings.semiOpenStatus && screenWidth > settings.semiOpenScreenWidth) { this.$hiddenThin.show(); this.$menu.animate({ width: this.width}, {duration: Math.abs(settings.speed - 100), easing: 'linear'}); this.$push.css(settings.side, this.width); this.$shrink.css({ 'width': 'calc(100% - ' + this.width + ')' }); this.$menu.removeClass('semiOpen'); } else { this.$menu.css(settings.side, '0'); this.$push.css(settings.side, this.width); this.$shrink.css({ 'width': 'calc(100% - ' + this.width + ')' }); } }, applyClosedStyles: function() { var screenWidth = $(window).width(); if (settings.semiOpenStatus && screenWidth > settings.semiOpenScreenWidth) { this.$hiddenThin.hide(); this.$menu.animate({ width: this.semiOpenMenuWidth}, {duration: Math.abs(settings.speed - 100), easing: 'linear'}); this.$push.css(settings.side, this.semiOpenMenuWidth); this.$shrink.css({ 'width': 'calc(100% - ' + this.semiOpenMenuWidth + ')' }); this.$menu.addClass('semiOpen'); } else { this.$menu.css(settings.side, '-' + this.width); this.$push.css(settings.side, '0'); this.$shrink.css('width', '100%'); } } } controller.init(); this.bigSlideAPI = { settings: settings, model: model, controller: controller, view: view, destroy: controller._destroy }; return this; }; }));