views:

182

answers:

7

With a single page app, where I change the hash and load and change only the content of the page, I'm trying to decide on how to manage the JavaScript that each "page" might need.

I've already got a History module monitoring the location hash which could look like domain.com/#/company/about, and a Page class that will use XHR to get the content and insert it into the content area.

function onHashChange(hash) {
    var skipCache = false;
    if(hash in noCacheList) {
        skipCache = true;
    } 
    new Page(hash, skipCache).insert();
}


// Page.js
var _pageCache = {};
function Page(url, skipCache) {

    if(!skipCache && (url in _pageCache)) {
        return _pageCache[url];
    }
    this.url = url;
    this.load();

}

The cache should let pages that have already been loaded skip the XHR. I also am storing the content into a documentFragment, and then pulling the current content out of the document when I insert the new Page, so I the browser will only have to build the DOM for the fragment once.

Skipping the cache could be desired if the page has time sensitive data.

Here's what I need help deciding on: It's very likely that any of the pages that get loaded will have some of their own JavaScript to control the page. Like if the page will use Tabs, needs a slide show, has some sort of animation, has an ajax form, or what-have-you.

What exactly is the best way to go around loading that JavaScript into the page? Include the script tags in the documentFragment I get back from the XHR? What if I need to skip the cache, and re-download the fragment. I feel the exact same JavaScript being called a second time might cause conflicts, like redeclaring the same variables.

Would the better way be to attach the scripts to the head when grabbing the new Page? That would require the original page know all the assets that every other page might need.

And besides knowing the best way to include everything, won't I need to worry about memory management, and possible leaks of loading so many different JavaScript bits into a single page instance?

A: 

I have never built such a site so I don't know if that is nbest practice, but I would put some sort of control information (like a comment or a HTTP header) in the response, and let the loader script handle redundancy/dependency cheching and adding the script tags to the header.

Tgr
A: 

Do you have control over those pages being loaded? If not, I would recommend inserting the loaded page in an IFrame.

Taking the page scripts out of their context and inserting them in the head or adding them to another HTML element may cause problems unless you know exactly how the page is build.

If you have full control of the pages being loaded, I would recommend that you convert all your HTML to JS. It may sound strange but actually, a HTML->JS converter is not that far away. You could start of with Pure JavaScript HTML Parser and then let the parser output JS code, that builds the DOM using JQuery for example.

I was actually about to go down that road for a while ago on a webapp that I started working on, but now I handed it over to a contractor who converted all my pure JS pages into HTML+JQuery, whatever makes his daily work productive, I dont care, but I was really into that pure JS webapp approach and will definitely try it.

Ernelli
I do have control over the pages. I was considering a JS parser. But I'll already be creating the pages in PHP, and since PHP is much faster than JS, I was just going to return the HTML.
seanmonstar
+1  A: 

If I understand the case correctly, you are trying to take a site that currently has pages already made for normal navigation, and you want to pull them down via ajax, to save yourself the page-reload?

Then, when this happens, you need to not reload the script tags for those pages, unless they're not loaded onto the page already?

If that is the case, you could try to grab all the tags from the page before inserting the new html into the dom:

//first set up a cache of urls you already have loaded.
var loadedScripts = [];

//after user has triggered the ajax call, and you've received the text-response
function clearLoadedScripts(response){
   var womb = document.createElement('div');
   womb.innerHTML = response;
   var scripts = womb.getElementsByTagName('script');
   var script, i = scripts.length;
   while (i--) {
      script = scripts[i];
      if (loadedScripts.indexOf(script.src) !== -1) {
          script.parentNode.removeChild(script);
      }
      else {
          loadedScripts.push(script.src);
      }
   }

   //then do whatever you want with the contents.. something like:
   document.body.innerHTML = womb.getElementsByTagName('body')[0].innerHTML);

}
This is a pretty awesome implementation...
gnarf
A: 

To me it sounds like you are creating a single-page app from the start (i.e. not re-factoring an existing site).

Several options I can think of:

  1. Let the server control which script tags are included. pass a list of already-loaded script tags with the XHR request and have the server sort out which additional scripts need to be loaded.
  2. Load all scripts before-hand (perhaps add them to the DOM after the page has loaded to save time) and then forget about it. For scripts that need to initialize UI, just have each requested page call include a script tag that calls a global init function with the page name.
  3. Have each requested page call a JS function that deals with loading/caching scripts. This function would be accessible from the global scope and would look like this: require_scripts('page_1_init', 'form_code', 'login_code') Then just have the function keep a list of loaded scripts and only append DOM script tags for scripts that haven't been loaded yet.
Daniel Beardsley
A: 

You could use a script loader like YUI Loader, LAB.js or other like jaf

Jaf provides you with mechanism to load views (HTML snippets) and their respective js, css files to create single page apps. Check out the sample todo list app. Although its not complete, there's still a lot of useful libraries you can use.

naikus
A: 

Oh boy are you in luck. I just did all of this research for my own project.

1: The hash event / manager you should be using is Ben Alman's BBQ: http://benalman.com/projects/jquery-bbq-plugin/

2: To make search engines love you, you need to follow this very clear set of rules: http://code.google.com/web/ajaxcrawling/docs/specification.html

I found this late and the game and had to scrap a lot of my code. It sounds like you're going to have to scrap some too, but you'll get a lot more out of it as a consequence.

Good luck!

Matrym
thanks for that google.com ajaxcrawling article. I was looking for that.
seanmonstar
NP! Please mark this as the answer if it's what you were looking for.
Matrym
i was looking for the link myself, but i dont think this answers the question, sorry. i'm more interested in the management of Javascript required by various pages, handling newly declared variables, attached and detaching events, and memory management.
seanmonstar
A: 

Personally, I would transmit JSON instead of raw HTML:

{
    "title": "About",
    "requires": ["navigation", "maps"],
    "content": "<div id=…"
}

This lets you send metadata, like an array of required scripts, along with the content. You'd then use a script loader, like one of the ones mentioned above, or your own, to check which ones are already loaded and pull down the ones that aren't (inserting them into the <head>) before rendering the page.

Instead of including scripts inline for page-specific logic, I'd use pre-determined classes, ids, and attributes on elements that need special handling. You can fire an "onrender" event or let each piece of logic register an on-render callback that your page loader will call after a page is rendered or loaded for the first time.

Sidnicious