Implement data-cache=false attribute on pages. #1554

Closed
scottjehl opened this Issue May 3, 2011 · 34 comments

Projects

None yet
@scottjehl
Contributor

The following live binding should enforce that a page with data-cache=false attribute will always be re-requested via ajax, rather than reusing a local page in the DOM:

$( ":jqmData(role='page'):jqmData(cache='false')" ).live( "pagehide", function(){  $(this).remove(); });

This should also be added as a page plugin option called "cache".

@scottjehl scottjehl was assigned May 3, 2011
@jfeyen
jfeyen commented May 10, 2011

The code provided works for all circumstances except when data-cache=false is set on the initial page (first page), navigate into the application and then go back to the initial page. This will result in a blank white page rather than a request for the initial page.

The following will remove all but the first page requests:
$( ":jqmData(role='page'):jqmData(cache='false'):gt(0)" ).live( "pagehide", function(){ $(this).remove(); });

I am not sure if this is technically an issue with the jQuery Mobile framework or not. I had previously implemented something like this for Alpha 3 but hand to add the exclusion of the first page.

@Mark-H
Mark-H commented May 11, 2011

I'm confused - is this something that will be added to JQM in a future release, or something that can be used as is when setting data-cache="false" on a data-role="page" div?

@hiroprotagonist

A little modified version of ara.t.howards plugin: https://gist.github.com/881817
This will also render a backbutton.

(function($,undefined) {

$( ":jqmData(role='page'):jqmData(cache='false')" )
    .live(
            "pagehide",
            function() {
                $(this).remove();
            });

$( ":jqmData(role='page'):jqmData(cache='false')" )
    .live( 
            "pagebeforecreate",
            function() {
                var header = $(this).find( ":jqmData(role='header')" );
                if(header.jqmData( "backbtn" ) !== false && header.find("jqmData(rel='back')").size() == 0) {
                    header.prepend($('<a href="#" data-icon="back" data-rel="back">Back</a>'));
                }
            });

}) (jQuery)

@jhaag75
jhaag75 commented May 11, 2011

jfeyen points out that it doesn't work if applied to the first page. I was able to replicate this.

@StevenBlack
Contributor

Note that the first page typically comes with the original payload which includes jQuery, jQuery Mobile, the css files, custom scripts, and so on.

So yes, for some practical reasons, you don't want to scrub the first page from the DOM. There's no framework-level "fix" I can imagine that would ever support this. @jfeyen is on the right track here.

@scottjehl
Contributor

I think we may be able to support @data-cache=false on the initial page, since it should contain a data-role=page element like any other (and upon requesting via changePage, jqm will know to inject just that portion of the markup while ignoring the rest, like any page request). I guess the trick would be that we don't put a url-based data-url on the initial page, but rather a data-url of "jqm-home". When navigating back to that page, we could re-request the initial location.href if it has a data-cache false attr.

Just thinking it's probably doable.

@StevenBlack
Contributor

The overall Pull request is here, of which 29acda3 is part: #1713

@StevenBlack
Contributor

Some notes on this: Major blocker right now is we have no structure tying together the pages created by the listview widget. There is simply no current way to know if a page spawned subpages, and no way to tie the sub pages back to the parent list item.

Until we can reliably logically link these, this issue appears to be is unresolvable.

@StevenBlack
Contributor

Somebody please change the tag on this issue. Thanks.

@wietsevenema
Contributor

Trying to think out of the box here; isn't the best solution here to never cache remote pages?

There are several excellent ways for a server to signal cacheable pages using proper HTTP headers (expires on, cache-control, etc.). The key point being that the browser probably already handles this for us.

So shouldn't we document the ways to mark pages cacheable using HTTP-headers using several popular server side languages (php, rails, etc) and just disable the caching of remote pages altogether?

@daz
daz commented Jun 22, 2011

It's not that kind of cache, it's client-side. The official home for this issue is #1555

@wietsevenema
Contributor

Thanks, I'll repost there.

@scottjehl
Contributor

Okay, I've created a new branch for this issue:
https://github.com/jquery/jquery-mobile/tree/disable-ajax-dom-caching

The following commit is a step towards making this functionality the default. Please check it out give feedback on the implementation. Thanks
2265330

Commit msg:

Added "ajaxDomCaching" option to the page plugin, which defaults to false. This is also available on page divs via the attribute data-ajax-dom-caching.

