/** * @file * Contains the router code and route definitions * * Inspired by Joakim Beng"s example. * * @see http://joakim.beng.se/blog/posts/a-javascript-router-in-20-lines.html */ // The routes object. var routes = {}; // The route registering function. function route(path, controller) { routes[path] = controller; } /** * Set the target content to the template result. * * Use only if global "ready" is true, otherwise global "templates" may not be * entirely initialized. * * @param {DOMElement} target * The element in which to inject the template output. * @param {string} name * The name of the template to render. * @param {Object} context * The template context. * * @returns {void} * * @globals templates */ function render(target, name, context) { var output = null; var template = templates[name]; if (!template) { console.error("Template not found", name); return; } output = template(context); target.innerHTML = output; } /** * The router. * * @returns {void} * * @globals contentElement, ready, EVENT_TEMPLATES_LOADED */ function router() { var context = null; var CurrentRoute = null; var listener = null; var path = null; var query; var templateName = null; // Current route url (getting rid of "#" in hash as well): var matches = location.hash.match(/^#?([\w]*)(?:\?)?(.*)?/); path = matches[1] ? matches[1] : "/"; path = path.toLowerCase(); query = matches[2] ? matches[2].split("&").reduce(function (accu, v) { var vArray = v.split("="); accu[vArray[0]] = vArray[1]; return accu; }, {}) : {}; // console.log("url", url, "path", path, "queryObject", queryObject); // Get route by path: CurrentRoute = routes[path]; // Do we have a route ? if (CurrentRoute) { // Obtain the template context by passing the query to the controller. context = new CurrentRoute(query); // Render template with Handlebars templateName = (path === "/") ? "home" : path; if (ready) { console.log("Immediate render"); render(contentElement, templateName, context); } else { console.log("Deferred render"); listener = contentElement.addEventListener(EVENT_TEMPLATES_LOADED, function () { render(contentElement, templateName, context); contentElement.removeEventListener(EVENT_TEMPLATES_LOADED, listener); }); } } else { console.warn("Content found but no route matching", path); window.location.assign(""); } } // Bind router to browser events. window.addEventListener("hashchange", router); window.addEventListener("load", router); // ======== Route context functions ============================================ function AboutContext() { console.log("About page"); } function ClassContext(q) { var category = animals_data.category; var animals = null; var klass; this.class = q.class ? q.class : null; if (!this.class) { window.location.assign(""); } console.log("Class page(" + this.class + ")"); for (klass in category) { if (category.hasOwnProperty(klass)) { if (category[klass].name === this.class) { animals = category[klass].animals; break; } } } this.animals = animals; } /** * Home context. * * - category * - indicators * * @constructor */ function HomeContext() { var i; console.log("Home page"); // jQuery doesn't find the carousel without the 0 (or more) timeout. setTimeout(function () { $("#home-carousel").carousel(); }, 0); // Expose animals category to build the carousel. this.category = animals_data.category; // Carousel indicators count: JS doesn't have a range() function. this.indicators = []; for (i = 0; i < Object.keys(this.category).length; i++) { this.indicators.push(i); } } /** * Species context * * @param {Object} q * - class * - species * @constructor */ function SpeciesContext(q) { var category = animals_data.category; var klass = null; var speciesArray = null; var species; var speciesIndex; var prop; if (!q.class || !q.species) { console.error("Missing class or species parameter."); window.location.assign(""); return; } console.log("Species page(" + q.class + ", " + q.species + ")"); this.class = q.class ? q.class : null; for (klass in category) { if (category.hasOwnProperty(klass)) { if (category[klass].name === this.class) { speciesArray = category[klass].animals; break; } } } // No species means class was not found. if (!speciesArray) { console.warn("No species found for class ", this.class); window.location.assign(""); } for (speciesIndex in speciesArray) { if (speciesArray.hasOwnProperty(speciesIndex)) { species = speciesArray[speciesIndex]; if (species.name === q.species) { for (prop in species) { if (species.hasOwnProperty(prop)) { this[prop] = species[prop]; } } break; } } } } // ======== Route binding ====================================================== /** * Route binding: * * - route context functions return a context instance. * - on invalid arguments, they redirect to the home page */ route("/", HomeContext); route("class", ClassContext); route("species", SpeciesContext); route("about", AboutContext);