Austin
Austin

Reputation: 1627

jQuery mobile multipage wait for ajax to return data before switching pages

I'm using jQuery mobile in a multi page form. When the user clicks the button labeled Next Page I want to run an AJAX request. Depending on the result of the AJAX request, I would want to prevent the user from accessing the next page.

I believe the problem I'm running into is that my AJAX request takes longer to reply than the next page event. Therefore, it's letting the user enter the next page without letting the AJAX request finish.

At least I think that's what is happening. Has anyone run into this before or know of a solution to this problem?

HTML:

<div id="page1" data-role="page">
    <div data-role="header" data-position="fixed" data-theme="d">
        <a href="#page2" data-role="button" id="btnPage2" class="ui-btn-right" data-icon="arrow-r">Next Page</a>
    </div>
    <div role="main" class="ui-content">
        <label for="testVal">Test Val:</label>
        <input type="text" id="testVal" name="testVal"/>
    </div>
</div>
<div id="page2" data-role="page">
    <div role="main" class="ui-content">

    </div>
</div> 

jQuery:

$('#btnPage2').click(function(){
    var isValid = '';
    var aValue = $('#testVal').val();
    $.ajax({
        type: "POST",
        url: "php-file.php",
        data: {"something":aValue },
        success: function(data){
            var json = $.parseJSON(data);
            if(json.hasOwnProperty('error')){
                console.log("the user is not allowed on page 2");
                isValid = false;
            }else{
                isValid = true;
            }
        }
    });
    if(!isValid){
        alert('sorry you can not enter page 2');
        return false;
    }
});