When set to false, pages that are requested via Ajax (not local multi-page pages) will be removed from the DOM after pagehide, allowing for the DOM to have usually 1 or 2 pages in it at any given time.

Re-requests of that page will pass through the browser's native cache handling like any request, so the server-side can inform the browser whether it should re-fetch pages remotely or pull them from its cache. Cache amounts vary on mobile devices, so setting this to true may prevent requests from going out, even when cache settings are configured to do the same.

Pages that generate sub-pages, such as those containing nested listviews, are removed as a group after the parent page is hidden and none of its sub-pages are active. We might investigate a more aggressive approach to this, where the group is removed upon leaving any of the sub-pages as well (as long as the parent page and no subpages are active), however, in testing I found that after doing this, browsing back to a subpage didn't cause the parent page to re-request and generate all subpages, so for now, I've left it using a simpler mechanism.

@globalmatt
Contributor

Great work Scott. This looks like the best of both worlds! Native browser caching, while also maintaining control over the pages that jQM caches.

Just to clarify: The data-ajax-dom-caching attribute will replace the data-cache attribute, and will default to false instead of true. Is that correct?

Also, with this new arrangement, to prevent a specific mobile page from ever being cached, you'd need to use the HTTP Expires and Cache-Control headers, yes?

@hiroprotagonist

Hey Scott, i like it. It works for me. Thanks!

@wietsevenema
Contributor

Thanks Scott, this is a pragmatic approach. Until we know more about the behaviour of mobile browsers with regard to HTTP caching headers it's good to have a workaround in jqm.

@jfeyen
jfeyen commented Jul 14, 2011

Thanks Scott, this is a step in the right direction that’s for sure. I have two comments / thoughts.

  1. Have you thought of a way to remove the initial requested page from the DOM so that it is refreshed new? The initial page of my application contains a listview with counts of new items within that view, for example new messages. The problem I run into is once I have read a message and returned to the menu listview, the counts are wrong (because this page is not removed from the DOM.
  2. When enabling data-ajax-dom-caching attribute on all pages (preferred for highly dynamic applications to reduce memory load on the device), you will lose the back buttons reverse transition. The application itself will transition forward when the back button is pressed, giving the end user an unpleasant UX.

Thanks again!

@smoothcontract

I've tried a variant of the code posted above to remove the page on hide but have come across an issue with that. I have a data entry form that uses a dialog for populating one of the fields (a date popup) - when running on an iPhone the display of the dialog causes the pagehide event to fire for the data entry form, so it gets removed so I can't close the dialog as there's no page to return to.

So for scenarios like that I think it would be better to have a little more control over when it gets removed - not entirely sure how though.

Could the data-cache attribute be implemented on the calling link to the page instead or perhaps as well as on the page?

@smoothcontract

Okay, I just hacked in support for data-cache attribute on links and this works for me. I can now get a fresh page instance when I link to it but still use dialogs within that page. Hopefully this would be useful to others?

(This code is from the end of the main click handler implementation, line number 3092):

        //use ajax
        var transition = $link.jqmData( "transition" ),
            direction = $link.jqmData( "direction" ),
            reverse = ( direction && direction === "reverse" ) ||
                        // deprecated - remove by 1.0
                        $link.jqmData( "back" ),

            //this may need to be more specific as we use data-rel more
            role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined,
            reload = ($link.attr("data-cache") == "never");

        $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role, reloadPage: reload } );
@hiroprotagonist

hey scott!
i suggest to merge issue #1555 with this one. your current approach is in my opinion the solution for both.

Cheers

@toddparker
Contributor

@smoothcontract If you added data-ajax-dom-caching="true" to the parent form page that opens the picker dialog, that should solve your issue, right?

@johnbender
Contributor

@smoothcontract applying data-ajax-dom-caching to your form page should preserve the values. An example which assumes the root of the project:

/foo.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="js/jquery.js"></script>
    <script src="js"></script>

    <link rel="stylesheet" href="themes/default/"/>
  </head>
  <body>
    <div id="start" data-role="page">
      <a href="bar.html">bar</a>
    </div>
  </body>
</html>

/bar.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="js/jquery.js"></script>
    <script src="js"></script>

    <link rel="stylesheet" href="themes/default/"/>
  </head>
  <body>
    <div id="form" data-role="page" data-ajax-dom-caching="true">
      <div data-role="fieldcontain">
        <label for="name">Text Input:</label>
        <input type="text" name="name" id="name" value=""  />
      </div>

      <a href="baz.html" data-rel="dialog">baz dialog</a>
    </div>
  </body>
