New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tabs widget exhibits odd behavior when it's on a page that is linked to #7169

Closed
bkosborne opened this Issue Feb 24, 2014 · 29 comments

Comments

Projects
None yet
@bkosborne

A bit of a confusing title, sorry.

When a tabs widget is on a page, and I link to that page, it seems that the page is loaded twice. The second time it's loaded, almost the entire page is re-inserted into the DOM near the original tabs widget for the first page load.

Still confused?

Here's a working demonstration. Notice how the page loads twice when clicking the link. If you go to page2.html directly, there is no issue. Only when the page is loaded via AJAX thru JQM.

The example code is taken almost exactly from the docs page.

I thought maybe the navbar widget was causing some issues, so I removed that, but it makes no difference.

I have a stack overflow post as well as a jquery mobile forums post on the subject.

I'm either missing something very obvious, or I believe there is a bug in the implementation of the tabs widget or the documentation for the tabs widget.

@arschmitz

This comment has been minimized.

Show comment
Hide comment
@arschmitz

arschmitz Feb 24, 2014

Member

@bkosborne thank you for reporting this. Can you please see our contributing guidelines https://github.com/jquery/jquery-mobile/blob/master/CONTRIBUTING.md and create a jsbin testpage demonstrating the issue. I took the liberty of creating one http://jsbin.com/ofuhaw/1190/edit and I don't see an issue I'm going to close this as incomplete and cant reproduce if you can post a simple jsbin testpage reproducing the issue we will reopen this

Member

arschmitz commented Feb 24, 2014

@bkosborne thank you for reporting this. Can you please see our contributing guidelines https://github.com/jquery/jquery-mobile/blob/master/CONTRIBUTING.md and create a jsbin testpage demonstrating the issue. I took the liberty of creating one http://jsbin.com/ofuhaw/1190/edit and I don't see an issue I'm going to close this as incomplete and cant reproduce if you can post a simple jsbin testpage reproducing the issue we will reopen this

@arschmitz arschmitz closed this Feb 24, 2014

@bkosborne

This comment has been minimized.

Show comment
Hide comment
@bkosborne

bkosborne Feb 24, 2014

Hello,

I tried modifying the jsbin's, but linking from one jsbin to another doesn't seem to work right, unless I'm in the /edit context, which I don't believe is a true real world example.

Page 1: http://jsbin.com/ofuhaw/1196
Page 2: http://jsbin.com/ofuhaw/1195

You can see that clicking the link to page 2 doesn't load properly. Not sure what's going on there.

The markup is identical to the example I provided on my website except for the version of jQM used. I tried using the Git source one, but it does not resolve the issue.

Hello,

I tried modifying the jsbin's, but linking from one jsbin to another doesn't seem to work right, unless I'm in the /edit context, which I don't believe is a true real world example.

Page 1: http://jsbin.com/ofuhaw/1196
Page 2: http://jsbin.com/ofuhaw/1195

You can see that clicking the link to page 2 doesn't load properly. Not sure what's going on there.

The markup is identical to the example I provided on my website except for the version of jQM used. I tried using the Git source one, but it does not resolve the issue.

@bkosborne

This comment has been minimized.

Show comment
Hide comment
@bkosborne

bkosborne Feb 24, 2014

Also please note that I made an effort to not "pollute" the same pages I made. I understand the benefit of having a jsbin available to test with, but my examples are clean and don't contain any unnecessary markup that could potentially interfere with the desired functionality.

Also please note that I made an effort to not "pollute" the same pages I made. I understand the benefit of having a jsbin available to test with, but my examples are clean and don't contain any unnecessary markup that could potentially interfere with the desired functionality.

@arschmitz

This comment has been minimized.

Show comment
Hide comment
@arschmitz

arschmitz Feb 24, 2014

Member

@bkosborne the issue with the edit view is a know issue with jsbin returning json data in some ajax requests. The edit view is just an iframe and does not effect the example. I thank you for your attempt at making a clean test page this still only has limited usefullness we cant debug it because we cant make changes we cant change jquery versions or mobile versions this is why we use jsbin and the fact that it works in demos and jsbin but not your test page leads me to believe there is either an unseen issue with your page. I have even changed to match your page with 1.4.1 instead of master and I still see no issue http://jsbin.com/ofuhaw/1197/edit

Member

