views:

142

answers:

2

Basically it breaks down like this:

  • A master template page is loaded when one visits the application.
  • Links are handled by an onclick handler, which reload relevant portions of the master via a jQuery ajax call, trigger other events, etc.
  • After the initial onclick handler completes, it then calls two or more ajax callbacks which update other portions of the page.

The problem:

  • The final callback reads CakePHP's flash message command $session->flash(), and displays the result in a simple javascript popup, who's div tag is created via $('body').prepend(). Once the popup is closed, the element that was created is .remove()'ed from the DOM.
  • When the user clicks a link and the flash message callback is ran for the next page, the CakePHP flash message data never got deleted, and it redisplays the message. This will repeat anywhere from 2-5 times as one continues to click pages, or until a little time has passed, and then finally delete the message from the PHP session.
  • When visiting the ajax link directly, the flash data is displayed once, and then deleted after the page is reloaded.

This has been boggling my mind for the last week. I've trimmed out every other unrelated thing except the callback itself, and it still displays the same behavior. Frustrating.

The code is quite simple, and looks like so:

The layout page which gets handed back after an onclick call.

<?php echo $content_for_layout ?>

<script>
  $(window).ready(function() {
    page_load_callback();
  });
</script>

The javascript.

function page_load_callback() {

  $.ajaxSetup({
        cache: true // We want our client to cache stuff, and override this in PHP.
  });

  $.ajax({
        cache: false, // Force this to not cache via jQuery
        beforeSend: function() {
            $('body').prepend('<div id="flash_message_popup"></div>');
        },
        url: flash_message_popup_link, // global variable set in default.ctp
        success: function(data) {
            $('#flash_message_popup').html(data);
            if ($('#flash_message_popup').is(':empty')) {
                $('#flash_message_popup').remove();
            } else {
                create_popup('flash_message_popup');
            }
        },
        error: function(a, b, c) {
            $('#flash_message_popup').remove();
        }
    });
}

function create_popup(tag) {
    if (editing_hall == true) {
        return false;
    }
    var selector = '#' + tag;
    $(selector).attr('style', 'position: absolute; display: none; top: 5; left: 5; z-index: 100;');
    $(selector).slideDown('slow');
}

The *.ctp file.

<?php if ($message = $session->flash()): ?>
<span id="flash_data">
    <div class="flash_message"><?php echo $message; ?></div>
    <div class="btn_large bottom_round"><a name="" onclick="$('#flash_message_popup').slideUp('slow', function() { $('#flash_message_popup').remove(); });">close</a></div>
</span>
<?php endif; ?>

The controller method.

function flash_message_popup() {
    $expires = 60*60*24*30*7;
    header('Expires: '.gmdate('D, d M Y H:i:s', time()-$expires) . ' JST'); // minus for past  
    $this->layout = 'ajax';

    $this->render('../elements/flash_message_popup');
}

Sessions get stored in a database, and haven't proven to be a problem up until this specific instance. I'm not sure if this is a browser caching problem (I shouldn't be? I am specifically not caching via both PHP and jQuery), a quirky session problem, a timing issue, something going haywire in my javascript, or what. I've tried adding a redundant callback which deletes the session in question with $this->Session->delete('Messages.flash'); with no luck as well.

If anyone has any advice I'd love to hear it. I am plum out of ideas.

Edit: I've also checked the Apache logs, and the callback is definitely getting called by the ajax query, but not deleting the session message. Only after I manually call it does it delete.

A: 

I think what's happening is that AJAX is doing stuff with the Session flash before the PHP has reset it. Normally, it is set on success or failure in the CRUD methods, e.g.

    if ($this->Invoice->save($this->data)) {
        $this->Session->setFlash(__('The Invoice has been saved', true));
        $this->redirect(array('action' => 'index'));
    } else {
        $this->Session->setFlash(__('The Invoice could not be saved. Please, try again.', true));
    }

Because you are doing AJAX stuff, it may not be reaching that code before the flash message is displayed. In this situation, I would either reset it on entry to the method or use an AJAX call to reset it after you have displayed it.

Leo
Thanks for the response Leo. Right now the onclick calls go to a standard CRUD based /controller/action, which in turn calls setFlash based on the response, and then hands back a standard action.ctp file. This was all tested and working fine as standard page jumps. After we AJAX'ed it all, we added a callback which runs **after** the /controller/action's data is returned (no redirects). In theory this means that the next time you call $session->flash() it will print, and immediately delete that data. Unfortunately it's not immediately deleting it, and subsequent actions recall it again.
bojo
Not enough characters to comment! On a whim I added an explicit $this->Session->delete to afterFilter() to no avail, and have also used standard sessions (not Message.flash) with no luck either.
bojo
A: 

The problem appears to be too many AJAX requests conflicting with database sessions, so the timing is completely off, and the database data seems to be getting overwritten with older session data. I will leave it at that and close this question.

bojo

related questions