</html>

/baz.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="js/jquery.js"></script>
    <script src="js"></script>

    <link rel="stylesheet" href="themes/default/"/>
  </head>
  <body>
    <div id="dialog" data-role="page">
      go backwards!
    </div>
  </body>
</html>

@jfeyen

I'm going to review 2 on your list and possibly open another issue which I'll post here

@johnbender
Contributor

@jfeyen

I could use some clarification. You said

When enabling data-ajax-dom-caching attribute on all pages (preferred for highly dynamic applications to reduce memory load on the device)

Did you mean disabling since that would reduce the memory foot print. Enabling the DOM caching causes the pages to be cached and increases the DOM size over time.

In either case I'm not able to reproduce the transition issue that you highlighted, can you provide an example similar to the one I provided for @smoothcontract above?

Thanks everyone for your help!

@jfeyen
jfeyen commented Jul 26, 2011

Sorry @johnbender I did mean disabling. I have just tried to reproduce the reverse transition issue that I had previously outlined however I have come out empty handed. I may have been dealing with a specific scenario in which was causing the transitions to be lost.

I will create another issue for this in the future if I am able to reproduce.

@johnbender
Contributor

@jfeyen

Thanks much for responding and please do post another issue if you run into something fishy like that. The team appreciates your contribution!

@toddparker
Contributor
@toddparker toddparker closed this Jul 29, 2011
@scottjehl
Contributor

Per suggestions above, should we close this as well?
#1555

On Jul 29, 2011, at 5:49 PM, toddparker wrote:

The ability to cache or not is now landed:
http://jquerymobile.com/blog/2011/07/29/team-update-week-of-july-25th/

Reply to this email directly or view it on GitHub:
#1554 (comment)

@wietsevenema
Contributor

Yeah I think so!

@ahutch
ahutch commented Oct 5, 2011

I know this issue is closed, but I am trying to change the dom-cache/domCache on a page that is already loaded. If I am displaying a confirmation page or a dialog I want previous page cached. Otherwise I want the dynamic contend to be refreshed. I have looked through the code but think that the only place to do this is before creation. Once the page is created its cache state cannot be changed. Is there anything I can do to achieve this without manually removing the element?

@johnbender
Contributor

@ahutch

You should be able to set the domCache property on individual page elements like so:

$( "div#mypage" ).page( 'option', 'domCache', true )

As a side note, the best place to ask these kinds of things is in the irc channel #jquerymobile. Hope that helps!

@ahutch
ahutch commented Oct 6, 2011

@john, thanks for the advice. None of the following seem to have any effect after the page is constructed.

$( "div#mypage" ).page( 'option', 'domCache', true )
$( "div#mypage" ).page( 'option', 'domCache', false )
$("div#mypage").page({ domCache: true })
$("div#mypage").page({ domCache: false })

I am still using 1.0b2 - I was going to post a bug/request but wanted to make sure I was not doing something wrong

In:
$.mobile.loadPage = function ...
..
// when dom caching is not enabled bind to remove the page on hide
if( !page.data("page").options.domCache ){
page.bind( "pagehide.remove", function(){
$(this).remove();
});
}

@johnbender
Contributor

@ahutch

Have you tried with the latest we have made some changes to that code, and fixes for other areas it affects.

<link href="http://code.jquery.com/mobile/latest/jquery.mobile.min.css" rel="stylesheet" type="text/css" />
<script src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
<script src="http://code.jquery.com/mobile/latest/jquery.mobile.min.js"></script>
@ahutch
ahutch commented Oct 6, 2011

I just tried with the rc1 and the latest. I got the same result.

  1. Load JQM page.
  2. Click link to load new page. New page has data-dom-cache="true"
  3. Go to the console and change the cache opt of the page
    $('#new-page').page( 'option', 'domCache', false );
    $('#new-page').page({ domCache: false });
  4. Press the back button to return to the original page
  5. Check the DOM to see if the page has been removed. It is still there.
  6. Tried $('#new-page').jqmData('dom-cache',false)

I want to be able to change the cache option in the pageshow/pagehide where I can see both pages.

@diosney
diosney commented Feb 16, 2013

+1 on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment