Welcome back! If you’ve read my previous post about creating a Single Page Application (SPA) using Knockout.js and jQuery Mobile, you might already have some context for this discussion.
Today, I want to address a common issue you might encounter when building SPAs: the duplication of id
attributes in header elements.
Why that can happen?
The problem arises because you're using a template for the header, and this template is injected into each page or section. As a result, the id
attributes of header elements are duplicated across pages. This duplication prevents jQuery from working correctly with id
selectors, making it impossible to interact with specific elements on certain pages.
I’ve seen many developers ask how to resolve this issue, so I thought I’d share a simple and effective solution.
Problem
To understand the problem, consider this example. Here is a template.
<nav class="v2_n">
<a href="#home" id="home" data-transition="fade">Home</a>
<a href="#list" id="list" data-transition="fade">List</a>
<a href="#form" id="form" data-transition="fade">Form</a>
</nav>
This template is being called on each section/page.
Solution
<section id="home" data-role="page" data-bind="with:HomePage">
<header>
<nav class="v2_n">
<a href="#home" id="home" data-transition="fade" >Home</a>
<a href="#list" id="list" data-transition="fade" >List</a>
<a href="#form" id="form" data-transition="fade" >Form</a>
</nav>
</header>
</section>
<section id="list" data-role="page" data-bind="with:ListPage">
<header>
<nav class="v2_n">
<a href="#home" id="home" data-transition="fade" >Home</a>
<a href="#list" id="list" data-transition="fade" >List</a>
<a href="#form" id="form" data-transition="fade" >Form</a>
</nav>
</header>
</section>
<section id="form" data-role="page" data-bind="with:FormPage">
<header>
<nav class="v2_n">
<a href="#home" id="home" data-transition="fade" >Home</a>
<a href="#list" id="list" data-transition="fade" >List</a>
<a href="#form" id="form" data-transition="fade" >Form</a>
</nav>
</header>
</section>
While working on SPA using this approach it is very simple to avoid this problem. You just need a property in parent view model. Let see how we can do this.
function VM() {
var self = this;
self.Title = ko.observable('Knockout! Parent and Child Usage Techniques')
self.Home = ko.observable()
self.Form = ko.observable()
self.List = ko.observable()
/* Define a property to hold id of current page */
self.CurrentPage = ko.observable()
// other code
}
And on page change event use this
$(window).on( "pagechange", function( event, data ) {
var page_id = data.toPage[0].id
/*
While navigating among pages each time pass
the page id to CurrentPage property
*/
vm.CurrentPage(page)
switch(page_id)
{
case 'form':
vm.FormPage().LoadData()
break;
case 'list':
vm.ListPage().LoadData()
break;
default:
vm.HomePage().LoadData()
break;
}
});
And now on view you need a simple condition
<section id="home" data-role="page" data-bind="with:HomePage">
<!-- ko if:$root.CurrentPage() == 'home' -->
<header>
<nav class="v2_n">
<a href="#home" id="home" data-transition="fade" >Home</a>
<a href="#list" id="list" data-transition="fade" >List</a>
<a href="#form" id="form" data-transition="fade" >Form</a>
</nav>
</header>
<section class="main_body max_600width">
// Other content
</section>
<!-- /ko -->
</section>
<section id="list" data-role="page" data-bind="with:ListPage">
<!-- ko if:$root.CurrentPage() == 'list' -->
<header>
<nav class="v2_n">
<a href="#home" id="home" data-transition="fade" >Home</a>
<a href="#list" id="list" data-transition="fade" >List</a>
<a href="#form" id="form" data-transition="fade" >Form</a>
</nav>
</header>
<section class="main_body max_600width">
// Other content
</section>
<!-- /ko -->
</section>
<section id="form" data-role="page" data-bind="with:FormPage">
<!-- ko if:$root.CurrentPage() == 'form' -->
<header>
<nav class="v2_n">
<a href="#home" id="home" data-transition="fade" >Home</a>
<a href="#list" id="list" data-transition="fade" >List</a>
<a href="#form" id="form" data-transition="fade" >Form</a>
</nav>
</header>
<section class="main_body max_600width">
// Other content
</section>
<!-- /ko -->
</section>
Now what this does is when you navigate between pages the other contents in each sections gets removed and the page being viewed is generated. So if you inspect section elements you will see only current page is having content and all other sections are empty. This way you can avoid duplication of id attributes in header. Hope this simple technique is useful.
You can view a demo here. Any question, i am here to explain.