Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulating frames with jQuery load() and pushState()

tl;dr summary: jQuery's load() method called like:

$('#foo').load('similar.html #foo')

results in DOM structure of:

<div id="foo">
  <div id="foo">…</div>
</div>

What's the Right way to swap out a portion of a page with the equivalent part in a similar page using jQuery that does not double up the wrapper?


I want to create a site that:

  1. Has consistent site 'chrome' around all pages (header, nav sidebar, search box, etc.).
  2. Swaps out the 'content' of the site during navigation (all chrome is persistent).
  3. Provides the History and Bookmark experience of standard browsing.

enter image description here

I don't want to use frames due to #3, and also because I want the persistent content to overlap the dynamic content (note the persistent search results in pink overlaying the content).

So, every page in the site will have structure similar to the following abbreviated shell:

<html><head><title>NAME</title>…</head><body>
  <div id="chrome">…</div>
  <div id="content">…</div>
</body></html>

Using jQuery I can intercept the navigation and search links and use AJAX to load the page and replace just the content. And I can use history.pushState() to make the address bar update in a navigable and bookmarkable way:

$('#nav a').click(function(e){
  var url = this.href;
  $('#content').load(url+" #content",function(html){
    var title = /<title>(.+?)<\/title>/i.exec(html)[1];
    history.pushState({},title,url);
    $('title').html(title);
  });
  e.preventDefault();
});

Additional code is needed to properly handle moving forward and backward through the history; these are irrelevant to the question.

However, the above code results in the DOM having:

<div id="content">
  <div id="content">contents from other page</div>
</div>

Is there a better way to prevent this illegal doubling of id other than changing all my markup to…

<div id="content-wrap"><div id="content">…</div></div>

and the JS to…

$('#content-wrap').load(url+" #content",…);
like image 810
Phrogz Avatar asked Nov 04 '22 09:11

Phrogz


1 Answers

Instead of using .load() which automatically loads the server response into the selected element (using .html()), you can use a $.get() request and filter the server response to only include the innerHTML of the response:

$.get('similar.html', function (serverResponse) {
    var filter = $(serverResponse).filter('#foo').html();
    $('#foo').html(filter);
});

Here is a demo: http://jsfiddle.net/QRBaw/

You can also use .children().parent() to select the #foo element rather than using .filter() (in-case you want to do this dynamically).

UN-TESTED

Have you tried to change the selector in your .load() function call to select the children of the #foo element?

$('#foo').load('similar.html #foo>*');
like image 195
Jasper Avatar answered Nov 09 '22 15:11

Jasper