(function() { var content = document.getElementById('content'); var form = document.getElementById('mainform'); // Lives outside #content (a sibling in the persistent shell, next to // the menu bar/form) - a fragment swap never touches it on its own, // so a per-page side menu would otherwise stay frozen on whichever // page's menu happened to be there when the shell first loaded. See // applyResponse()'s own handling of the #_spa_panelmenu marker below. var panelMenu = document.getElementById('panelmenu'); // Same story as panelMenu just above, for the top-of-page tab bar and // the upper-right "Welcome X / Logout" box - both live outside // #content too, and both depend on server-side login state // ($[_login_id], see menu.bar's own ACCOUNT/EMAIL/ADMIN tabs and // login.menu's Welcome/Logout block) that only the server can know. // Without this, logging in via a SPA form submit left the tab bar // still showing LOGIN and the header box empty, even though the // account page's own content came back correctly - only a full page // reload picked up the new state, since only the non-fragment branch // ever rendered these two includes at all. var menuBar = document.getElementById('menubar'); var loginMenu = document.getElementById('loginmenu'); // The form's own initial action="/" (server- // rendered per-request - "/spa/$[PAGE]" during the 8081 test-path // phase, "/$[PAGE]" once a port is cut over to the real path) tells // us the route prefix this specific deployment is using, captured // once here since the form itself lives outside #content and never // gets re-rendered by a client-side navigation - only the initial // page load sets it. Used to build the target for every subsequent // submit (the PAGE hidden field's own value changes per navigation, // this prefix does not) - keeps this one shared spa.js correct across // every phase of the rollout without needing a matching edit each // time a port's routing changes. var formPrefix = ''; if (form) { var initialAction = form.getAttribute('action') || ''; var initialPage = (form.elements['PAGE'] && form.elements['PAGE'].value) || ''; // action is "/" normally, or "//" // when the page loaded directly on a URL that already has one (e.g. // a bookmark to /opensource/barantmail) - suffix has to account for // both or this initial load's own subpage would get mistaken for // part of the prefix. var initialSubpage = (form.elements['SUBPAGE'] && form.elements['SUBPAGE'].value) || ''; var initialSuffix = initialPage + (initialSubpage ? '/' + initialSubpage : ''); if (initialSuffix && initialAction.slice(-initialSuffix.length) === initialSuffix) formPrefix = initialAction.slice(0, initialAction.length - initialSuffix.length - 1); } // The nav bar (menu.bar) lives in the persistent shell, outside // #content - a fetch-and-swap navigation never re-renders it, so // without this the "selected" tab highlight would just stay frozen // on whichever page the shell happened to load with. Each tab's own // (the real, un-prefixed target path - see menu.bar's // own ... shape) is what's used // to identify it; toggling classes here keeps it in sync with every // client-side navigation without needing the server to resend the // whole menu bar on every fragment response. function syncActiveTab(targetPath) { if (formPrefix && targetPath.indexOf(formPrefix) === 0) targetPath = targetPath.slice(formPrefix.length) || '/'; var tabs = document.querySelectorAll('.baritem, .baritemselected'); for (var i = 0; i < tabs.length; i++) { // Each tab wraps TWO anchors: the outer one linkbutton generates // (href="javascript:...", drives the actual click) and a real // nested inside it as the visible link text/label // (see menu.bar's own ... // shape). Both match a[href] - only the second, real-path one // identifies the page, so the javascript: one has to be skipped // explicitly rather than just taking the first match. var links = tabs[i].querySelectorAll('a[href]'); var isActive = false; for (var j = 0; j < links.length; j++) { var href = links[j].getAttribute('href'); // Exact match covers a plain page; a subpage URL (e.g. // /admin/users, /opensource/barantmail) never equals its own // tab's bare /admin or /opensource href, so the tab lost its // highlight the moment a subpage was selected. Matching on // "targetPath starts with href, then either ends or continues // with a /" keeps the tab highlighted for any of its own // subpages without also matching an unrelated page whose name // merely starts with the same characters (e.g. a hypothetical // /administration would not match /admin). if (href && href.indexOf('javascript:') !== 0 && href !== '/' && (href === targetPath || (targetPath.indexOf(href) === 0 && (targetPath.charAt(href.length) === '/' || targetPath.charAt(href.length) === '')))) { isActive = true; break; } } tabs[i].classList.toggle('baritemselected', isActive); tabs[i].classList.toggle('baritem', !isActive); } } // Returns false if the response was a full-navigation redirect // (already handled here, caller does nothing further); true if no // data-spa-page marker was present at all (caller keeps using the // URL it already requested); otherwise swaps in the content and // returns {page, subpage} - the AUTHORITATIVE page/subpage the // server actually rendered. mainpagetemplate.jet emits //
right // before the fragment body, reflecting whatever a may have // changed PAGE to via while handling the request (e.g. a // successful login handing back the account page, not the login page // the form was actually submitted to). Callers must use THIS value // for history.pushState()/the active-tab highlight, not whatever // page they originally requested - using the pre-request page here // was exactly why a successful login left the address bar (and the // tab highlight) stuck on /login even though the account page's // content had correctly come back. subpage is the optional second // URL segment (e.g. /opensource/barantmail) a page can use to pick // what to render in its own second-tier content area. function applyResponse(html) { var redirect = / elements set via innerHTML are inert - the browser // deliberately never executes them (both inline scripts and // src="..." ones), only ones present when the HTML parser itself // builds the DOM (a full page load). Pages whose own content // depends on a