4

I am trying to use 2 jQuery navigation scripts on one page (Superfish for desktops and FlexNav for mobile). I am currently using matchMedia along with the polyfill by Paul Irish to respond to CSS3 media query state changes within JavaScript.

The current code is only accomplishing 50% of the overall goal. If you access the web page initially with a window size equal to or greater than 999px wide then you get Superfish and if you initially access the web page with a window size less than 999px then you get FlexNav. The problem occurs when you resize the window above or below 999px as both scripts become active.

// media query event handler
if (matchMedia) {
    var mq = window.matchMedia("(min-width: 999px)");
    mq.addListener(WidthChange);
    WidthChange(mq);
}
// media query change
function WidthChange(mq) {
    if (mq.matches) {
        $("ul.sf-menu").superfish({
            delay: 350,
            speed: 400,
        });
    } else {
        $("ul.flexnav").flexNav({
            'animationSpeed': '250',
            'transitionOpacity': true,
            'buttonSelector': '.menu-button',
            'hoverIntent': false
        });
    }
}

As much as I would like to get this working with matchMedia, I am open to all suggestions.

Update: Thanks to Stephan's suggestion I now have the following code:

jQuery(document).ready(function () {
    // add destroy function for FlexNav
    flexNavDestroy = function () {
        $('.touch-button').off('touchstart click').remove();
        $('.item-with-ul *').off('focus');
    }
    // media query event handler
    if (matchMedia) {
        var mq = window.matchMedia("(min-width: 999px)");
        mq.addListener(WidthChange);
        WidthChange(mq);
    }
    // media query change

    function WidthChange(mq) {
        if (mq.matches) {
            if (typeof (flexNav) != "undefined") {
                flexNavDestroy();
            }
            superfish = $("ul.sf-menu").superfish({
                delay: 350,
                speed: 400,
            });
        } else {
            if (typeof (superfish) != "undefined") {
                superfish.superfish('destroy');
            }
            flexNav = $("ul.flexnav").flexNav({
                'animationSpeed': '250',
                'transitionOpacity': true,
                'buttonSelector': '.menu-button',
                'hoverIntent': false
            });
        }
    }
});

Remaining Issue: The destroy function for FlexNav is only partially destroying it.

2 Answers2

5

The best way would probably be to destroy the other plugin when you're activating one.

If I look in the source of Superfish there is a destroy function which does this, but flexNav doesn't have such a function. You can create one though:

flexNavDestroy = function(){
    $('.touch-button').off('touchstart click').remove();
    $(('.item-with-ul *').off('focus');
}

Then you could do this:

function WidthChange(mq) {
    if (mq.matches) {
        if(typeof(flexNav) != "undefined") {
            flexNavDestroy();
        }

        superfish = $("ul.sf-menu").superfish({
            delay: 350,
            speed: 400,
        });
    } else {
        if(typeof(superfish) != "undefined") {
            superfish.superfish('destroy'); 
        }

        flexNav = $("ul.flexnav").flexNav({
            'animationSpeed': '250',
            'transitionOpacity': true,
            'buttonSelector': '.menu-button',
            'hoverIntent': false
        });
    }
}

UPDATE I've looked a little bit more into FlexNav, and there's a few things I missed.

I think the styles are colliding because FlexNav sets a lot of styles by default. We can easily prevent that by using two classes: One for flexnav styling (the default .flexnav) that we can remove to hide all it's styles, and one for binding the javascript function (that will always stay there, or we can't re-attach it).

I generally like to prepend any classes that are meant as JS hooks with js-, so in my example (below) I replaces the .flexnav class on the menu with .js-flexnav. Then to activate flexnav you have to add this line just before you call $('ul.flexnav').flexNav()

$('.js-flexnav').addClass('flexnav');

In the destroy function you will have to remove the class again, which I will show shortly.

In addition, I'm not sure how Superfish does the showing and hiding, but since FlexNav collapses all submenus, it's also safe to say you should re-show them so that Superfish can do it's own thing.

The updated destroy function to reflect this:

function flexNavDestroy(){
    $('.touch-button').off('touchstart click').remove();
    $('.item-with-ul *').off('focus');
    $('.js-flexnav').removeClass('flexnav').find('ul').show(); // removes .flexnav for styling, then shows all children ul's
}

Here's a jsFiddle that shows activating/deactivating flexNav with the new code: http://jsfiddle.net/9HndJ/

Let me know if this does the trick for you!

Stephan Muller
  • 27,018
  • 16
  • 85
  • 126
  • Using the destroy functions definitely improves the issue, the only problem that persists is Superfish not initializing after a window resize. – MichaelCorey Sep 30 '13 at 23:54
  • Found an extra left parenthesis in the destroy function that was causing the Superfish problem. However, the destroy function only partially destroys FlexNav. – MichaelCorey Oct 01 '13 at 20:11
  • 1
    Hmm, that's what I was afraid of. I didn't install FlexNav, I wrote the function by looking at the source code and trying to undo all the binds that were in there. I'll try to make some time to see if I can help you out a bit more tomorrow. – Stephan Muller Oct 01 '13 at 20:37
1

here is an alternative path :

once page is loaded :

cache the menu in a jquery object, clone it & instantiate both plugin one on each clone

$menucontainer= $("#menu_container");
$memufish = $menucontainer.find(".menu");
$menuflex=$menufish.clone();

$menufish.superfish().detach();
$menuflex.prependTo($menucontainer).flexnav().detach(); 

(they are loaded anyway so it's no big deal even if most of the time one won't be needed, it will be there & ready just in case - however test if you can instantiate on the clone without appending it to the DOM)

depending on width append / prepend the required one

$menuflex.prependTo($menucontainer);    

on change width detach one reattach the other

$menufish.detach();
$menuflex.prependTo($menucontainer);

you could also work your way checking if plugin was instantiated on a width change (in order to not instantiate uselessly onload) but in any way I believe the use of clone() and detach() are very much adapted to solve easily your problem. The destroy way seems to be a hassle, lots of work (for the script as well when some user is raving with window resize) loss of time & a risk of many bugs to me ( expect more and more lag at every destroy re instantiate - with detach() no worries)

cons : will use a bit more memory overhaul

pros :

  • script will work less & it will be real fast to switch from one to the other

  • you could make a plugin from this and add other menu plugin to your app very easily without worry about conflict and how to destroy

mikakun
  • 2,203
  • 2
  • 16
  • 24