UNPKG

koco

Version:

knockout components with routing

186 lines (144 loc) 5.54 kB
// Copyright (c) CBC/Radio-Canada. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // https://developer.mozilla.org/en-US/docs/Web/API/Location // http://medialize.github.io/URI.js/about-uris.html // TODO: Supporter les urls complètes (on supporte relative seulement en ce moement). // Pour, exemple, pemettre de naviguer dans un sous-domain dans la même app... const tagNames = ['A', 'AREA']; function getClickableElement(clckedElement /*, tagNames */ ) { if (tagNames.indexOf(clckedElement.tagName) > -1) { return clckedElement; } let cursor = clckedElement; while (cursor.parentNode) { if (tagNames.indexOf(cursor.tagName) > -1) { return cursor; } cursor = cursor.parentNode; } return null; } function backAndForwardButtonHandler(self, e) { // why this if??? // if (e.state !== null) { self.backOrForwardDebounced(e.state); // } } // todo: rendre overridable (configuration)? function shouldHandleNavigation(self, url) { return url.toLowerCase().startsWith(self.router.settings.baseUrl.toLowerCase()); } function startsWithRootUrl(self, url) { return url.toLowerCase().startsWith((document.location.origin + self.router.settings.baseUrl).toLowerCase()); } function getRelativeUrl(url) { return '/' + url.replace(/^(?:\/\/|[^\/]+)*\//, ''); } function hrefClickHandler(self, e, clickableElement) { // Only handle left-click with no modifiers if (e.which !== 1 || e.shiftKey || e.altKey || e.metaKey || e.ctrlKey) { return; } const ignore = clickableElement.getAttribute('data-router-ignore'); if (ignore) { return; } let url = clickableElement.getAttribute('href'); // TODO: permettre un regex (ou autre) en config pour savoir si c'est un lien interne // car avec ça les sous-domaines vont etre exclus // ce qui ne doit pas nécessairement etre le cas! const isRelativeUrl = url.indexOf(':') === -1; /* var isSameDomain = url.indexOf(document.domain) > -1;*/ // if ( /*isSameDomain || */ isRelativeUrl) { if (!isRelativeUrl && startsWithRootUrl(self, url)) { url = getRelativeUrl(url); } if (shouldHandleNavigation(self, url)) { e.preventDefault(); self.setUrlDebounced(url); } } // function makeRelativeUrlStartWithSlash(self, url) { // const isRelativeUrl = url.indexOf(':') === -1; // let result = url; // if (isRelativeUrl) { // // Replace all (/.../g) leading slash (^\/) or (|) trailing slash (\/$) with an empty string. // result = url.replace(/^\/|\/$/g, ''); // result = '/' + result; // } // return result; // } export default class RouterStatePush { constructor(router) { var self = this; // http://stackoverflow.com/questions/8980255/how-do-i-retrieve-if-the-popstate-event-comes-from-back-or-forward-actions-with self.stateId = 0; self.router = router; // TODO: Pas besoin de debounce étant donné que le router annule automatiquement les requêtes précédentes... pas certain du résultat --> à valider self.setUrlDebounced = /* _.debounce( */ function(url) { self.router.navigate(url); }; /* , 500, { 'leading': true, 'trailing': true });*/ // TODO: Pas besoin de debounce étant donné que le router annule automatiquement les requêtes précédentes... pas certain du résultat --> à valider self.backOrForwardDebounced = /*_.debounce(*/ function(state) { let direction; if (state.id < self.stateId) { self.stateId--; direction = 'back'; } else { direction = 'forward'; self.stateId++; } return self.backOrForward(state, direction); }; /* , 500, { 'leading': true, 'trailing': true });*/ // prevent bug with safari (popstate is fired on page load with safari) document.addEventListener('DOMContentLoaded', function( /*event*/ ) { // back and forward button support window.onpopstate = function(e) { backAndForwardButtonHandler(self, e); }; }); // http://www.smashingmagazine.com/2013/11/an-introduction-to-dom-events/ document.addEventListener('click', function(event) { var clickableElement = getClickableElement(event.target); if (clickableElement) { hrefClickHandler(self, event, clickableElement); } }); } backOrForward( /* state */ ) { // même dans le cas où on fait back, il se peut que, dû au pipeline du router, l'url ne // soit pas celle du back (a cause de guardRoute par exemple) // il faut donc faire un replace du state à la fin pour être certain d'avoir la bonne url return this.router.navigate(this.router.currentUrl(), { replace: true }); } pushState(options) { const defaultOptions = { url: '', pageTitle: '', stateObject: {}, replace: false }; const finalOptions = Object.assign({}, defaultOptions, options); finalOptions.stateObject.url = finalOptions.url; finalOptions.stateObject.pageTitle = finalOptions.pageTitle; if (finalOptions.replace) { finalOptions.stateObject.id = this.stateId; window.history.replaceState(finalOptions.stateObject, finalOptions.pageTitle, finalOptions.url); } else { finalOptions.stateObject.id = ++this.stateId; window.history.pushState(finalOptions.stateObject, finalOptions.pageTitle, finalOptions.url); } } }