Search code examples
javascripthtmlroutessingle-page-applicationcrossroadsjs

Single page application routing w/ Crossroads & Hasher, by example


I am trying to write my first single-page application. The idea is to have 1 HTML file that contains many <div> tags; where each <div> represents a single web "page". Then the application just shows 1 <div> at a time, and hides the others. In this way, as users navigate my app, I'm really just showing/hiding different "page" divs, and giving the illusion of a single page app.

Additional requirements are:

  • This is an HTML5 app
  • Each page div must map too its own bookmarkable URL (http://myapp.example.com/#fizz, http://myapp.example.com/#buzz, etc.)
  • Singe each page div is bookmarkable, the app must work with the HTML5 history api

I decided on using Crossroads for routing, and Hasher for History. The other lead contender was AngularJS, but in the end I decided against AngularJS because it was too heavyweight for what I'm trying to do here, and seemed to have a steeper learning curve associated with it.

So far, my project has the following directory structure:

myapp/
    index.html
    myapp.js
    myapp.css
    signals.min.js          <-- Required by both Crossroads and Hasher
    crossroads.min.js
    hasher.min.js

The JSFiddle containing my index.html, myapp.css and myapp.js files is here:

http://jsfiddle.net/Sxfms/2/

The idea is that the user can click one of the links in the "navbar" ("Home", "About", "Contact") and be brought to the "page" (div) representing that particular page. As you can see, the default "page" should be HOME, meaning this is the only div you should be able to see. But all the page divs are visible, and none are hidden. And until I can get the page divs showing/hiding correctly, I can't really test routing/history functionality. Have I configured Crossroads/Hasher wrong somehow?


Solution

  • I think there is a solution for your requirements. It is a really easy, lightweight approach without the need of any javascript just with the power of CSS. ;-)

    The key of the whole approach is the CSS pseudo-class selector :target.

    So let me first explain the concept of :target: The pseudo selector matches when the fragment identifier (or hash, #content for instance) in the URL and the id of an HTML element are the same. If we have a URL like http://www.example.com/hallo.html#content and an element with the id="content" the selector #content:target { ... } would match.

    You can´t really see the URL in this fiddel, but you will in another example. Her is the code of the fiddle:

    HTML:

    <a href="#content">content</a>
    <div id="content">
        Markup is poetry!
    </div>
    

    CSS:

    #content {
        border: 1px solid black;
        padding: 20px;
    }
    
    #content:target {
        background: lightblue;
    }
    

    The :target approach leads to this stripped down example to explain the page-navigation-idea: http://jsfiddle.net/Cxr73/1/ Again you can´t really see the URLs with the fragment identifier.

    HTML:

    <a href="#div1">div1</a>
    <a href="#div2">div2</a>
    <a href="#div3">div3</a>
    
    <div id="div2">
        <div id="div3">
            <div class="div1Inner">content div1</div>
            <div class="div2Inner">content div2</div>
            <div class="div3Inner">content div3</div>
        </div>
    </div>
    

    CSS:

    .div2Inner, .div3Inner, 
    #div2:target .div1Inner, #div3:target .div1Inner {
        display: none;
    }
    
    #div2:target .div2Inner, #div3:target .div3Inner {
        display: block;
    }
    

    Hide all divs that should not be displayed at first: .div2Inner, .div3Inner { display: none;}. So just <div class="div1Inner">content div1</div> is visible. Show the corresponding div when the fragment identifier is part of the URL: #div2:target .div2Inner, #div3:target .div3Inner {display: block;}. In the end you have to hide div1 when div2 or div3 are visible: #div2:target .div1Inner, #div3:target .div1Inner { display: none; }. Combine the first and the last CSS selector and you get to the CSS shown above.


    Some recommendations on your markup:

    • As recommended by the HTML5 spec (4.2.5.5 Specifying the document's character encoding), add your charset declaration early to avoid a potential encoding-related security issue in IE. It should come in the first 1024 bytes.
    • The <center> element was deprecated because it defines the presentation of its contents. For this purposes we have CSS.
    • You are writing an HTML5 app, so throw in some more semantic markup, elements like: nav, header, section, footer, etc.

    Here you have the final approach of my ideas, with your CSS plus the :target selectors (starts at line 600) and what I consider a clean markup:

    Fiddle: http://jsfiddle.net/Cxr73/2/ To finally see the fragment identifier plus the :target in action and for test purposes another URL: DEMO ... this demo will disappear in a few days, but the fiddle will stay.

    I think that pretty much matches all your needs. Have fun!