Nav Menu stuck open for iPad user

After an upgrade from 8.15.15 to 10.0.21 we have an iPad user (iOS 11.2) reporting that after opening a Nav component menu, the menu stays open blocking the screen. Clicking elsewhere does not remove the menu. The only workaround we’ve found is reloading the page.

This issue appears to be isolated to the iPad, not other tablets or any desktop that we’ve tested.

This same user is the one reporting the Case of the Jumping Cursor.

Hi Matt,

I created a test page in our DEV org running Millau 11.1.8. I am using a Dropdown Nav and it works as expected on my iPad (iOS 11.3). Now the question portion of the show.

  • Did you update the themes after upgrading to 10.0.21?
  • Can I get more details on how your Navigation component is configured?
Let’s start with those.

Apologies for taking so long to get back with you.

Themes are up to date.
My nav is a custom component which builds a skuid nav component based on salesforce data.

Here’s the runtime js:

    
    var $ = skuid.$,
        $u = skuid.utils,
        $xml = $u.makeXMLDoc,
        $uid = $u.generateUniqueId,
        $e = skuid.events.subscribe,
        mm = skuid.model.Model;
    //Helper function to generate random string if script label or signature type isn't available
    var randomString = function(length) {
        var result = '',
            chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        for (var i = length; i > 0; i--) result += chars[Math.floor(Math.random() * chars.length)];
        return result;
    },
        removeSpaces = function(text){
        return text.split(' ').join('');
    },
    //Staff and User Info
        getCurrentSession = function(){
        var model = skuid.$M('CurrentStaffSession'),
            row = model && model.getFirstRow();
        return row;    
    },
        getCurrentStaff = function(session){
        return session && session.Staff__c;
    },
        getCurrentProfile = function(session){
        return session && session.Staff__c && session.Staff__r.Profile__c;
    },
        getCurrentStaffName = function(session){
        return session && session.Staff__c && session.Staff__r.Name;
    },
        getUserProfile = function(){
            var model = skuid.$M('CurrentUser'),
                row = model && model.getFirstRow();
        return row && row.Optimize_Profile_Type__c;
    };
    skuid.componentType.register('ccoptimize__nav',function (element,xmlDef,component){
        // Get properties and variables
        var navCode = xmlDef.attr('code'),
            navName = xmlDef.attr('nav'),
            navRBP = xmlDef.attr('responsivebreakpoint'),
            navAlt = xmlDef.attr('alternateformat'),
            navLabel = 'Navigation_' + removeSpaces(navName),
            navModelId = (navLabel) ? navLabel.replace(/[^A-Z0-9_]+/ig, "") : 'Navigation_' + randomString(4),
            staffId, staffProfile, navMenuModel, navCustomModel;
        var getStaff = function(){
            var staffSession = getCurrentSession();
            staffId = getCurrentStaff(staffSession);
            staffProfile = getCurrentProfile(staffSession);
            return staffId;
        };
        var getNavVisibility = function(){
            var dfd = new $.Deferred(),
                model = new mm();
            model.objectName = 'Navigation_Menu_Set__c';
            model.id = removeSpaces(navCode)+'Model';
            model.recordsLimit = 1;
            model.fields = [
                { id: 'Optimize_Code__c'},
                { id: 'Id'},
                { id: 'Profile_Visibility__c'},
                { id: 'Name'}
            ];
            model.conditions = [
                {
                    type: 'fieldvalue',
                    field: 'Optimize_Code__c',
                    operator: '=',
                    value: navCode,
                    encloseValueInQuotes: true
                }
            ];
            $.when(model.initialize().load()).done(function(){
                var row = model.getFirstRow();
                if (row){
                    dfd.resolve(row.Profile_Visibility__c);
                } else {
                    dfd.reject();
                }
            }).fail(function(result){
                console.error('Dynamic model query failed: ' + result.error);
                dfd.reject(result.error);
            });
            return dfd.promise();
        };
        // Function to include legacy menu if no custom navigation available
        var includeLegacyMenuPage = function(){
            element.empty();
            var xmlPageInclude = $xml('<includepanel type="skuid" uniqueid="'+$uid()+'" pagename="LegacyNavigationMenu"/>');
            var includeContainer = $('<div id='+$uid()+'>');
            skuid.component.factory({
                xmlDefinition: xmlPageInclude,
                element: includeContainer
            });
            // Add components to the DOM element
            element.append(includeContainer);
        };

        // Generate Nav Menu Model
        var buildNavMenuModels = function(){
            var dfd = new $.Deferred();
            //Customizations Model
            navCustomModel = new mm();
            navCustomModel.objectName = 'Navigation_Customization__c';
            navCustomModel.id = 'NavCustomizations';
            navCustomModel.fields = [
                { id: 'Id'},
                { id: 'Name'},
                { id: 'Navigation_Menu__c'},
                { id: 'Navigation_Menu__r.Item__c'},
                { id: 'Navigation_Menu__r.Item__r.Icon__c'},
                { id: 'Navigation_Menu__r.Item__r.Name'},
                { id: 'Navigation_Menu__r.Item__r.Open_URL_In__c'},
                { id: 'Navigation_Menu__r.Item__r.URL__c'},
                { id: 'Navigation_Menu__r.Name'},
                { id: 'Navigation_Menu__r.Navigation_Menu_Set__c'},
                { id: 'Navigation_Menu__r.Navigation_Menu_Set__r.Name'},
                { id: 'Navigation_Menu__r.Navigation_Menu_Set__r.Optimize_Code__c'},
                { id: 'Navigation_Menu__r.Order__c'},
                { id: 'Navigation_Menu__r.Parent_Menu__c'},
                { id: 'Navigation_Menu__r.Parent_Menu__r.Item__c'},
                { id: 'Navigation_Menu__r.Parent_Menu__r.Item__r.Name'},
                { id: 'Navigation_Menu__r.Parent_Menu__r.Name'},
                { id: 'Navigation_Menu__r.Parent_Menu__r.Order__c'},
                { id: 'Navigation_Menu__r.Parent_Menu__r.Parent_Menu__c'},
                { id: 'Navigation_Menu__r.Parent_Menu__r.Parent_Menu__r.Item__c'},
                { id: 'Navigation_Menu__r.Parent_Menu__r.Parent_Menu__r.Item__r.Name'},
                { id: 'Navigation_Menu__r.Parent_Menu__r.Parent_Menu__r.Name'},
                { id: 'Navigation_Menu__r.Parent_Menu__r.Parent_Menu__r.Order__c'},
                { id: 'Staff__c'},
                { id: 'Staff__r.Name'},
                { id: 'Visible__c'}
            ];
            navCustomModel.conditions = [
                {
                    type: 'fieldvalue',
                    field: 'Navigation_Menu__r.Navigation_Menu_Set__r.Optimize_Code__c',
                    operator: '=',
                    value: navCode,
                    encloseValueInQuotes: true
                },
                {
                    type: 'fieldvalue',
                    field: 'Staff__c',
                    operator: '=',
                    value: staffId,
                    encloseValueInQuotes: true
                }
            ];
            navCustomModel.initialize().register();
            //Defaults Model
            navMenuModel = new mm();
            navMenuModel.objectName = 'Navigation_Menu__c';
            navMenuModel.id = navModelId;
            navMenuModel.dataSourceTypeName = 'salesforce';
            navMenuModel.orderByClause = "Parent_Menu__c NULLS FIRST, Order__c";
            navMenuModel.fields = [
                { id: 'Default_Profile_Visibility__c'},
                { id: 'Id'},
                { id: 'Item__c'},
                { id: 'Item__r.Icon__c'},
                { id: 'Item__r.Name'},
                { id: 'Item__r.Open_URL_In__c'},
                { id: 'Item__r.URL__c'},
                { id: 'Name'},
                { id: 'Navigation_Menu_Set__c'},
                { id: 'Navigation_Menu_Set__r.Name'},
                { id: 'Navigation_Menu_Set__r.Optimize_Code__c'},
                { id: 'Order__c'},
                { id: 'Parent_Menu__c'},
                { id: 'Parent_Menu__r.Item__c'},
                { id: 'Parent_Menu__r.Item__r.Name'},
                { id: 'Parent_Menu__r.Name'},
                { id: 'Parent_Menu__r.Order__c'},
                { id: 'Parent_Menu__r.Parent_Menu__c'},
                { id: 'Parent_Menu__r.Parent_Menu__r.Name'},
                { id: 'Parent_Menu__r.Parent_Menu__r.Order__c'},
                { id: 'Parent_Menu__r.Parent_Menu__r.Item__c'},
                { id: 'Parent_Menu__r.Parent_Menu__r.Item__r.Name'}
            ];
            navMenuModel.conditions = [
                {
                    type: 'fieldvalue',
                    field: 'Navigation_Menu_Set__r.Optimize_Code__c',
                    operator: '=',
                    value: navCode,
                    encloseValueInQuotes: true
                },
                {
                    type: 'fieldvalue',
                    field: 'Default_Profile_Visibility__c',
                    operator: 'includes',
                    value: staffProfile,
                    encloseValueInQuotes: true
                }
            ];
            navMenuModel.initialize().register();
            $.when(mm.load([navCustomModel,navMenuModel])).done(function(){
                dfd.resolve();
            }).fail(function(result){
                console.error('Dynamic Nav Customizations model query failed: ' + result.error);
                dfd.reject(result.error);
            });
            return dfd.promise();
        };
        var mergeMenus = function(){
            var menuRows = navMenuModel.getRows(),
                rowsToAdopt = [];
            $.each(navCustomModel.getRows(),function(n,customRow){
                var found = false, i = 0;
                while (!found &amp;&amp; i<menuRows.length){
                    if (menuRows[i].Id === customRow.Navigation_Menu__c){
                        if (customRow.Visible__c === "Off"){
                            navMenuModel.abandonRow(menuRows[i]);
                        }
                        found = true;
                    } else i++;
                }
                if (!found &amp;&amp; customRow.Visible__c === "On"){
                    var cr = customRow.Navigation_Menu__r;
                    rowsToAdopt.push({
                        'Id':customRow.Navigation_Menu__c,
                        'Name':cr.Name,
                        'Item__c':cr.Item__c,
                        'Item__r':cr.Item__r,
                        'Navigation_Menu_Set__c':cr.Navigation_Menu_Set__c,
                        'Navigation_Menu_Set__r':cr.Navigation_Menu_Set__r,
                        'Order__c':cr.Order__c,
                        'Parent_Menu__c':cr.Parent_Menu__c,
                        'Parent_Menu__r':cr.Parent_Menu__r
                    });
                }
            });
            if (rowsToAdopt.length) navMenuModel.adoptRows(rowsToAdopt);
        };
        var renderMenus = function(){
            mergeMenus();
            var menu = {}, menuOrder = {}, usedRows = [];
            // Get Top Menu
            $.each(navMenuModel.getRows(),function(){
                if (!this.Parent_Menu__c &amp;&amp; this.Item__c) {
                    // Menu Id
                    menu[this.Id] = {
                        label: this.Item__r.Name,
                        order: this.Order__c,
                        url: this.Item__r.URL__c,
                        target: this.Item__r.Open_URL_In__c,
                        icon: this.Item__r.Icon__c,
                        kids: {}
                    };
                    // Ordered Menu
                    menuOrder[this.Order__c] = {
                        label: this.Item__r.Name,
                        url: this.Item__r.URL__c,
                        target: this.Item__r.Open_URL_In__c,
                        icon: this.Item__r.Icon__c,
                        kids: {}
                    };
                    usedRows.push(this);
                }
            });
            navMenuModel.abandonRows(usedRows);
            usedRows = [];
            //Get Secondary Menu
            $.each(navMenuModel.getRows(),function(){
                if (this.Parent_Menu__c &amp;&amp; menu[this.Parent_Menu__c] &amp;&amp; this.Item__c) {
                    // Menu
                    menu[this.Parent_Menu__c].kids[this.Id] = {
                        label: this.Item__r.Name,
                        order: this.Order__c,
                        url: this.Item__r.URL__c,
                        target: this.Item__r.Open_URL_In__c,
                        icon: this.Item__r.Icon__c,
                        kids: {}
                    };
                    // Ordered Menu
                    menuOrder[this.Parent_Menu__r.Order__c].kids[this.Order__c] = {
                        label: this.Item__r.Name,
                        url: this.Item__r.URL__c,
                        target: this.Item__r.Open_URL_In__c,
                        icon: this.Item__r.Icon__c,
                        kids: {}
                    };
                    usedRows.push(this);
                }
            });
            navMenuModel.abandonRows(usedRows);
            usedRows = [];
            //Get Tertiary Menus
            $.each(navMenuModel.getRows(),function(){
                if (this.Parent_Menu__c &amp;&amp; this.Parent_Menu__r.Parent_Menu__c &amp;&amp; this.Item__c &amp;&amp; 
                    menu[this.Parent_Menu__r.Parent_Menu__c] &amp;&amp; 
                    menu[this.Parent_Menu__r.Parent_Menu__c].kids[this.Parent_Menu__c]
                    ) {
                    // Menu Id
                    menu[this.Parent_Menu__r.Parent_Menu__c].kids[this.Parent_Menu__c].kids[this.Id] = {
                        label: this.Item__r.Name,
                        order: this.Order__c,
                        url: this.Item__r.URL__c,
                        target: this.Item__r.Open_URL_In__c,
                        icon: this.Item__r.Icon__c
                    };
                    // Ordered Menu
                    menuOrder[this.Parent_Menu__r.Parent_Menu__r.Order__c].kids[this.Parent_Menu__r.Order__c].kids[this.Order__c] = {
                        label: this.Item__r.Name,
                        url: this.Item__r.URL__c,
                        target: this.Item__r.Open_URL_In__c,
                        icon: this.Item__r.Icon__c
                    };
                    usedRows.push(this);
                }
            });
            navMenuModel.abandonRows(usedRows);
            var xmlItems = [];
            $.each(menuOrder,function(i,m){
                var actionXML = m.url ? 
                    $xml('<actions/>').append(
                        $xml('<action type="redirect" window="'+m.target+'" url="'+m.url+'"/>')
                        ) : 
                    $xml('<actions/>');
                // Get Children
                var kids = [];
                $.each(m.kids, function(i,s) {
                    var sActionXML = s.url ? 
                        $xml('<actions/>').append(
                            $xml('<action type="redirect" window="'+s.target+'" url="'+s.url+'"/>')
                            ) : 
                        $xml('<actions/>');
                    
                    // Get Grandchildren
                    var grandkids = [];
                    $.each(s.kids, function(i,t) {
                        var tActionXML = t.url ? 
                            $xml('<actions/>').append(
                                $xml('<action type="redirect" window="'+t.target+'" url="'+t.url+'"/>')
                                ) : 
                            $xml('<actions/>');
                        grandkids.push($xml('<navigationitem label="'+$u.encodeHTML(t.label)+'"'+ (t.icon ? ' icon="'+t.icon+'"' : '') +'/>').append(tActionXML));
                    });
                    kids.push($xml('<navigationitem label="'+$u.encodeHTML(s.label)+'"'+ (s.icon ? ' icon="'+s.icon+'"' : '') +'/>').append(
                        sActionXML,
                        $xml('<navigationitems/>').append(grandkids)
                        )
                    );
                });
                xmlItems.push($xml('<navigationitem label="'+$u.encodeHTML(m.label)+'"'+ (m.icon ? ' icon="'+m.icon+'"' : '') +'/>').append(
                    actionXML,
                    $xml('<navigationitems/>').append(kids)
                    )
                );
            });
            // Define XML for our child components
            var xmlNavigation = $xml('<navigation uniqueid="'+navLabel+'" responsivebreakpoint="'+navRBP+'" alternateformat="'+navAlt+'" overflowtomenu="false"/>').append(
                $xml('<navigationitems/>').append(xmlItems)
                );
            // Make containers for our components
            var container = $('<div id='+$uid()+'>');
            skuid.component.factory({
                xmlDefinition: xmlNavigation,
                element: container
            });
            // Add components to the DOM element
            element.append(container);
        };
        // Generate C

And here’s the builder-side js:

(function(skuid) {
    // Global shortcuts &amp; variables
    var $ = skuid.$,
        $xml = skuid.utils.makeXMLDoc,
        b = skuid.builder,
        c = b.core;
//Custom Navigation Component
    c.registerBuilder(new c.Builder({
        id: "ccoptimize__nav",
        name: "Custom Navigation",
        icon: "sk-icon-share",
        description: "Inserts a Custom Navigation Component based on the Staff or Profile",
        isJSCreateable: true,
        componentRenderer : function (component) {
            // Set title
            component.setTitle(component.builder.name);
            // Shortcuts
            var state = component.state;
            // Script name property
            var navName = state.attr('nav');
            // Build containers and contents
            var builderText = 'The Custom Navigation Component creates a Skuid Navigation component based on the configuration found in the ' +
                            '<a href="/apex/skuid__ui?page=NavMenuSettings" taarget="_blank">Navigation Menu Settings</a>.';
            var div = $('<div>').html(builderText);
            
            // Add contents to DOM
            component.body.append(div); 
        },
        propertiesRenderer : function (propertiesObj, component) {
            propertiesObj.setTitle("Custom Navigation Component Properties");
            var state = component.state;
            var propCategories = [];
            var basicPropsList = [
                {
                    id : 'navset',
                    type : 'autocomplete',
                    sobject : 'Navigation_Menu_Set__c',
                    fieldsToReturn : ['Name', 'Optimize_Code__c', 'Id'],
                    fieldsToSearch : ['Name', 'Optimize_Code__c'],
                    displayTemplate : '{{Name}} ({{Optimize_Code__c}})',
                    valueTemplates : {
                            'nav' : '{{Name}}',
                            'code' : '{{Optimize_Code__c}}'
                    },
                    order : 'Name',
                    label : 'Navigation Set (autocomplete)',
                    required : true,
                    onChange : function() {
                            component.refresh();
                    }
                },
                {
                    id: "responsivebreakpoint",
                    type: "picklist",
                    label: "Screen Size to Change Format",
                    location: "attribute",
                    picklistEntries: [
                        {label: 'Never', value: ''},
                        {label: 'Tablets and smaller', value: 'medium'},
                        {label: 'Phones', value: 'small'}
                    ],
                    onChange: function(){
                        component.rebuildProps();
                    }
                }
            ];
            if (state.attr('responsivebreakpoint')) basicPropsList.push({
                    id: "alternateformat",
                    type: "picklist",
                    label: "Alternate Format",
                    location: "attribute",
                    picklistEntries: [
                        {label: 'Collapse to menu', value: 'collapse'},
                        {label: 'Wrap centered', value: 'wrap'}
                    ]
            });
            var advancedPropsList = [
                c.coreProps.uniqueIdProp({component: component}),
                b.cssClassProp
            ];
            propCategories.push(
                {
                    name: "Basic",
                    props: basicPropsList,
                },
                {
                    name: "Advanced",
                    props: advancedPropsList
                },
                c.getRenderingCategory({
                    component: component,
                    model: null,
                    propViewer: propertiesObj
                })
            );
            if(skuid.mobile) propCategories.push({ name : "Remove", props : [{ type : "remove" }] });
            propertiesObj.applyPropsWithCategories(propCategories,state);
        },
        defaultStateGenerator : function() {
            return skuid.utils.makeXMLDoc('<ccoptimize__nav/>');
        }
    }));
})(skuid);

Hi Matt,

This quickly exited my level of expertise. There was a big jump in Skuid versions and feel it would be difficult to easily identify the change(s) that are affecting your custom component. 

That said, a co-worker of mine asked if your component is only listening for an onclick event, or something that tapping on a screen doesn’t trigger?

Couple options:

- Give the Skuid Community time to weigh in.
- Contact your Customer Success Rep to discuss options on getting more targeted expertise.





Matthew-- Don’t give up so easily!

The fact that I’m using a custom component may be irrelevant, since I all my component is doing is dynamically creating a skuid nav component on pageload based on salesforce data. My component doesn’t change anything at all about the way the standard skuid nav component works.

I don’t have an iPad handy or I would test it myself.

The user has upgraded their iPad to 11.3. Can you test Nav functionality on an iPad with a 10.0.21 org?

I’m wondering if the issue has something to do with the theme? Is there a good way I can pass a theme file to you?

Ok, I’ll bite. Let me see if there is anything I can do on my end. No promises.

Can you add your theme to Dropbox and post the link?

Can the users try a cloned page with a standard theme using iPad? Had to ask.

Were you able to get where you were headed? Or still stuck.

Still stuck… moved the user to a different theme (skuid’s stock Modern theme), but the menus are still sticking.

I’ll try to get you my theme tomorrow, but that doesn’t seem to be the problem…

ok. I’ll keep an eye out for it. I did spin up a dev org with Brooklyn 10.0.21. At least have the same environment set up for testing.

Here’s the theme: https://www.dropbox.com/s/aavnjrdm4heedwa/OptimizeModern.sktheme?dl=0

Got it! Thanks Matt.

Confirmed your theme is not the issue. Are you able to send me a component pack? Could it be adjusted to work in my org? We’ve gone this far, so open to ideas.

Experiencing something similar. The Nav component dropdown when on mobile is semi stuck. You have to tap somewhere else on screen for the dropdown to go away.

This is on latest version of Android. 8.1.0

Hi Pat, I created a quick page with navigation, page title and table components. The menu only hides if I tap on the Nav or table, not anywhere else. Is that what you’re experiencing?

Tapping anywhere else hides the dropdown. Regardless of selecting menu item.

Our user is not able to hide the menu except by selecting an option from that menu. It will not collapse if she clicks elsewhere on the screen.

Oh fun. Opposite problem.

Just to make sure I understand, the nav is open on page load. Then you click anywhere to close. Bear with me.

No. Upon tapping on it. Selecting an item should also close the drop down.