3
\$\begingroup\$

I have an HTML menu with sub-menus and also sub-sub-menus. This code verifies each menu's trigger. If clicked, sub-menu opens. Sub-menus also have triggers that open sub-sub-menus and so on. A working example can be found here. This menu is a modified version of the menu found here, made by Mary Lou. I want this to be my mobile menu but I don't want to manually enter all the cases.

My code is very lengthy and repetitive. How can I simplify it?

( function( window ) {
    'use strict';

    // http://stackoverflow.com/a/11381730/989439
    function mobilecheck() {
        var check = false;
        (function(a){if(/(android|ipad|playbook|silk|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
        return check;
    }

    function gnMenu( el, options ) {
        this.el = el;
        this._init();
    }

    function gnSubMenu( el, options ) {
        this.el = el;
        this._init();
    }

    function gnSubMenu2( el, options ) {
        this.el = el;
        this._init();
    }

    gnMenu.prototype = {
        _init : function() {
            this.trigger = this.el.querySelector( '.gn-icon-menu' );
            this.subtrigger = this.el.querySelector('.submenu' );
            this.subtrigger2 = this.el.querySelector('.submenu2' );
            this.menu = this.el.querySelector( '.menu-wrapper' );
            this.submenu = this.el.querySelector( '.sub-menu-wrapper1' );
            this.submenu2 = this.el.querySelector( '.sub-menu-wrapper2' );
            this.isMenuOpen = false;
            this.isSubMenuOpen = false;
            this.isSubMenu2Open = false;
            this.eventtype = mobilecheck() ? 'touchstart' : 'click';
            this._initEvents();
            var self = this;
            this.bodyClickFn = function() {
                self._closeMenu();
                self._closeSubMenu();
                self._closeSubMenu2();
                this.removeEventListener( self.eventtype, self.bodyClickFn );
            };
        },
        _initEvents : function() {
            var self = this;
            if( !mobilecheck() ) {
                this.menu.addEventListener( 'mouseover', function(ev) {
                    self._openMenu();
                    document.addEventListener( self.eventtype, self.bodyClickFn );
                } );
                this.submenu.addEventListener( 'mouseover', function(ev) {
                    self._openSubMenu();
                    document.addEventListener( self.eventtype, self.bodyClickFn );
                } );
                this.submenu2.addEventListener( 'mouseover', function(ev) {
                    self._openSubMenu2();
                    document.addEventListener( self.eventtype, self.bodyClickFn );
                } );
            }
            this.trigger.addEventListener( this.eventtype, function( ev ) {
                ev.stopPropagation();
                ev.preventDefault();
                if( self.isMenuOpen ) {
                    self._closeMenu();
                    document.removeEventListener( self.eventtype, self.bodyClickFn );
                }
                else {
                    self._openMenu();
                    document.addEventListener( self.eventtype, self.bodyClickFn );
                }
            } );
            this.subtrigger.addEventListener( this.eventtype, function( ev ) {
                ev.stopPropagation();
                ev.preventDefault();
                if( self.isSubMenuOpen ) {
                    self._closeSubMenu();
                    document.removeEventListener( self.eventtype, self.bodyClickFn );
                }
                else {
                    self._openSubMenu();
                    document.addEventListener( self.eventtype, self.bodyClickFn );
                }
            } );
            this.subtrigger2.addEventListener( this.eventtype, function( ev ) {
                ev.stopPropagation();
                ev.preventDefault();
                if( self.isSubMenu2Open ) {
                    self._closeSubMenu2();
                    document.removeEventListener( self.eventtype, self.bodyClickFn );
                }
                else {
                    self._openSubMenu2();
                    document.addEventListener( self.eventtype, self.bodyClickFn );
                }
            } );
            this.menu.addEventListener( this.eventtype, function(ev) { ev.stopPropagation(); } );
            this.submenu.addEventListener( this.eventtype, function(ev) { ev.stopPropagation(); } );
            this.submenu2.addEventListener( this.eventtype, function(ev) { ev.stopPropagation(); } );
        },
        _openMenu : function() {
            if( this.isMenuOpen )
                return;
            classie.add( this.trigger, 'gn-selected' );
            this.isMenuOpen = true;
            classie.add( this.menu, 'gn-open-all' );
        },
        _closeMenu : function() {
            if( !this.isMenuOpen )
                return;
            classie.remove( this.trigger, 'gn-selected' );
            this.isMenuOpen = false;
            classie.remove( this.menu, 'gn-open-all' );
        },
        _openSubMenu : function() {
            if( this.isSubMenuOpen )
                return;
            classie.add( this.subtrigger, 'gn-selected' );
            this.isSubMenuOpen = true;
            classie.add( this.submenu, 'gn-open-all2' );
        },
        _closeSubMenu : function() {
            if( !this.isSubMenuOpen )
                return;
            classie.remove( this.subtrigger, 'gn-selected' );
            this.isSubMenuOpen = false;
            classie.remove( this.submenu, 'gn-open-all2' );
        },
        _openSubMenu2 : function() {
            if( this.isSubMenu2Open )
                return;
            classie.add( this.subtrigger2, 'gn-selected' );
            this.isSubMenu2Open = true;
            classie.add( this.submenu2, 'gn-open-all3' );
        },
        _closeSubMenu2 : function() {
            if( !this.isSubMenu2Open )
                return;
            classie.remove( this.subtrigger2, 'gn-selected' );
            this.isSubMenu2Open = false;
            classie.remove( this.submenu2, 'gn-open-all3' );
        }
    };
    // Add to global namespace
    window.gnMenu = gnMenu;
} )( window );
\$\endgroup\$
  • 2
    \$\begingroup\$ Well, you could start with proper intendation! After that, factor out common stuff. Functions are not special, so you could do var gnSubMenu = gnMenu, gnSubMenu2 = gnMenu; instead of defining three identical functions. Use loops for repetitive code (e.g. over the addEventListener). After that you can start with actual refactoring (e.g. choosing better names). \$\endgroup\$ – amon Aug 10 '13 at 7:47
  • 1
    \$\begingroup\$ @amon y u answer in comments? \$\endgroup\$ – Vogel612 Aug 26 '14 at 13:21
2
\$\begingroup\$

One thing that I saw right away, while I was editing your question to make it more noticeable, is that you have 3 functions that are the same, they just have different names.

function gnMenu( el, options ) {
    this.el = el;
    this._init();
}

function gnSubMenu( el, options ) {
    this.el = el;
    this._init();
}

function gnSubMenu2( el, options ) {
    this.el = el;
    this._init();
}

You don't need to do this. It looks like they all take in the same parameters and do the same thing, so just use one function for the 3 of them.

function menu( el, options ) {
    this.el = el;
    this._init();
}

One step to being DRYer already (DRY = Don't Repeat Yourself)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.