/////////////////////////////////////// // INITIALIZATION /////////////////////////////////////// /** * Functionality for scaling, showing by media query, and navigation between multiple pages on a single page. * Code subject to change. **/ if (window.console==null) { window["console"] = { log : function() {} } }; // some browsers do not set console var Application = function() { // event constants this.prefix = "--web-"; this.NAVIGATION_CHANGE = "viewChange"; this.VIEW_NOT_FOUND = "viewNotFound"; this.VIEW_CHANGE = "viewChange"; this.VIEW_CHANGING = "viewChanging"; this.STATE_NOT_FOUND = "stateNotFound"; this.APPLICATION_COMPLETE = "applicationComplete"; this.APPLICATION_RESIZE = "applicationResize"; this.SIZE_STATE_NAME = "data-is-view-scaled"; this.STATE_NAME = this.prefix + "state"; this.lastTrigger = null; this.lastView = null; this.lastState = null; this.lastOverlay = null; this.currentView = null; this.currentState = null; this.currentOverlay = null; this.currentQuery = {index: 0, rule: null, mediaText: null, id: null}; this.inclusionQuery = "(min-width: 0px)"; this.exclusionQuery = "none and (min-width: 99999px)"; this.LastModifiedDateLabelName = "LastModifiedDateLabel"; this.viewScaleSliderId = "ViewScaleSliderInput"; this.pageRefreshedName = "showPageRefreshedNotification"; this.applicationStylesheet = null; this.mediaQueryDictionary = {}; this.viewsDictionary = {}; this.addedViews = []; this.views = {}; this.viewIds = []; this.viewQueries = {}; this.overlays = {}; this.overlayIds = []; this.numberOfViews = 0; this.verticalPadding = 0; this.horizontalPadding = 0; this.stateName = null; this.viewScale = 1; this.viewLeft = 0; this.viewTop = 0; this.horizontalScrollbarsNeeded = false; this.verticalScrollbarsNeeded = false; // view settings this.showUpdateNotification = false; this.showNavigationControls = false; this.scaleViewsToFit = false; this.scaleToFitOnDoubleClick = false; this.actualSizeOnDoubleClick = false; this.scaleViewsOnResize = false; this.navigationOnKeypress = false; this.showViewName = false; this.enableDeepLinking = true; this.refreshPageForChanges = false; this.showRefreshNotifications = true; // view controls this.scaleViewSlider = null; this.lastModifiedLabel = null; this.supportsPopState = false; // window.history.pushState!=null; this.initialized = false; // refresh properties this.refreshDuration = 250; this.lastModifiedDate = null; this.refreshRequest = null; this.refreshInterval = null; this.refreshContent = null; this.refreshContentSize = null; this.refreshCheckContent = false; this.refreshCheckContentSize = false; var self = this; self.initialize = function(event) { var view = self.getVisibleView(); var views = self.getVisibleViews(); if (view==null) view = self.getInitialView(); self.collectViews(); self.collectOverlays(); self.collectMediaQueries(); for (let index = 0; index < views.length; index++) { var view = views[index]; self.setViewOptions(view); self.setViewVariables(view); self.centerView(view); } // sometimes the body size is 0 so we call this now and again later if (self.initialized) { window.addEventListener(self.NAVIGATION_CHANGE, self.viewChangeHandler); window.addEventListener("keyup", self.keypressHandler); window.addEventListener("keypress", self.keypressHandler); window.addEventListener("resize", self.resizeHandler); window.document.addEventListener("dblclick", self.doubleClickHandler); if (self.supportsPopState) { window.addEventListener('popstate', self.popStateHandler); } else { window.addEventListener('hashchange', self.hashChangeHandler); } // we are ready to go window.dispatchEvent(new Event(self.APPLICATION_COMPLETE)); } if (self.initialized==false) { if (self.enableDeepLinking) { self.syncronizeViewToURL(); } if (self.refreshPageForChanges) { self.setupRefreshForChanges(); } self.initialized = true; } if (self.scaleViewsToFit) { self.viewScale = self.scaleViewToFit(view); if (self.viewScale<0) { setTimeout(self.scaleViewToFit, 500, view); } } else if (view) { self.viewScale = self.getViewScaleValue(view); self.centerView(view); self.updateSliderValue(self.viewScale); } else { // no view found } if (self.showUpdateNotification) { self.showNotification(); } //"addEventListener" in window ? null : window.addEventListener = window.attachEvent; //"addEventListener" in document ? null : document.addEventListener = document.attachEvent; } /////////////////////////////////////// // AUTO REFRESH /////////////////////////////////////// self.setupRefreshForChanges = function() { self.refreshRequest = new XMLHttpRequest(); if (!self.refreshRequest) { return false; } // get document start values immediately self.requestRefreshUpdate(); } /** * Attempt to check the last modified date by the headers * or the last modified property from the byte array (experimental) **/ self.requestRefreshUpdate = function() { var url = document.location.href; var protocol = window.location.protocol; var method; try { if (self.refreshCheckContentSize) { self.refreshRequest.open('HEAD', url, true); } else if (self.refreshCheckContent) { self.refreshContent = document.documentElement.outerHTML; self.refreshRequest.open('GET', url, true); self.refreshRequest.responseType = "text"; } else { // get page last modified date for the first call to compare to later if (self.lastModifiedDate==null) { // File system does not send headers in FF so get blob if possible if (protocol=="file:") { self.refreshRequest.open("GET", url, true); self.refreshRequest.responseType = "blob"; } else { self.refreshRequest.open("HEAD", url, true); self.refreshRequest.responseType = "blob"; } self.refreshRequest.onload = self.refreshOnLoadOnceHandler; // In some browsers (Chrome & Safari) this error occurs at send: // // Chrome - Access to XMLHttpRequest at 'file:///index.html' from origin 'null' // has been blocked by CORS policy: // Cross origin requests are only supported for protocol schemes: // http, data, chrome, chrome-extension, https. // // Safari - XMLHttpRequest cannot load file:///Users/user/Public/index.html. Cross origin requests are only supported for HTTP. // // Solution is to run a local server, set local permissions or test in another browser self.refreshRequest.send(null); // In MS browsers the following behavior occurs possibly due to an AJAX call to check last modified date: // // DOM7011: The code on this page disabled back and forward caching. // In Brave (Chrome) error when on the server // index.js:221 HEAD https://www.example.com/ net::ERR_INSUFFICIENT_RESOURCES // self.refreshRequest.send(null); } else { self.refreshRequest = new XMLHttpRequest(); self.refreshRequest.onreadystatechange = self.refreshHandler; self.refreshRequest.ontimeout = function() { self.log("Couldn't find page to check for updates"); } var method; if (protocol=="file:") { method = "GET"; } else { method = "HEAD"; } //refreshRequest.open('HEAD', url, true); self.refreshRequest.open(method, url, true); self.refreshRequest.responseType = "blob"; self.refreshRequest.send(null); } } } catch (error) { self.log("Refresh failed for the following reason:") self.log(error); } } self.refreshHandler = function() { var contentSize; try { if (self.refreshRequest.readyState === XMLHttpRequest.DONE) { if (self.refreshRequest.status === 2 || self.refreshRequest.status === 200) { var pageChanged = false; self.updateLastModifiedLabel(); if (self.refreshCheckContentSize) { var lastModifiedHeader = self.refreshRequest.getResponseHeader("Last-Modified"); contentSize = self.refreshRequest.getResponseHeader("Content-Length"); //lastModifiedDate = refreshRequest.getResponseHeader("Last-Modified"); var headers = self.refreshRequest.getAllResponseHeaders(); var hasContentHeader = headers.indexOf("Content-Length")!=-1; if (hasContentHeader) { contentSize = self.refreshRequest.getResponseHeader("Content-Length"); // size has not been set yet if (self.refreshContentSize==null) { self.refreshContentSize = contentSize; // exit and let interval call this method again return; } if (contentSize!=self.refreshContentSize) { pageChanged = true; } } } else if (self.refreshCheckContent) { if (self.refreshRequest.responseText!=self.refreshContent) { pageChanged = true; } } else { lastModifiedHeader = self.getLastModified(self.refreshRequest); if (self.lastModifiedDate!=lastModifiedHeader) { self.log("lastModifiedDate:" + self.lastModifiedDate + ",lastModifiedHeader:" +lastModifiedHeader); pageChanged = true; } } if (pageChanged) { clearInterval(self.refreshInterval); self.refreshUpdatedPage(); return; } } else { self.log('There was a problem with the request.'); } } } catch( error ) { //console.log('Caught Exception: ' + error); } } self.refreshOnLoadOnceHandler = function(event) { // get the last modified date if (self.refreshRequest.response) { self.lastModifiedDate = self.getLastModified(self.refreshRequest); if (self.lastModifiedDate!=null) { if (self.refreshInterval==null) { self.refreshInterval = setInterval(self.requestRefreshUpdate, self.refreshDuration); } } else { self.log("Could not get last modified date from the server"); } } } self.refreshUpdatedPage = function() { if (self.showRefreshNotifications) { var date = new Date().setTime((new Date().getTime()+10000)); document.cookie = encodeURIComponent(self.pageRefreshedName) + "=true" + "; max-age=6000;" + " path=/"; } document.location.reload(true); } self.showNotification = function(duration) { var notificationID = self.pageRefreshedName+"ID"; var notification = document.getElementById(notificationID); if (duration==null) duration = 4000; if (notification!=null) {return;} notification = document.createElement("div"); notification.id = notificationID; notification.textContent = "PAGE UPDATED"; var styleRule = "" styleRule = "position: fixed; padding: 7px 16px 6px 16px; font-family: Arial, sans-serif; font-size: 10px; font-weight: bold; left: 50%;"; styleRule += "top: 20px; background-color: rgba(0,0,0,.5); border-radius: 12px; color:rgb(235, 235, 235); transition: all 2s linear;"; styleRule += "transform: translateX(-50%); letter-spacing: .5px; filter: drop-shadow(2px 2px 6px rgba(0, 0, 0, .1))"; notification.setAttribute("style", styleRule); notification.className= "PageRefreshedClass"; document.body.appendChild(notification); setTimeout(function() { notification.style.opacity = "0"; notification.style.filter = "drop-shadow( 0px 0px 0px rgba(0,0,0, .5))"; setTimeout(function() { notification.parentNode.removeChild(notification); }, duration) }, duration); document.cookie = encodeURIComponent(self.pageRefreshedName) + "=; max-age=1; path=/"; } /** * Get the last modified date from the header * or file object after request has been received **/ self.getLastModified = function(request) { var date; // file protocol - FILE object with last modified property if (request.response && request.response.lastModified) { date = request.response.lastModified; } // http protocol - check headers if (date==null) { date = request.getResponseHeader("Last-Modified"); } return date; } self.updateLastModifiedLabel = function() { var labelValue = ""; if (self.lastModifiedLabel==null) { self.lastModifiedLabel = document.getElementById("LastModifiedLabel"); } if (self.lastModifiedLabel) { var seconds = parseInt(((new Date().getTime() - Date.parse(document.lastModified)) / 1000 / 60) * 100 + ""); var minutes = 0; var hours = 0; if (seconds < 60) { seconds = Math.floor(seconds/10)*10; labelValue = seconds + " seconds"; } else { minutes = parseInt((seconds/60) + ""); if (minutes>60) { hours = parseInt((seconds/60/60) +""); labelValue += hours==1 ? " hour" : " hours"; } else { labelValue = minutes+""; labelValue += minutes==1 ? " minute" : " minutes"; } } if (seconds<10) { labelValue = "Updated now"; } else { labelValue = "Updated " + labelValue + " ago"; } if (self.lastModifiedLabel.firstElementChild) { self.lastModifiedLabel.firstElementChild.textContent = labelValue; } else if ("textContent" in self.lastModifiedLabel) { self.lastModifiedLabel.textContent = labelValue; } } } self.getShortString = function(string, length) { if (length==null) length = 30; string = string!=null ? string.substr(0, length).replace(/\n/g, "") : "[String is null]"; return string; } self.getShortNumber = function(value, places) { if (places==null || places<1) places = 4; value = Math.round(value * Math.pow(10,places)) / Math.pow(10, places); return value; } /////////////////////////////////////// // NAVIGATION CONTROLS /////////////////////////////////////// self.updateViewLabel = function() { var viewNavigationLabel = document.getElementById("ViewNavigationLabel"); var view = self.getVisibleView(); var viewIndex = view ? self.getViewIndex(view) : -1; var viewName = view ? self.getViewPreferenceValue(view, self.prefix + "view-name") : null; var viewId = view ? view.id : null; if (viewNavigationLabel && view) { if (viewName && viewName.indexOf('"')!=-1) { viewName = viewName.replace(/"/g, ""); } if (self.showViewName) { viewNavigationLabel.textContent = viewName; self.setTooltip(viewNavigationLabel, viewIndex + 1 + " of " + self.numberOfViews); } else { viewNavigationLabel.textContent = viewIndex + 1 + " of " + self.numberOfViews; self.setTooltip(viewNavigationLabel, viewName); } } } self.updateURL = function(view) { view = view == null ? self.getVisibleView() : view; var viewId = view ? view.id : null var viewFragment = view ? "#"+ viewId : null; if (viewId && self.viewIds.length>1 && self.enableDeepLinking) { if (self.supportsPopState==false) { self.setFragment(viewId); } else { if (viewFragment!=window.location.hash) { if (window.location.hash==null) { window.history.replaceState({name:viewId}, null, viewFragment); } else { window.history.pushState({name:viewId}, null, viewFragment); } } } } } self.updateURLState = function(view, stateName) { stateName = view && (stateName=="" || stateName==null) ? self.getStateNameByViewId(view.id) : stateName; if (self.supportsPopState==false) { self.setFragment(stateName); } else { if (stateName!=window.location.hash) { if (window.location.hash==null) { window.history.replaceState({name:view.viewId}, null, stateName); } else { window.history.pushState({name:view.viewId}, null, stateName); } } } } self.setFragment = function(value) { window.location.hash = "#" + value; } self.setTooltip = function(element, value) { // setting the tooltip in edge causes a page crash on hover if (/Edge/.test(navigator.userAgent)) { return; } if ("title" in element) { element.title = value; } } self.getStylesheetRules = function(styleSheet) { try { if (styleSheet) return styleSheet.cssRules || styleSheet.rules; return document.styleSheets[0]["cssRules"] || document.styleSheets[0]["rules"]; } catch (error) { // ERRORS: // SecurityError: The operation is insecure. // Errors happen when script loads before stylesheet or loading an external css locally // InvalidAccessError: A parameter or an operation is not supported by the underlying object // Place script after stylesheet console.log(error); if (error.toString().indexOf("The operation is insecure")!=-1) { console.log("Load the stylesheet before the script or load the stylesheet inline until it can be loaded on a server") } return []; } } /** * If single page application hide all of the views. * @param {Number} selectedIndex if provided shows the view at index provided **/ self.hideViews = function(selectedIndex, animation) { var rules = self.getStylesheetRules(); var queryIndex = 0; var numberOfRules = rules!=null ? rules.length : 0; // loop through rules and hide media queries except selected for (var i=0;i=numberOfMediaQueries) { return; } // loop through rules and hide media queries except selected for (var i=0;i=scaleNeededToFitWidth; canCenterHorizontally = scaleNeededToFitWidth>=1 && enableScaleUp==false; if (isSliderChange) { canCenterHorizontally = desiredScale1 && (enableScaleUp || isSliderChange)) { transformValue = "scale(" + desiredScale + ")"; } else if (desiredScale>=1 && enableScaleUp==false) { transformValue = "scale(" + 1 + ")"; } else { transformValue = "scale(" + desiredScale + ")"; } if (self.centerVertically) { if (canCenterVertically) { translateY = "-50%"; topPosition = "50%"; } else { translateY = "0"; topPosition = "0"; } if (style.top != topPosition) { style.top = topPosition + ""; } if (canCenterVertically) { transformValue += " translateY(" + translateY+ ")"; } } if (self.centerHorizontally) { if (canCenterHorizontally) { translateX = "-50%"; leftPosition = "50%"; } else { translateX = "0"; leftPosition = "0"; } if (style.left != leftPosition) { style.left = leftPosition + ""; } if (canCenterHorizontally) { transformValue += " translateX(" + translateX+ ")"; } } style.transformOrigin = "0 0"; style.transform = transformValue; self.viewScale = desiredScale; self.viewToFitWidthScale = scaleNeededToFitWidth; self.viewToFitHeightScale = scaleNeededToFitHeight; self.viewLeft = leftPosition; self.viewTop = topPosition; return desiredScale; } // scale to fit height if (scaleToHeight && scaleToWidth==false) { //canCenterVertically = scaleNeededToFitHeight>=scaleNeededToFitWidth; //canCenterHorizontally = scaleNeededToFitHeight<=scaleNeededToFitWidth && enableScaleUp==false; canCenterVertically = scaleNeededToFitHeight>=scaleNeededToFitWidth; canCenterHorizontally = scaleNeededToFitWidth>=1 && enableScaleUp==false; if (isSliderChange) { canCenterHorizontally = desiredScale=scaleNeededToFitHeight && enableScaleUp==false; } desiredScale = self.getShortNumber(desiredScale); canCenterHorizontally = self.canCenterHorizontally(view, "height", enableScaleUp, desiredScale, minimumScale, maximumScale); canCenterVertically = self.canCenterVertically(view, "height", enableScaleUp, desiredScale, minimumScale, maximumScale); if (desiredScale>1 && (enableScaleUp || isSliderChange)) { transformValue = "scale(" + desiredScale + ")"; } else if (desiredScale>=1 && enableScaleUp==false) { transformValue = "scale(" + 1 + ")"; } else { transformValue = "scale(" + desiredScale + ")"; } if (self.centerHorizontally) { if (canCenterHorizontally) { translateX = "-50%"; leftPosition = "50%"; } else { translateX = "0"; leftPosition = "0"; } if (style.left != leftPosition) { style.left = leftPosition + ""; } if (canCenterHorizontally) { transformValue += " translateX(" + translateX+ ")"; } } if (self.centerVertically) { if (canCenterVertically) { translateY = "-50%"; topPosition = "50%"; } else { translateY = "0"; topPosition = "0"; } if (style.top != topPosition) { style.top = topPosition + ""; } if (canCenterVertically) { transformValue += " translateY(" + translateY+ ")"; } } style.transformOrigin = "0 0"; style.transform = transformValue; self.viewScale = desiredScale; self.viewToFitWidthScale = scaleNeededToFitWidth; self.viewToFitHeightScale = scaleNeededToFitHeight; self.viewLeft = leftPosition; self.viewTop = topPosition; return scaleNeededToFitHeight; } if (scaleToFitType=="fit") { //canCenterVertically = scaleNeededToFitHeight>=scaleNeededToFitWidth; //canCenterHorizontally = scaleNeededToFitWidth>=scaleNeededToFitHeight; canCenterVertically = scaleNeededToFitHeight>=scaleNeededToFit; canCenterHorizontally = scaleNeededToFitWidth>=scaleNeededToFit; if (hasMinimumScale) { desiredScale = Math.max(desiredScale, Number(minimumScale)); } desiredScale = self.getShortNumber(desiredScale); if (isSliderChange || scaleToFit==false) { canCenterVertically = scaleToFitFullHeight>=desiredScale; canCenterHorizontally = desiredScale=1) { canCenter = true; } } else if (type=="height") { minScale = Math.min(1, scaleNeededToFitHeight); if (minimumScale!="" && maximumScale!="") { minScale = Math.max(minimumScale, Math.min(maximumScale, scaleNeededToFitHeight)); } else { if (minimumScale!="") { minScale = Math.max(minimumScale, scaleNeededToFitHeight); } if (maximumScale!="") { minScale = Math.max(minimumScale, Math.min(maximumScale, scaleNeededToFitHeight)); } } if (scaleUp && maximumScale=="") { canCenter = false; } else if (scaleNeededToFitWidth>=minScale) { canCenter = true; } } else if (type=="fit") { canCenter = scaleNeededToFitWidth>=scaleNeededToFit; } else { if (scaleUp) { canCenter = false; } else if (scaleNeededToFitWidth>=1) { canCenter = true; } } self.horizontalScrollbarsNeeded = canCenter; return canCenter; } /** * Returns true if view can be centered horizontally * @param {HTMLElement} view view to scale * @param {String} type type of scaling * @param {Boolean} scaleUp if scale up enabled * @param {Number} scale target scale value */ self.canCenterVertically = function(view, type, scaleUp, scale, minimumScale, maximumScale) { var scaleNeededToFit = self.getViewFitToViewportScale(view, scaleUp); var scaleNeededToFitWidth = self.getViewFitToViewportWidthScale(view, scaleUp); var scaleNeededToFitHeight = self.getViewFitToViewportHeightScale(view, scaleUp); var canCenter = false; var minScale; type = type==null ? "none" : type; scale = scale==null ? 1 : scale; scaleUp = scaleUp == null ? false : scaleUp; if (type=="width") { canCenter = scaleNeededToFitHeight>=scaleNeededToFitWidth; } else if (type=="height") { minScale = Math.max(minimumScale, Math.min(maximumScale, scaleNeededToFit)); canCenter = scaleNeededToFitHeight>=minScale; } else if (type=="fit") { canCenter = scaleNeededToFitHeight>=scaleNeededToFit; } else { if (scaleUp) { canCenter = false; } else if (scaleNeededToFitHeight>=1) { canCenter = true; } } self.verticalScrollbarsNeeded = canCenter; return canCenter; } self.getViewFitToViewportScale = function(view, scaleUp) { var enableScaleUp = scaleUp; var availableWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; var availableHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; var elementWidth = parseFloat(getComputedStyle(view, "style").width); var elementHeight = parseFloat(getComputedStyle(view, "style").height); var newScale = 1; // if element is not added to the document computed values are NaN if (isNaN(elementWidth) || isNaN(elementHeight)) { return newScale; } availableWidth -= self.horizontalPadding; availableHeight -= self.verticalPadding; if (enableScaleUp) { newScale = Math.min(availableHeight/elementHeight, availableWidth/elementWidth); } else if (elementWidth > availableWidth || elementHeight > availableHeight) { newScale = Math.min(availableHeight/elementHeight, availableWidth/elementWidth); } return newScale; } self.getViewFitToViewportWidthScale = function(view, scaleUp) { // need to get browser viewport width when element var isParentWindow = view && view.parentNode && view.parentNode===document.body; var enableScaleUp = scaleUp; var availableWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; var elementWidth = parseFloat(getComputedStyle(view, "style").width); var newScale = 1; // if element is not added to the document computed values are NaN if (isNaN(elementWidth)) { return newScale; } availableWidth -= self.horizontalPadding; if (enableScaleUp) { newScale = availableWidth/elementWidth; } else if (elementWidth > availableWidth) { newScale = availableWidth/elementWidth; } return newScale; } self.getViewFitToViewportHeightScale = function(view, scaleUp) { var enableScaleUp = scaleUp; var availableHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; var elementHeight = parseFloat(getComputedStyle(view, "style").height); var newScale = 1; // if element is not added to the document computed values are NaN if (isNaN(elementHeight)) { return newScale; } availableHeight -= self.verticalPadding; if (enableScaleUp) { newScale = availableHeight/elementHeight; } else if (elementHeight > availableHeight) { newScale = availableHeight/elementHeight; } return newScale; } self.keypressHandler = function(event) { var rightKey = 39; var leftKey = 37; // listen for both events if (event.type=="keypress") { window.removeEventListener("keyup", self.keypressHandler); } else { window.removeEventListener("keypress", self.keypressHandler); } if (self.showNavigationControls) { if (self.navigationOnKeypress) { if (event.keyCode==rightKey) { self.nextView(); } if (event.keyCode==leftKey) { self.previousView(); } } } else if (self.navigationOnKeypress) { if (event.keyCode==rightKey) { self.nextView(); } if (event.keyCode==leftKey) { self.previousView(); } } } /////////////////////////////////// // GENERAL FUNCTIONS /////////////////////////////////// self.getViewById = function(id) { id = id ? id.replace("#", "") : ""; var view = self.viewIds.indexOf(id)!=-1 && self.getElement(id); return view; } self.getViewIds = function() { var viewIds = self.getViewPreferenceValue(document.body, self.prefix + "view-ids"); var viewId = null; viewIds = viewIds!=null && viewIds!="" ? viewIds.split(",") : []; if (viewIds.length==0) { viewId = self.getViewPreferenceValue(document.body, self.prefix + "view-id"); viewIds = viewId ? [viewId] : []; } return viewIds; } self.getInitialViewId = function() { var viewId = self.getViewPreferenceValue(document.body, self.prefix + "view-id"); return viewId; } self.getApplicationStylesheet = function() { var stylesheetId = self.getViewPreferenceValue(document.body, self.prefix + "stylesheet-id"); self.applicationStylesheet = document.getElementById("applicationStylesheet"); return self.applicationStylesheet.sheet; } self.getVisibleView = function() { var viewIds = self.getViewIds(); for (var i=0;itoIndex) { self.setElementAnimation(to, null); self.setElementAnimation(from, null); self.showViewByMediaQuery(to); self.fadeOut(from, update, reverse); setTimeout(function() { self.setElementAnimation(to, null); self.setElementAnimation(from, null); self.hideView(from); self.updateURL(); self.setViewVariables(to); }, duration) } } } self.fadeIn = function(element, update, animation) { self.showViewByMediaQuery(element); if (update) { self.updateURL(element); element.addEventListener("animationend", function(event) { element.style.animation = null; self.setViewVariables(element); self.updateViewLabel(); element.removeEventListener("animationend", arguments.callee); }); } self.setElementAnimation(element, null); element.style.animation = animation; } self.fadeOutCurrentView = function(animation, update) { if (self.currentView) { self.fadeOut(self.currentView, update, animation); } if (self.currentOverlay) { self.fadeOut(self.currentOverlay, update, animation); } } self.fadeOut = function(element, update, animation) { if (update) { element.addEventListener("animationend", function(event) { element.style.animation = null; self.hideView(element); element.removeEventListener("animationend", arguments.callee); }); } element.style.animationPlayState = "paused"; element.style.animation = animation; element.style.animationPlayState = "running"; } self.getReverseAnimation = function(animation) { if (animation && animation.indexOf("reverse")==-1) { animation += " reverse"; } return animation; } /** * Get duration in animation string * @param {String} animation animation value * @param {Boolean} inMilliseconds length in milliseconds if true */ self.getAnimationDuration = function(animation, inMilliseconds) { var duration = 0; var expression = /.+(\d\.\d)s.+/; if (animation && animation.match(expression)) { duration = parseFloat(animation.replace(expression, "$" + "1")); if (duration && inMilliseconds) duration = duration * 1000; } return duration; } self.setElementAnimation = function(element, animation, priority) { element.style.setProperty("animation", animation, "important"); } self.getElement = function(id) { var elementId = id ? id.trim() : id; var element = elementId ? document.getElementById(elementId) : null; return element; } self.getElementByClass = function(className) { className = className ? className.trim() : className; var elements = document.getElementsByClassName(className); return elements.length ? elements[0] : null; } self.resizeHandler = function(event) { if (self.showByMediaQuery) { if (self.enableDeepLinking) { var stateName = self.getHashFragment(); if (stateName==null || stateName=="") { var initialView = self.getInitialView(); stateName = initialView ? self.getStateNameByViewId(initialView.id) : null; } self.showMediaQueryViewsByState(stateName, event); } } else { var visibleViews = self.getVisibleViews(); for (let index = 0; index < visibleViews.length; index++) { var view = visibleViews[index]; self.scaleViewIfNeeded(view); } } window.dispatchEvent(new Event(self.APPLICATION_RESIZE)); } self.scaleViewIfNeeded = function(view) { if (self.scaleViewsOnResize) { if (view==null) { view = self.getVisibleView(); } var isViewScaled = view.getAttributeNS(null, self.SIZE_STATE_NAME)=="false" ? false : true; if (isViewScaled) { self.scaleViewToFit(view, true); } else { self.scaleViewToActualSize(view); } } else if (view) { self.centerView(view); } } self.centerView = function(view) { if (self.scaleViewsToFit) { self.scaleViewToFit(view, true); } else { self.scaleViewToActualSize(view); // for centering support for now } } self.preventDoubleClick = function(event) { event.stopImmediatePropagation(); } self.getHashFragment = function() { var value = window.location.hash ? window.location.hash.replace("#", "") : ""; return value; } self.showBlockElement = function(view) { view.style.display = "block"; } self.hideElement = function(view) { view.style.display = "none"; } self.showStateFunction = null; self.showMediaQueryViewsByState = function(state, event) { // browser will hide and show by media query (small, medium, large) // but if multiple views exists at same size user may want specific view // if showStateFunction is defined that is called with state fragment and user can show or hide each media matching view by returning true or false // if showStateFunction is not defined and state is defined and view has a defined state that matches then show that and hide other matching views // if no state is defined show view // an viewChanging event is dispatched before views are shown or hidden that can be prevented // get all matched queries // if state name is specified then show that view and hide other views // if no state name is defined then show var matchedViews = self.getMatchingViews(); var matchMediaQuery = true; var foundViews = self.getViewsByStateName(state, matchMediaQuery); var showViews = []; var hideViews = []; // loop views that match media query for (let index = 0; index < matchedViews.length; index++) { var view = matchedViews[index]; // let user determine visible view if (self.showStateFunction!=null) { if (self.showStateFunction(view, state)) { showViews.push(view); } else { hideViews.push(view); } } // state was defined so check if view matches state else if (foundViews.length) { if (foundViews.indexOf(view)!=-1) { showViews.push(view); } else { hideViews.push(view); } } // if no state names are defined show view (define unused state name to exclude) else if (state==null || state=="") { showViews.push(view); } } if (showViews.length) { var viewChangingEvent = new Event(self.VIEW_CHANGING); viewChangingEvent.showViews = showViews; viewChangingEvent.hideViews = hideViews; window.dispatchEvent(viewChangingEvent); if (viewChangingEvent.defaultPrevented==false) { for (var index = 0; index < hideViews.length; index++) { var view = hideViews[index]; if (self.isOverlay(view)) { self.removeOverlay(view); } else { self.hideElement(view); } } for (var index = 0; index < showViews.length; index++) { var view = showViews[index]; if (index==showViews.length-1) { self.clearDisplay(view); self.setViewOptions(view); self.setViewVariables(view); self.centerView(view); self.updateURLState(view, state); } } } var viewChangeEvent = new Event(self.VIEW_CHANGE); viewChangeEvent.showViews = showViews; viewChangeEvent.hideViews = hideViews; window.dispatchEvent(viewChangeEvent); } } self.clearDisplay = function(view) { view.style.setProperty("display", null); } self.hashChangeHandler = function(event) { var fragment = self.getHashFragment(); var view = self.getViewById(fragment); if (self.showByMediaQuery) { var stateName = fragment; if (stateName==null || stateName=="") { var initialView = self.getInitialView(); stateName = initialView ? self.getStateNameByViewId(initialView.id) : null; } self.showMediaQueryViewsByState(stateName); } else { if (view) { self.hideViews(); self.showView(view); self.setViewVariables(view); self.updateViewLabel(); window.dispatchEvent(new Event(self.VIEW_CHANGE)); } else { window.dispatchEvent(new Event(self.VIEW_NOT_FOUND)); } } } self.popStateHandler = function(event) { var state = event.state; var fragment = state ? state.name : window.location.hash; var view = self.getViewById(fragment); if (view) { self.hideViews(); self.showView(view); self.updateViewLabel(); } else { window.dispatchEvent(new Event(self.VIEW_NOT_FOUND)); } } self.doubleClickHandler = function(event) { var view = self.getVisibleView(); var scaleValue = view ? self.getViewScaleValue(view) : 1; var scaleNeededToFit = view ? self.getViewFitToViewportScale(view) : 1; var scaleNeededToFitWidth = view ? self.getViewFitToViewportWidthScale(view) : 1; var scaleNeededToFitHeight = view ? self.getViewFitToViewportHeightScale(view) : 1; var scaleToFitType = self.scaleToFitType; // Three scenarios // - scale to fit on double click // - set scale to actual size on double click // - switch between scale to fit and actual page size if (scaleToFitType=="width") { scaleNeededToFit = scaleNeededToFitWidth; } else if (scaleToFitType=="height") { scaleNeededToFit = scaleNeededToFitHeight; } // if scale and actual size enabled then switch between if (self.scaleToFitOnDoubleClick && self.actualSizeOnDoubleClick) { var isViewScaled = view.getAttributeNS(null, self.SIZE_STATE_NAME); var isScaled = false; // if scale is not 1 then view needs scaling if (scaleNeededToFit!=1) { // if current scale is at 1 it is at actual size // scale it to fit if (scaleValue==1) { self.scaleViewToFit(view); isScaled = true; } else { // scale is not at 1 so switch to actual size self.scaleViewToActualSize(view); isScaled = false; } } else { // view is smaller than viewport // so scale to fit() is scale actual size // actual size and scaled size are the same // but call scale to fit to retain centering self.scaleViewToFit(view); isScaled = false; } view.setAttributeNS(null, self.SIZE_STATE_NAME, isScaled+""); isViewScaled = view.getAttributeNS(null, self.SIZE_STATE_NAME); } else if (self.scaleToFitOnDoubleClick) { self.scaleViewToFit(view); } else if (self.actualSizeOnDoubleClick) { self.scaleViewToActualSize(view); } } self.scaleViewToFit = function(view) { return self.setViewScaleValue(view, true); } self.scaleViewToActualSize = function(view) { self.setViewScaleValue(view, false, 1); } self.onloadHandler = function(event) { self.initialize(); } self.setElementHTML = function(id, value) { var element = self.getElement(id); element.innerHTML = value; } self.getStackArray = function(error) { var value = ""; if (error==null) { try { error = new Error("Stack"); } catch (e) { } } if ("stack" in error) { value = error.stack; var methods = value.split(/\n/g); var newArray = methods ? methods.map(function (value, index, array) { value = value.replace(/\@.*/,""); return value; }) : null; if (newArray && newArray[0].includes("getStackTrace")) { newArray.shift(); } if (newArray && newArray[0].includes("getStackArray")) { newArray.shift(); } if (newArray && newArray[0]=="") { newArray.shift(); } return newArray; } return null; } self.log = function(value) { console.log.apply(this, [value]); } // initialize on load // sometimes the body size is 0 so we call this now and again later window.addEventListener("load", self.onloadHandler); window.document.addEventListener("DOMContentLoaded", self.onloadHandler); } window.application = new Application();