In the code sample above when I click the Next Page button (i.e. #btnPage2) it allows me to access page 2 even though when I check the console log and I see "the user is not allowed on page 2" .

UPDATE:

I tried adjusting the code to the following. It kind of works but, only when the user is not allowed to access page2. e.preventDefault is letting ajax finish but, I can't figure out a way to force a change to page2. If the user is allowed access to page2 the line

$(":mobile-pagecontainer").pagecontainer("change", "#page2");

seems to be causing the problem...it just creates an endless loop of calling the pageChange event. I thought that is the way to force a page change to page2 but I think i'm wrong and I cannot figure out how to force it to change to page2

$(document).bind('pagebeforechange', function(e, data) {
    var to = data.toPage,
        from = data.options.fromPage;

    if (typeof to === 'string') {
        var u = $.mobile.path.parseUrl(to);
        to = u.hash || '#' + u.pathname.substring(1);
        if (from) from = '#' + from.attr('id');

        if (from === '#page1' && to === '#page2') {
        e.preventDefault();
        var aValue = $('#testVal').val();
            $.ajax({
                type: "POST",
                url: "php-file.php",
                data: {"something":aValue },
                cache: false,
                success: function(data){
                    var json = $.parseJSON(data);
                    if(json.hasOwnProperty('canNotPass')){
                        alert('Sorry you can not view page 2');
                    }else{
                        ///  if json does not contain canNotPass then continue to page2 
                        $(":mobile-pagecontainer").pagecontainer("change", "#page2");
                    }
                },
                beforeSend: function() {
                    $.mobile.loading("show");
                },
                complete: function() {
                    $.mobile.loading("hide");
                }
            });   
        }            
    }
});

UPDATE #2

I tried implementing @deblocker fix and I'm still coming up with the same problem. When I run the following code in a situation where ajax sets canNotPass to true the code never seems to get to the point where is tests the value of canNotPass. Therefore, it never gets to set e.preventDefault and the user is allowed access to page2.

I think the solution has something to do with setting up a deferred object which would allow time for the ajax to finish before the page change occurs. Only problem is I have know idea how to set up/work with deferred objects.

JSFiddle

Upvotes: 1

Views: 912

Answers (2)

Austin
Austin

Reputation: 1627

Thanks Omar and deblocker for spending so much time on this issue. Unfortunately, I was unable to implement your suggestions without interfering with the rest of my page interactions. The only solution I came up with that works is listed below. Unfortunately, this solution does not use the built in hash functions that handle the page change event. I just use a normal button and assign click listener to it. A bit clunky but it seems to be the only way it can work in my situation.

<button id="btnNewPage">Go To Page 2</button>

$(document).ready(function(){
    $("#btnPageNew").click(function(){
        var testVal = $('#testVal').val();
        var canNotPass = promiseTest(testVal);
            canNotPass.done(function(data){
                var json = $.parseJSON(data);
                    console.log(json);
                    if(json.hasOwnProperty('error')){
                        // user can not access the next page
                        console.log('can not pass');
                        $.mobile.activePage.find('.ui-btn-active').removeClass('ui-btn-active').blur();
                    }else{
                        // allow user to access the next page
                        $(":mobile-pagecontainer").pagecontainer("change", "#page2");
                    }
            });


    });
});

function promiseTest(aValue){
    return $.ajax({
                type: "POST",
                url: "php-file.php",
                data: {"something":aValue  },
                cache: false
            });
}

Upvotes: 0

deblocker
deblocker

Reputation: 7705

Use a button instead of an anchor:

<button id="btnPage2" class="ui-btn-right ui-icon-arrow-r">Next Page</a>

...and navigate by code inside the success handler:

$(":mobile-pagecontainer").pagecontainer("change", "#page2");

BTW, you could also use some other handlers from ajax:

$.ajax({
  type:...
  url:...
  data:...
  success: ...
  beforeSend: function() {
    $.mobile.loading("show");
  },
  complete: function() {
    $.mobile.loading("hide");
  },
  error: function (request,error) {
    alert('sorry you can not enter page 2');
});

UPDATE:

Following Omar's suggestion, inside the first part of the pagecontainerbeforechange handler you should break the navigation, as you are already on the half way to page2. So, you don't need anymore to tell to pagecontainer to switch to page2 inside the pagecontainerbeforechange event here. You should simply keep your ajax request and your canNotPass flag inside $('#btnPage2').click().

DEMO:

$(document).on("pagecontainerbeforechange", function(e, ui) {
  var from = "#" + $(":mobile-pagecontainer").pagecontainer("getActivePage").prop("id");
  var to = ui.toPage;	
  if (typeof ui.toPage == "string") {
    var u = $.mobile.path.parseUrl(to);
    to = u.hash || "#" + u.pathname.substring(1);
    if (from === "#page-one" && to === "#page-two") {
      var canNotPass = !$("#canPass").prop("checked");
      if(canNotPass) {
        e.preventDefault();
        $.mobile.activePage.find('.ui-btn-active').removeClass('ui-btn-active').blur();
      }
    }
  }
});
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
  <link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.css">
  <script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
  <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.js"></script>
</head>
<body>
  <div data-role="page" id="page-one">
    <div data-theme="a" data-role="header" data-position="fixed">
      <h1>First Page</h1>
      <a href="#page-two" class="ui-btn-left">Next</a>
    </div>
    <div data-role="content">
      <label for="canPass">Can Navigate to Page 2 ?</label>
      <input data-role="flipswitch" name="canPass" id="canPass" data-on-text="Yes" data-off-text="No" type="checkbox">
    </div>
    <div data-theme="a" data-role="footer" data-position="fixed">
      <h1>Footer</h1>
    </div>
  </div>
  <div data-role="page" id="page-two">
    <div data-theme="a" data-role="header" data-position="fixed">
      <h1>Second Page</h1>
      <a href="#page-one" class="ui-btn-left">Back</a>
    </div>
    <div data-role="content">
    </div>
    <div data-theme="a" data-role="footer" data-position="fixed">
      <h1>Footer</h1>
    </div>
  </div>
</body>
</html>

EDIT2: (Credits: Omar) - Here is how to trap the direct navigation to that "protected" page

$(document).on("pagecontainerbeforechange", function(e, ui) {
  /* in case user enter foo.com/#page2 directly */
  if (typeof ui.toPage === "object" && ui.absUrl === undefined && ui.toPage[0].id == "page2") {
    /* make ajax call */
    var ajaxCall = true;
    if (ajaxCall) {
      $.noop
    } else {
      ui.toPage = "#noAccessPage"
    }
  }

  /* internal navigation from page1 to page2 */
  if (typeof ui.toPage === "string" && ui.absUrl !== undefined && $.mobile.path.parseUrl(ui.absUrl).hash == "#page2") {
    /* make ajax call */
    var ajaxCall = true;
    if (ajaxCall) {
      $.noop
    } else {
      ui.toPage = "#noAccessPage"
    }
  }
});

Upvotes: 2

Related Questions