• 沒有找到結果。

Fetching and Caching

在文檔中 HTML5 and JavaScript Web Apps (頁 51-57)

Now it’s time to take your page and resource caching to the next level. Much like the approach that jQuery Mobile and similar frameworks use, you can prefetch and cache your pages with concurrent AJAX calls. A few core mobile web challenges highlight the reasons why following this approach makes sense:

Fetching

Prefetching your pages allows users to take the app offline and also eliminates wait‐

ing between navigation actions. Of course, you don’t want to choke the device’s bandwidth when the device comes online, so you need to use this feature sparingly.

Caching

You want a concurrent or asynchronous approach when fetching and caching pages.

Because it’s well supported among devices, you also need to use localStorage, which unfortunately isn’t asynchronous.

AJAX and parsing the response

Using innerHTML() to insert the AJAX response into the DOM is dangerous, and it could be unreliable according to http://martinkou.blogspot.com/2011/05/

alternative-workaround-for-mobile.html. Instead, I recommend a reliable

mechanism for AJAX response insertion and handling concurrent calls (https://

community.jboss.org/people/wesleyhales/blog/2011/08/28/fixing-ajax-on-mobile-devices). You also can leverage some new features of HTML5 for parsing the xhr.re sponseText.

You can build on the code from the slide, flip, and rotate demos by adding some sec‐

ondary pages and linking to them. You can then parse the links and create transitions on the fly.

As you can see, this snippet leverages semantic markup with a link to another page. The child page follows the same node/class structure as its parent. You could take this a step further and use the data-* attribute for page nodes, and the like.

Here is the detail page (child) located in a separate HTML file (/demo2/home-detail.html), which will be loaded, cached, and set up for transition on app load.

<div id="home-page-detail"

class="page">

<h1>Home Page Details</h1>

<p>Here are the details.</p>

</div>

Now take a look at the JavaScript. For simplicity’s sake, I’m leaving any helpers or op‐

timizations out of the code. The code is looping through a specified array of DOM nodes to dig out links to fetch and cache. For the complete source, see https://github.com/

html5e/slidfast/blob/master/slidfast.js#L264.

var fetchAndCache = function() {

// iterate through all nodes in this DOM to //find all mobile pages we care about

var pages = document.getElementsByClassName('page');

for (var i = 0; i < pages.length; i++) { // find all links

var pageLinks = pages[i].getElementsByTagName('a');

for (var j = 0; j < pageLinks.length; j++) { var link = pageLinks[j];

if (link.hasAttribute('href') &&

//'#' in the href tells us that this page is //already loaded in the DOM - and

!(/[\#]/g).test(link.href) &&

The use of the AJAX object ensures proper asynchronous post-processing. In this ex‐

ample, you see the basic use of caching on each request and of providing the cached objects when the server returns anything but a successful (200) response.

function processRequest () {

if (callback) callback(req.responseText,url);

} else {

Unfortunately, because localStorage uses UTF-16 for character encoding, each single byte is stored as 2 bytes, bringing our storage limit from 5MB to 2.6MB total. Fetching and caching these pages/markup outside of the application cache scope allows you to take advantage of all the storage space provided by the device.

With the recent advances in the iframe element with HTML5, you now have a simple and effective way to parse the responseText you get back from an AJAX call. There are plenty of 3,000-line JavaScript parsers and regular expressions that remove script tags and so on. But why not let the browser do what it does best? The next example writes the responseText into a temporary hidden iframe. This uses the HTML5 sandbox attribute, which disables scripts and offers many security features. (See complete source.)

To quote the HTML5 spec: “The sandbox attribute, when specified, en‐

ables a set of extra restrictions on any content hosted by the iframe. Its value must be an unordered set of unique space-separated tokens that are ASCII case-insensitive. The allowed values are allow-forms, allow-same-origin, allow-scripts, and allow-top-navigation. When the attribute is set, the content is treated as being from a unique origin, forms and scripts are disabled, links are prevented from targeting other browsing contexts, and plug-ins are disabled. To limit the damage that can be caused by hostile HTML content, it should be served using the text/html-sandboxed MIME type.”

var getFrame = function() {

var frame = document.getElementById("temp-frame");

if (!frame) { // create frame

frame = document.createElement("iframe");

frame.setAttribute("id", "temp-frame");

frame.setAttribute("name", "temp-frame");

frame.setAttribute("seamless", "");

frame.setAttribute("sandbox", "allow-same-origin");

frame.style.display = 'none';

document.documentElement.appendChild(frame);

}

// load a page

return frame.contentDocument;

};

var insertPages = function(text, originalLink) { var frame = getFrame();

//write the ajax response text to the frame and let //the browser do the work

frame.write(text);

//now we have a DOM to work with

var incomingPages = frame.getElementsByClassName('page');

var pageCount = incomingPages.length;

for (var i = 0; i < pageCount; i++) {

//the new page will always be at index 0 because //the last one just got popped off the stack with //appendChild (below)

var newPage = incomingPages[0];

//stage the new pages to the left by default newPage.className = 'page stage-left';

//find out where to insert

var location = newPage.parentNode.id ==

try {

// mobile safari will not allow nodes to be transferred from one // DOM to another so we must use adoptNode()

document.getElementById(location).

appendChild(document.adoptNode(newPage));

} catch(e) {

// todo graceful degradation?

} } };

The target browser (Mobile Safari) correctly refuses to implicitly move a node from one document to another. An error is raised if the new child node was created in a different document. So this example uses adoptNode, and all is well.

So why iframe? Why not just use innerHTML? Even though innerHTML is now part of the HTML5 spec, it is a dangerous practice to insert the response from a server (evil or good) into an unchecked area. innerHTML has also been noted to fail intermittently on iOS (just do a Google search on “ios innerhtml” to see the latest results) so it’s best to have a good workaround when the time comes.

Figure 3-11 shows the latest performance test from http://jsperf.com/ajax-response-handling-innerhtml-vs-sandboxed-iframe. It shows that this sandboxed iframe approach is just as fast, if not faster than innerHTML on many of today’s top mobile browsers. Keep in mind the measurement is operations per second, so higher scores are better.

Figure 3-11. HTML5 iframe versus innerHTML() performance

在文檔中 HTML5 and JavaScript Web Apps (頁 51-57)

相關文件