arschmitz commented Feb 24, 2014

@bkosborne the issue with the edit view is a know issue with jsbin returning json data in some ajax requests. The edit view is just an iframe and does not effect the example. I thank you for your attempt at making a clean test page this still only has limited usefullness we cant debug it because we cant make changes we cant change jquery versions or mobile versions this is why we use jsbin and the fact that it works in demos and jsbin but not your test page leads me to believe there is either an unseen issue with your page. I have even changed to match your page with 1.4.1 instead of master and I still see no issue http://jsbin.com/ofuhaw/1197/edit

@bkosborne

This comment has been minimized.

Show comment
Hide comment
@bkosborne

bkosborne Feb 24, 2014

@arschmitz,

I completely replaced my page1.html and page2.html with your examples, replacing only the link. The problem is still present on my pages (though note that theres a few issues with the markup, like duplicated ID's on the second page).

I cannot trust that jsbin is operating as a normal web page would. My environment is a vanilla apache web server and there is no special fetching or iframes included. I can't quite figure out how jsbin is retrieving the second page after analyzing Chrome devtools. I see a request is made to http://jsbin.com/ofuhaw/1198?0.5647212639451027, and the MIME type is text/event-stream, but I'm not sure what that means here? The response is blank as analyzed in the dev tools.

I urge you to please consider my example, or to quickly setup your own. I'm sure you have a local web server on your computer where you can simply place two HTML pages. I can't seem to get jsbin to work properly. I would be more trusting if I could view the standalone page and link to the other standalone page, without using that edit view.

@arschmitz,

I completely replaced my page1.html and page2.html with your examples, replacing only the link. The problem is still present on my pages (though note that theres a few issues with the markup, like duplicated ID's on the second page).

I cannot trust that jsbin is operating as a normal web page would. My environment is a vanilla apache web server and there is no special fetching or iframes included. I can't quite figure out how jsbin is retrieving the second page after analyzing Chrome devtools. I see a request is made to http://jsbin.com/ofuhaw/1198?0.5647212639451027, and the MIME type is text/event-stream, but I'm not sure what that means here? The response is blank as analyzed in the dev tools.

I urge you to please consider my example, or to quickly setup your own. I'm sure you have a local web server on your computer where you can simply place two HTML pages. I can't seem to get jsbin to work properly. I would be more trusting if I could view the standalone page and link to the other standalone page, without using that edit view.

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

jsbin may not be able to accommodate retrieval via Ajax, but jsfiddle does.

Contributor

gabrielschulhof commented Feb 25, 2014

jsbin may not be able to accommodate retrieval via Ajax, but jsfiddle does.

@gabrielschulhof gabrielschulhof self-assigned this Feb 25, 2014

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

This is extremely weird. It seems to be running two copies of jQuery Mobile. After changing to page two, it starts executing a second copy of jQuery Mobile, because, after the transition to page 2 is completed, it adds ui-page-active to page 1. I added a DOM breakpoint after the transition completed to trap the moment when ui-page-active is erroneously re-added to page 1, and it turns out the ui-page-active class is being added from ... $.mobile.initializePage!

Contributor

gabrielschulhof commented Feb 25, 2014

This is extremely weird. It seems to be running two copies of jQuery Mobile. After changing to page two, it starts executing a second copy of jQuery Mobile, because, after the transition to page 2 is completed, it adds ui-page-active to page 1. I added a DOM breakpoint after the transition completed to trap the moment when ui-page-active is erroneously re-added to page 1, and it turns out the ui-page-active class is being added from ... $.mobile.initializePage!

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

OK. I've tracked it down to where tabs loads content. The variable response ends up containing the whole document from inside page2.html. Basically, tabs does not sanitize content the way jQuery Mobile does, by getting rid of the head. It simply places the whole shebang into an element, which is causing core to start evaluating the scripts it finds inside.

Contributor

gabrielschulhof commented Feb 25, 2014

OK. I've tracked it down to where tabs loads content. The variable response ends up containing the whole document from inside page2.html. Basically, tabs does not sanitize content the way jQuery Mobile does, by getting rid of the head. It simply places the whole shebang into an element, which is causing core to start evaluating the scripts it finds inside.

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

Tracing further still, it turns out that "#one" isn't considered a local link at the time when the tabs' load() function gets called, because the native anchor.href returns the new URL (the URL of page2) instead of the old URL (the URL of page1), because the page load() happens before we navigate and thereby update the location.href.

Basically, isLocal() is consulting location.href, which in our case is not yet updated to reflect that page2.html is the current page.

Contributor

gabrielschulhof commented Feb 25, 2014

Tracing further still, it turns out that "#one" isn't considered a local link at the time when the tabs' load() function gets called, because the native anchor.href returns the new URL (the URL of page2) instead of the old URL (the URL of page1), because the page load() happens before we navigate and thereby update the location.href.

Basically, isLocal() is consulting location.href, which in our case is not yet updated to reflect that page2.html is the current page.

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

Once it decides that the anchor is not local, it Ajax-es in "#one", which basically ends up Ajax-ing in location.href + "#one", which means the page Ajax-es itself in.

Contributor

gabrielschulhof commented Feb 25, 2014

Once it decides that the anchor is not local, it Ajax-es in "#one", which basically ends up Ajax-ing in location.href + "#one", which means the page Ajax-es itself in.

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

This also wouldn't work when pushState is not available, because in that case the location.href never changes.

Contributor

gabrielschulhof commented Feb 25, 2014

This also wouldn't work when pushState is not available, because in that case the location.href never changes.

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

A possible solution would be to make the isLocal() function of the tabs widget an extension point, so we could override it in jQM. @scottgonzalez?

Contributor

gabrielschulhof commented Feb 25, 2014

A possible solution would be to make the isLocal() function of the tabs widget an extension point, so we could override it in jQM. @scottgonzalez?

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

OK. Looks like that's already done, so we need to grab a newer version of the tabs widget.

Contributor

gabrielschulhof commented Feb 25, 2014

OK. Looks like that's already done, so we need to grab a newer version of the tabs widget.

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

isLocal() was moved into the prototype in jquery/jquery-ui@ecd4f95, so we need something later than that. There have been no releases containing that change.

Contributor

gabrielschulhof commented Feb 25, 2014

isLocal() was moved into the prototype in jquery/jquery-ui@ecd4f95, so we need something later than that. There have been no releases containing that change.

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

There is another way to fix this, but it's, shall we say, not ideal. You can extend tabs and override _createWidget. In the overriding function, you account for the fact that the tabs' parent page has not yet been navigated to by the fact that it does not have the ui-page-active class. You then wait for the page container to perform the hash manipulation, causing the location to change to the new URL, and only then (upon pagebeforeshow) do you continue with _createWidget by calling this._super():

$.widget( "ui.tabs", $.ui.tabs, {

    _createWidget: function( options, element ) {
        var page, delayedCreate,
            that = this;

        if ( $.mobile.page ) {
            page = $( element )
                .parents( ":jqmData(role='page'),:mobile-page" )
                .first();

            if ( page.length > 0 && !page.hasClass( "ui-page-active" ) ) {
                delayedCreate = this._super;
                page.one( "pagebeforeshow", function() {
                    delayedCreate.call( that, options, element );
                });
            }
        } else {
            return this._super();
        }
    }
});
Contributor

gabrielschulhof commented Feb 25, 2014

There is another way to fix this, but it's, shall we say, not ideal. You can extend tabs and override _createWidget. In the overriding function, you account for the fact that the tabs' parent page has not yet been navigated to by the fact that it does not have the ui-page-active class. You then wait for the page container to perform the hash manipulation, causing the location to change to the new URL, and only then (upon pagebeforeshow) do you continue with _createWidget by calling this._super():

$.widget( "ui.tabs", $.ui.tabs, {

    _createWidget: function( options, element ) {
        var page, delayedCreate,
            that = this;

        if ( $.mobile.page ) {
            page = $( element )
                .parents( ":jqmData(role='page'),:mobile-page" )
                .first();

            if ( page.length > 0 && !page.hasClass( "ui-page-active" ) ) {
                delayedCreate = this._super;
                page.one( "pagebeforeshow", function() {
                    delayedCreate.call( that, options, element );
                });
            }
        } else {
            return this._super();
        }
    }
});
@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

Note that you currently must make use of the deprecated pagebeforeshow because of #7176.

Contributor

gabrielschulhof commented Feb 25, 2014

Note that you currently must make use of the deprecated pagebeforeshow because of #7176.

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

Here's the fiddle with the above workaround applied.

Contributor

gabrielschulhof commented Feb 25, 2014

Here's the fiddle with the above workaround applied.

@gabrielschulhof

This comment has been minimized.

Show comment
Hide comment
@gabrielschulhof

gabrielschulhof Feb 25, 2014

Contributor

@arschmitz brought up the point that an even simpler workaround is to just specify absolute URLs for your tab links. So, that would be http://server/path/to/page2.html#one and http://server/path/to/page2.html#two.

Contributor

gabrielschulhof commented Feb 25, 2014

@arschmitz brought up the point that an even simpler workaround is to just specify absolute URLs for your tab links. So, that would be http://server/path/to/page2.html#one and http://server/path/to/page2.html#two.

@gabrielschulhof gabrielschulhof added this to the 1.5.0 milestone Feb 25, 2014

@gabrielschulhof gabrielschulhof removed their assignment Feb 25, 2014

@bkosborne

This comment has been minimized.

Show comment
Hide comment
@bkosborne

bkosborne Feb 26, 2014

Thanks for looking into this. The latest suggested workaround, using absolute links, doesn't appear to work: http://bkosborne.com/jquery_mobile_tabs/page1.html

I'll try converting to a fiddle later on if that will help.

For now I think I'll just create my own basic tab functionality since this looks like it's a legit issue that requires a jQuery UI update.

Thanks for looking into this. The latest suggested workaround, using absolute links, doesn't appear to work: http://bkosborne.com/jquery_mobile_tabs/page1.html

I'll try converting to a fiddle later on if that will help.

For now I think I'll just create my own basic tab functionality since this looks like it's a legit issue that requires a jQuery UI update.

@androidfan415

This comment has been minimized.

Show comment
Hide comment
@androidfan415

androidfan415 Mar 12, 2014

I'm glad I found this thread. When I navigate between pages containing any tabbed element on the page, jquerymobile goes into an infinite loop pulling down the same page over and over as well as other pages and taking me to the wrong page. Using Network tab on Developer Tools in Chrome shows this. It would be nice if we could put a note on the demo documentation or somewhere to alert other devs.

I'm glad I found this thread. When I navigate between pages containing any tabbed element on the page, jquerymobile goes into an infinite loop pulling down the same page over and over as well as other pages and taking me to the wrong page. Using Network tab on Developer Tools in Chrome shows this. It would be nice if we could put a note on the demo documentation or somewhere to alert other devs.

@vincentbab

This comment has been minimized.

Show comment
Hide comment
@vincentbab

vincentbab Mar 14, 2014

This issue is very annoying and makes the tabs wiget almost unusable.
I spent the last 3hours trying to understant what was going wrong with my mobile app. Put some breakpoints down to the IsLocal function as explained by gabrielschulhof but had no idea how to fix the bug.
The "_createWidget" workaround is working well, thank you for this.

This issue is very annoying and makes the tabs wiget almost unusable.
I spent the last 3hours trying to understant what was going wrong with my mobile app. Put some breakpoints down to the IsLocal function as explained by gabrielschulhof but had no idea how to fix the bug.
The "_createWidget" workaround is working well, thank you for this.

@sammich

This comment has been minimized.

Show comment
Hide comment
@sammich

sammich Aug 6, 2014

I've come across these incorrectly created tabs. Even though the a[href=#] for the tabs point to legitimate elements after the [data-role=navbar] element, a new generic set of tabs [id=ui-tabs-#] are being made.

Below are the pages which should demonstrate this issue. With the correct role set (auto-enhance), the tab contents all show and no hiding/showing is going on.

[role=tabs] - auto-enhance'd (broken)
index: http://jsfiddle.net/2769c/9/
contentPage: http://jsfiddle.net/6X86z/11/

This pair with an invalid role-type doesn't auto-enhance, but will work once you init the tabs manually after page load.

[role=nottabs] - manual enhance (works)
index: http://jsfiddle.net/2769c/11/
contentPage: http://jsfiddle.net/6X86z/10/

sammich commented Aug 6, 2014

I've come across these incorrectly created tabs. Even though the a[href=#] for the tabs point to legitimate elements after the [data-role=navbar] element, a new generic set of tabs [id=ui-tabs-#] are being made.

Below are the pages which should demonstrate this issue. With the correct role set (auto-enhance), the tab contents all show and no hiding/showing is going on.

[role=tabs] - auto-enhance'd (broken)
index: http://jsfiddle.net/2769c/9/
contentPage: http://jsfiddle.net/6X86z/11/

This pair with an invalid role-type doesn't auto-enhance, but will work once you init the tabs manually after page load.

[role=nottabs] - manual enhance (works)
index: http://jsfiddle.net/2769c/11/
contentPage: http://jsfiddle.net/6X86z/10/

@armanim

This comment has been minimized.

Show comment
Hide comment
@armanim

armanim Sep 19, 2014

@gabrielschulhof your idea helps a lot. Thanks.

armanim commented Sep 19, 2014

@gabrielschulhof your idea helps a lot. Thanks.

@jjwdesign

This comment has been minimized.

Show comment
Hide comment
@jjwdesign

jjwdesign Sep 29, 2014

Thank you Gabriel! Your comment corrected the issue for me: "gabrielschulhof commented on Feb 25"

Thank you Gabriel! Your comment corrected the issue for me: "gabrielschulhof commented on Feb 25"

@ankitgoyal91

This comment has been minimized.

Show comment
Hide comment
@ankitgoyal91

ankitgoyal91 Nov 10, 2014

@gabrielschulhof
Thanks buddy!!
Just embed following code in your first page javascript section and it will work fine.
-------------------------Code Starts-------------------------------------------
$.widget( "ui.tabs", $.ui.tabs, {

_createWidget: function( options, element ) {
    var page, delayedCreate,
        that = this;

    if ( $.mobile.page ) {
        page = $( element )
            .parents( ":jqmData(role='page'),:mobile-page" )
            .first();

        if ( page.length > 0 && !page.hasClass( "ui-page-active" ) ) {
            delayedCreate = this._super;
            page.one( "pagebeforeshow", function() {
                delayedCreate.call( that, options, element );
            });
        }
    } else {
        return this._super();
    }
}

});
-------------------------Code Ends-------------------------------------------

All credit goes to @gabrielschulhof .

This issue raised by @bkosborne is indeed a bug in JqueryMobile and its long known and faced by many developers but don't know why JqueryMobile team is not working on this.
This issue is not solved even in JqueryMobile 1.4.5

@gabrielschulhof
Thanks buddy!!
Just embed following code in your first page javascript section and it will work fine.
-------------------------Code Starts-------------------------------------------
$.widget( "ui.tabs", $.ui.tabs, {

_createWidget: function( options, element ) {
    var page, delayedCreate,
        that = this;

    if ( $.mobile.page ) {
        page = $( element )
            .parents( ":jqmData(role='page'),:mobile-page" )
            .first();

        if ( page.length > 0 && !page.hasClass( "ui-page-active" ) ) {
            delayedCreate = this._super;
            page.one( "pagebeforeshow", function() {
                delayedCreate.call( that, options, element );
            });
        }
    } else {
        return this._super();
    }
}

});
-------------------------Code Ends-------------------------------------------

All credit goes to @gabrielschulhof .

This issue raised by @bkosborne is indeed a bug in JqueryMobile and its long known and faced by many developers but don't know why JqueryMobile team is not working on this.
This issue is not solved even in JqueryMobile 1.4.5

@ankitgoyal91

This comment has been minimized.

Show comment
Hide comment
@ankitgoyal91

ankitgoyal91 Nov 10, 2014

I appreciate the effort spent by @bkosborne in concerning this and raising this to @arschmitz , but don't know why @arschmitz is not understanding and instead giving his own examples.

I appreciate the effort spent by @bkosborne in concerning this and raising this to @arschmitz , but don't know why @arschmitz is not understanding and instead giving his own examples.

gabrielschulhof added a commit that referenced this issue Dec 5, 2014

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725

gabrielschulhof added a commit that referenced this issue Dec 29, 2014

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727

gabrielschulhof added a commit that referenced this issue Jan 15, 2015

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727

gabrielschulhof added a commit that referenced this issue Feb 12, 2015

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727
@slavap

This comment has been minimized.

Show comment
Hide comment
@slavap

slavap Mar 5, 2015

Workaround proposed by @gabrielschulhof is almost OK for me, but I've encounter some issues in IE9, for some reason pagebeforeshow was not raised in some cases. So I've improved the code with setInterval(), and now it works just fine in all browsers:

$.widget( "ui.tabs", $.ui.tabs, {

    _createWidget: function( options, element ) {
        var page, delayedCreate,
            that = this;

        if ( $.mobile.page ) {
            page = $( element )
                .parents( ":jqmData(role='page'),:mobile-page" )
                .first();

            if ( page.length > 0 && !page.hasClass( "ui-page-active" ) ) {
                delayedCreate = this._super;
                page.one( "pagebeforeshow", function() {
                    //if (window.console) console.log("pagebeforeshow: delayedCreate exec");
                    if (delayedCreate !== null) delayedCreate.call( that, options, element );
                    delayedCreate = null;
                });
                var wait = setInterval(function() {
                    if (delayedCreate === null) clearInterval(wait);
                    else {
                        if ( page.hasClass( "ui-page-active" ) ) {
                            if (delayedCreate !== null) delayedCreate.call( that, options, element );
                            delayedCreate = null;
                            clearInterval(wait);
                        }
                    }
                }, 200);
            } else {
                return this._super( options, element );
            }
        } else {
            return this._super( options, element );
        }
    }
}); 

slavap commented Mar 5, 2015

Workaround proposed by @gabrielschulhof is almost OK for me, but I've encounter some issues in IE9, for some reason pagebeforeshow was not raised in some cases. So I've improved the code with setInterval(), and now it works just fine in all browsers:

$.widget( "ui.tabs", $.ui.tabs, {

    _createWidget: function( options, element ) {
        var page, delayedCreate,
            that = this;

        if ( $.mobile.page ) {
            page = $( element )
                .parents( ":jqmData(role='page'),:mobile-page" )
                .first();

            if ( page.length > 0 && !page.hasClass( "ui-page-active" ) ) {
                delayedCreate = this._super;
                page.one( "pagebeforeshow", function() {
                    //if (window.console) console.log("pagebeforeshow: delayedCreate exec");
                    if (delayedCreate !== null) delayedCreate.call( that, options, element );
                    delayedCreate = null;
                });
                var wait = setInterval(function() {
                    if (delayedCreate === null) clearInterval(wait);
                    else {
                        if ( page.hasClass( "ui-page-active" ) ) {
                            if (delayedCreate !== null) delayedCreate.call( that, options, element );
                            delayedCreate = null;
                            clearInterval(wait);
                        }
                    }
                }, 200);
            } else {
                return this._super( options, element );
            }
        } else {
            return this._super( options, element );
        }
    }
}); 

gabrielschulhof added a commit that referenced this issue Mar 16, 2015

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727

gabrielschulhof added a commit that referenced this issue Mar 18, 2015

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727

gabrielschulhof added a commit that referenced this issue Mar 20, 2015

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727

gabrielschulhof added a commit that referenced this issue Mar 27, 2015

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727

gabrielschulhof added a commit that referenced this issue Apr 8, 2015

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727
@slavap

This comment has been minimized.

Show comment
Hide comment
@slavap

slavap May 20, 2015

I've backported the latest @gabrielschulhof fix to jqm 1.4.5, so now tabs is working just fine.
_processTabs() and load() are just copies of original 1.4.5 code with isLocal() replaced by that._isLocal()

$.widget( "ui.tabs", $.ui.tabs, {

    _isLocal: function( anchor ) {
        var path, baseUrl, absUrl;

        if ( $.mobile.ajaxEnabled ) {
            path = $.mobile.path;
            baseUrl = path.parseUrl( $.mobile.base.element.attr( "href" ) );
            absUrl = path.parseUrl( path.makeUrlAbsolute( anchor.getAttribute( "href" ),
                baseUrl ) );

            return ( path.isSameDomain( absUrl.href, baseUrl.href ) &&
                absUrl.pathname === baseUrl.pathname );
        }

        var rhash = /#.*$/;
        var anchorUrl, locationUrl;

        anchorUrl = anchor.href.replace( rhash, "" );
        locationUrl = location.href.replace( rhash, "" );

        // decoding may throw an error if the URL isn't UTF-8 (#9518)
        try {
            anchorUrl = decodeURIComponent( anchorUrl );
        } catch ( error ) {}
        try {
            locationUrl = decodeURIComponent( locationUrl );
        } catch ( error ) {}

        return anchor.hash.length > 1 && anchorUrl === locationUrl;
    },

    _processTabs: function() {
        var that = this;

        this.tablist = this._getList()
            .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
            .attr( "role", "tablist" );

        this.tabs = this.tablist.find( "> li:has(a[href])" )
            .addClass( "ui-state-default ui-corner-top" )
            .attr({
                role: "tab",
                tabIndex: -1
            });

        this.anchors = this.tabs.map(function() {
                return $( "a", this )[ 0 ];
            })
            .addClass( "ui-tabs-anchor" )
            .attr({
                role: "presentation",
                tabIndex: -1
            });

        this.panels = $();

        this.anchors.each(function( i, anchor ) {
            var selector, panel, panelId,
                anchorId = $( anchor ).uniqueId().attr( "id" ),
                tab = $( anchor ).closest( "li" ),
                originalAriaControls = tab.attr( "aria-controls" );

            // inline tab
            if ( that._isLocal( anchor ) ) {
                selector = anchor.hash;
                panel = that.element.find( that._sanitizeSelector( selector ) );
            // remote tab
            } else {
                panelId = that._tabId( tab );
                selector = "#" + panelId;
                panel = that.element.find( selector );
                if ( !panel.length ) {
                    panel = that._createPanel( panelId );
                    panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
                }
                panel.attr( "aria-live", "polite" );
            }

            if ( panel.length) {
                that.panels = that.panels.add( panel );
            }
            if ( originalAriaControls ) {
                tab.data( "ui-tabs-aria-controls", originalAriaControls );
            }
            tab.attr({
                "aria-controls": selector.substring( 1 ),
                "aria-labelledby": anchorId
            });
            panel.attr( "aria-labelledby", anchorId );
        });

        this.panels
            .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
            .attr( "role", "tabpanel" );
    },

    load: function( index, event ) {
        index = this._getIndex( index );
        var that = this,
            tab = this.tabs.eq( index ),
            anchor = tab.find( ".ui-tabs-anchor" ),
            panel = this._getPanelForTab( tab ),
            eventData = {
                tab: tab,
                panel: panel
            };

        // not remote
        if ( that._isLocal( anchor[ 0 ] ) ) {
            return;
        }

        this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );

        // support: jQuery <1.8
        // jQuery <1.8 returns false if the request is canceled in beforeSend,
        // but as of 1.8, $.ajax() always returns a jqXHR object.
        if ( this.xhr && this.xhr.statusText !== "canceled" ) {
            tab.addClass( "ui-tabs-loading" );
            panel.attr( "aria-busy", "true" );

            this.xhr
                .success(function( response ) {
                    // support: jQuery <1.8
                    // http://bugs.jquery.com/ticket/11778
                    setTimeout(function() {
                        panel.html( response );
                        that._trigger( "load", event, eventData );
                    }, 1 );
                })
                .complete(function( jqXHR, status ) {
                    // support: jQuery <1.8
                    // http://bugs.jquery.com/ticket/11778
                    setTimeout(function() {
                        if ( status === "abort" ) {
                            that.panels.stop( false, true );
                        }

                        tab.removeClass( "ui-tabs-loading" );
                        panel.removeAttr( "aria-busy" );

                        if ( jqXHR === that.xhr ) {
                            delete that.xhr;
                        }
                    }, 1 );
                });
        }
    }

}); 

slavap commented May 20, 2015

I've backported the latest @gabrielschulhof fix to jqm 1.4.5, so now tabs is working just fine.
_processTabs() and load() are just copies of original 1.4.5 code with isLocal() replaced by that._isLocal()

$.widget( "ui.tabs", $.ui.tabs, {

    _isLocal: function( anchor ) {
        var path, baseUrl, absUrl;

        if ( $.mobile.ajaxEnabled ) {
            path = $.mobile.path;
            baseUrl = path.parseUrl( $.mobile.base.element.attr( "href" ) );
            absUrl = path.parseUrl( path.makeUrlAbsolute( anchor.getAttribute( "href" ),
                baseUrl ) );

            return ( path.isSameDomain( absUrl.href, baseUrl.href ) &&
                absUrl.pathname === baseUrl.pathname );
        }

        var rhash = /#.*$/;
        var anchorUrl, locationUrl;

        anchorUrl = anchor.href.replace( rhash, "" );
        locationUrl = location.href.replace( rhash, "" );

        // decoding may throw an error if the URL isn't UTF-8 (#9518)
        try {
            anchorUrl = decodeURIComponent( anchorUrl );
        } catch ( error ) {}
        try {
            locationUrl = decodeURIComponent( locationUrl );
        } catch ( error ) {}

        return anchor.hash.length > 1 && anchorUrl === locationUrl;
    },

    _processTabs: function() {
        var that = this;

        this.tablist = this._getList()
            .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
            .attr( "role", "tablist" );

        this.tabs = this.tablist.find( "> li:has(a[href])" )
            .addClass( "ui-state-default ui-corner-top" )
            .attr({
                role: "tab",
                tabIndex: -1
            });

        this.anchors = this.tabs.map(function() {
                return $( "a", this )[ 0 ];
            })
            .addClass( "ui-tabs-anchor" )
            .attr({
                role: "presentation",
                tabIndex: -1
            });

        this.panels = $();

        this.anchors.each(function( i, anchor ) {
            var selector, panel, panelId,
                anchorId = $( anchor ).uniqueId().attr( "id" ),
                tab = $( anchor ).closest( "li" ),
                originalAriaControls = tab.attr( "aria-controls" );

            // inline tab
            if ( that._isLocal( anchor ) ) {
                selector = anchor.hash;
                panel = that.element.find( that._sanitizeSelector( selector ) );
            // remote tab
            } else {
                panelId = that._tabId( tab );
                selector = "#" + panelId;
                panel = that.element.find( selector );
                if ( !panel.length ) {
                    panel = that._createPanel( panelId );
                    panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
                }
                panel.attr( "aria-live", "polite" );
            }

            if ( panel.length) {
                that.panels = that.panels.add( panel );
            }
            if ( originalAriaControls ) {
                tab.data( "ui-tabs-aria-controls", originalAriaControls );
            }
            tab.attr({
                "aria-controls": selector.substring( 1 ),
                "aria-labelledby": anchorId
            });
            panel.attr( "aria-labelledby", anchorId );
        });

        this.panels
            .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
            .attr( "role", "tabpanel" );
    },

    load: function( index, event ) {
        index = this._getIndex( index );
        var that = this,
            tab = this.tabs.eq( index ),
            anchor = tab.find( ".ui-tabs-anchor" ),
            panel = this._getPanelForTab( tab ),
            eventData = {
                tab: tab,
                panel: panel
            };

        // not remote
        if ( that._isLocal( anchor[ 0 ] ) ) {
            return;
        }

        this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );

        // support: jQuery <1.8
        // jQuery <1.8 returns false if the request is canceled in beforeSend,
        // but as of 1.8, $.ajax() always returns a jqXHR object.
        if ( this.xhr && this.xhr.statusText !== "canceled" ) {
            tab.addClass( "ui-tabs-loading" );
            panel.attr( "aria-busy", "true" );

            this.xhr
                .success(function( response ) {
                    // support: jQuery <1.8
                    // http://bugs.jquery.com/ticket/11778
                    setTimeout(function() {
                        panel.html( response );
                        that._trigger( "load", event, eventData );
                    }, 1 );
                })
                .complete(function( jqXHR, status ) {
                    // support: jQuery <1.8
                    // http://bugs.jquery.com/ticket/11778
                    setTimeout(function() {
                        if ( status === "abort" ) {
                            that.panels.stop( false, true );
                        }

                        tab.removeClass( "ui-tabs-loading" );
                        panel.removeAttr( "aria-busy" );

                        if ( jqXHR === that.xhr ) {
                            delete that.xhr;
                        }
                    }, 1 );
                });
        }
    }

}); 

arschmitz added a commit to arschmitz/jquery-mobile that referenced this issue Jun 9, 2015

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727
@arschmitz

This comment has been minimized.

Show comment
Hide comment
@arschmitz

arschmitz Jun 29, 2015

Member

This is already fixed on 1.5-dev

Member

arschmitz commented Jun 29, 2015

This is already fixed on 1.5-dev

@arschmitz arschmitz closed this Jun 29, 2015

arschmitz added a commit to arschmitz/jquery-mobile that referenced this issue Jul 4, 2016

Tabs: Override _isLocal() to account for Ajax nav
We cannot compare hrefs to location.href

Fixes gh-7169
Fixes gh-7725
Closes gh-7727
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment