import { PageData } from "./PageData"; import { PageTransitionHandler } from "./PageTransitionHandler"; import { sleep } from "../animation/sleep"; import { EventSlot } from "../events/EventSlot"; import { ElementAttribute } from "./ElementAttribute"; import { OnClick, OnMouseDown, OnMouseEnter } from "./EventListeners"; import { LanguageCode } from "../i18n/LanguageCode"; import { Link, LinkType } from "./LinkResolver"; import { RootPathResolver } from "./RootPathResolver"; import { Loader } from "../xhttp/Loader"; import { ElementType } from "./ElementType"; import { RegExpUtility } from "../text/RegExpUtitlity"; import { AttributeValue } from "./AttributeValue"; export class FunctionHandleAnchor extends HTMLAnchorElement { _functionCallback:()=>void; _assignedClickListener = false; } export class PageRequestEvent { lastPage:string; nextPage:string; } export class PagesJSON { pages:string[]; } export enum PageHandlerMode { PHP, HTML, ELECTRON } export class PageHandler { private _pagesPath = "_scripts/pages.json"; private _pages = new Map(); private _startPage:string; get startPage(){ return this._startPage;} private _currentPage:string; private _nextPage:string; private _isLoading:boolean = false; private _followUpPage:string = null; private _pageRootTag = "body"; private _forceHTTPS = true; private _pageParameters = "?no-page-token=true"; readonly onPageRequest = new EventSlot(); get pageRootTag(){ return this._pageRootTag; } transitionHandler:PageTransitionHandler; private _webAdress:string; private _localAdressExtension:string; private _rootPathResolver = new RootPathResolver(); get rootPathResolver(){ return this._rootPathResolver; } private _preloadImagesFlag:boolean = true; private _maxPreloadingDurationMS:number = 2000; private static _localHost = "http://localhost:8080"; private static _localNetworkRegexPattern = /^(http:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}?:XXXX)/ private static _localNetworkRegex = RegExpUtility.createRegExp( this._localNetworkRegexPattern, "XXXX", 8080 + "" ); private _isHTMLMode:boolean = false; private _fileLocation:string = ""; private _isFileMode:boolean = false; private _pagesLoaded = false; setPagesPath( path:string ) { this._pagesPath = path; } setAdress( localExtension: string, web:string ) { this._localAdressExtension = localExtension; this._webAdress = web; } setPageRootTag( pageRootTag:string ) { this._pageRootTag = pageRootTag; } get pageParameters() { return this._pageParameters; } getParameterValue( name:string ) { let parameters = window.location.search; let pageParameters = parameters.length > 0 ? parameters.substring( 1 ) : null; if ( ! pageParameters ) { return null; } let pairs = pageParameters.split( "&" ); for ( let p of pairs ) { let keyValue = p.split( "=" ); if ( keyValue.length !== 2 ) { continue; } if ( keyValue[ 0 ] === name ) { return decodeURIComponent( keyValue[ 1 ] ); } } return null; } static setLocalHostPort( port:number ) { PageHandler._localHost = "http://localhost:" + port; PageHandler._localNetworkRegex = RegExpUtility.createRegExp( this._localNetworkRegexPattern, "XXXX", port + "" ); } constructor( localAdressExtension:string, webAdress:string, disableForceHTTPS?:boolean, mode:PageHandlerMode = PageHandlerMode.PHP ) { if ( disableForceHTTPS === true ) { this._forceHTTPS = false; } if ( PageHandlerMode.HTML == mode || PageHandlerMode.ELECTRON == mode ) { this._isHTMLMode = true; } if ( PageHandlerMode.ELECTRON == mode ) { this.setFileLocation( localAdressExtension ); } else { this.setAdress( localAdressExtension, webAdress ); } //this._startPage = this.currentPage; //console.log( "START PAGE:", this._startPage ); } setFileLocation( location:string ) { this._fileLocation = location; this._isFileMode = true; } get localAdress() { let adress = this.trimmedLocation; if ( adress.startsWith( PageHandler._localHost ) ) { return PageHandler._localHost + this._localAdressExtension; } if ( PageHandler._localNetworkRegex.test( adress ) ) { return PageHandler._localNetworkRegex.exec( adress )[ 1 ] + this._localAdressExtension; } return null; } get isLocalNetwork() { let adress = this.trimmedLocation; // console.log( "Checking adress for localhost", adress, PageHandler._localHost, PageHandler._localNetworkRegex ); if ( adress.startsWith( PageHandler._localHost ) ) { // console.log( "Is starting with localhost" ); return true; } // console.log( "Not starting with localhost" ); if ( PageHandler._localNetworkRegex.test( adress ) ) { // console.log( "Is starting with localhost regex" ); return true; } // console.log( "Not starting with localhost regex" ); return false; } get isFileMode() { return this._isFileMode; } async initialize() { this._isLoading = true; let pagesPath = this.getAbsolutePath( this._pagesPath ) + "?" + new Date().getTime() ; //console.log( "loading pages", pagesPath ); let pages:string[] = []; setTimeout( ()=> { // console.log( "PAGES_DATA", ( window as any ).PAGES_DATA ); }, 3000 ); if ( ( window as any ).PAGES_DATA ) { //console.log( "USING PAGE DATA FROM JS PAGES" ); pages = ( window as any ).PAGES_DATA as string[]; } else { console.log( "LOADING PAGE DATA FROM JSON", this.isLocalNetwork, pagesPath ); let pagesData = await Loader.loadJSON( pagesPath ); pages = pagesData.pages; } for ( let i = 0; i < pages.length; i++ ) { let page = pages[ i ]; let absolutePath = this.getAbsolutePath( page ); //console.log( "adding page: ", page, ">>", absolutePath ); this._pages.set( page, new PageData( this, absolutePath, page ) ); } this._pagesLoaded = true; this._startPage = this.currentPage; ///console.log( "loading pages", this._startPage ); this.removeFunctionHandleLink(); this.resolveRootPaths( document ); this._isLoading = false; this._startUpdater(); if ( this._followUpPage ) { this._loadPage( this._followUpPage ); } return Promise.resolve(); } private _currentLanguage:string = null; get currentLanguage() { if ( ! this._currentLanguage ) { this._currentLanguage = document.querySelector( "html" ).getAttribute( "lang" ) || "en"; } return this._currentLanguage; } get languageCode() { return this.currentLanguage as LanguageCode; } resolveRootPaths( target:Element|Document ) { let filePath = this.currentPage; // console.log( "resolveRootPaths: FILEPATH:" + filePath ); if ( filePath === null ) { return; } this._rootPathResolver.process( target, filePath ); } private async _loadPage( page:string ):Promise { if ( this._isLoading ) { this._followUpPage = page; return Promise.resolve(); } this._dispatchPageRequest( this._currentPage, page ); this._isLoading = true; this._followUpPage = null; this._nextPage = page; let next = this._nextPage; if ( this.transitionHandler ) { await this.transitionHandler.onTransitionOut( this ); } let pageData = await this._getPageData( next ); if ( pageData == null ) { console.log( "No page data found:", page, next ); } this._currentLanguage = pageData.language; let elementType = new ElementType( this.pageRootTag ); let pageRoot = elementType.query( document.documentElement ); pageRoot.innerHTML = pageData.pageRootInnerHTML; this.resolveRootPaths( pageRoot ); this._scrollToY( pageData.scrollTop ); document.title = pageData.title; document.querySelector( "html" ).setAttribute( "lang", this._currentLanguage ); this._abortPreloadingImages = false; if ( this._preloadImagesFlag ) { await Promise.race( [ sleep( this._maxPreloadingDurationMS ), this._preloadImages() ]) } this._abortPreloadingImages = true; if ( this.transitionHandler ) { await this.transitionHandler.onTransitionIn( this ); } this._currentPage = this._nextPage; if ( this._followUpPage ) { let page = this._followUpPage; this._followUpPage = null; await this._loadPage( page ); } this._isLoading = false; return Promise.resolve(); } private _pageAliases:Map = new Map(); public normalizePageLink( page:string ) { return this._normalizePageLink( page ); } private _normalizePageLink( page:string ) { if ( page === "" ) { return this._isHTMLMode ? "index.html" : "index.php"; } if ( this._pages.has( page ) ) { return page; } if ( this._pageAliases.has( page ) ) { page = this._pageAliases.get( page ); } if ( this._pages.has( page ) ) { return page; } if ( ! this._isHTMLMode && page.endsWith( ".html" ) ) { let phpPage = page.replace( /\.html$/, ".php" ); if ( this._pages.has( phpPage ) ) { return phpPage; } } let ending = this._isHTMLMode ? ".html" : ".php"; if ( ! page.endsWith( ending ) ) { let indexPage = page + ending; if ( this._pages.has( indexPage ) ) { return indexPage; } let indexDirectoryPage = page.replace( /\/$/, "" ) + "/index" + ending; if ( this._pages.has( indexDirectoryPage ) ) { return indexDirectoryPage; } } if ( PageHandler.isHiddenPage() ) { return null; } // console.log( "Page not found:", page, [...this._pages.keys()].join( ", " ) ); return null; } static isHiddenPage() { let value = PageHandler.pageInfoAttribute.from( document.body ); return value === "hidden"; } static readonly pageInfoAttribute = new ElementAttribute( "page-info" ); getFilteredPages( filter:RegExp ) { let pages:string[] = []; this._pages.forEach( ( v, k )=> { if ( filter.test( k ) ) { pages.push( k ); } } ); return pages; } async getPage( page:string ):Promise { console.log( "getPage:", page ); return this._getPageData( page ); } private async _getPageData( page:string ):Promise { if ( ! this._pages.has( page ) ) { return Promise.resolve( null ); } let data = this._pages.get( page ); if ( ! data.ready ) { await data.load(); } return Promise.resolve( data ); } private _abortPreloadingImages = false; private async _preloadImages():Promise { let images = ElementType.image.queryAll( document.body ); let backgroundImages = document.body.querySelectorAll( `[style*="background-image"]` ); //console.log( "preloading images", `found: ${images.length}, [style*="background-image"] ${backgroundImages.length}`, ) for ( let i = 0; i < images.length && ! this._abortPreloadingImages; i++ ) { let path = images[ i ].getAttribute( "src" ); if ( path && path !== "" ) { await this._preloadImage( path ); } } for ( let i = 0; i < backgroundImages.length && ! this._abortPreloadingImages; i++ ) { let htmlElement = backgroundImages[ i ] as HTMLElement; let backgroundImageValue = htmlElement.style.backgroundImage; var regexResult = backgroundImageValue.match(/url\(["']?([^"']*)["']?\)/); if ( regexResult && regexResult[ 1 ] ) { let path = regexResult[ 1 ]; await this._preloadImage( path ); } } return Promise.resolve(); } private async _preloadImage( path:string ):Promise { try { //console.log( "preloading image", path ); await Loader.loadImage( path ); } catch ( e ) { } return Promise.resolve(); } getAbsolutePath( path:string ) { if ( this.isFileMode ) { return this._fileLocation + "/" + path } else if ( this.isLocalNetwork ) { return this.localAdress + "/" + path } else { return this._webAdress + "/" + path; } } getRootPath( absolutePath:string ) { let pagePath:string = null; if ( this.isFileMode ) { pagePath = absolutePath.substring( this._fileLocation.length ); } else if ( this.isLocalNetwork ) { pagePath = absolutePath.substring( this.localAdress.length ); } else { if ( absolutePath.startsWith( "http:" ) && this._forceHTTPS ) { absolutePath = "https:" + absolutePath.substring( "http:".length ); } pagePath = absolutePath.substring( this._webAdress.length ); } if ( pagePath.startsWith( "/" ) ) { pagePath = pagePath.substring( 1 ); } return pagePath; } get trimmedLocation() { let trimmedLocation = window.location.href; let hash = /#.*$/.exec( trimmedLocation ); if ( hash ) { trimmedLocation = trimmedLocation.replace( /#.*$/, "" ); } let search = /\?.*$/.exec( trimmedLocation ); if ( search ) { trimmedLocation = trimmedLocation.replace( /\?.*$/, "" ); } return trimmedLocation; } get hasFunctionHandleLink() { let trimmedLocation = window.location.href; let hash = /#\[.*\]$/.exec( trimmedLocation ); if ( hash ) { return true; } return false; } removeFunctionHandleLink() { if ( this.hasFunctionHandleLink ) { window.location.href = this.trimmedLocation; } } get currentPage() { let location = this.getRootPath( this.trimmedLocation ); let normalized = this._normalizePageLink( location ); return normalized; } private _startUpdater() { if ( 'scrollRestoration' in window.history ) { window.history.scrollRestoration = 'manual'; } let page = this.currentPage; this._currentPage = page; this._nextPage = page; let updater = () => { requestAnimationFrame( ()=> { this._update(); updater(); } ); } updater(); } get currentScrollTop() { var value= typeof window.pageYOffset !== undefined ? window.pageYOffset: document.documentElement.scrollTop? document.documentElement.scrollTop: document.body.scrollTop? document.body.scrollTop:0; return value; } replaceLinks() { let anchors = ElementType.anchor.queryAll( document.body ); for ( let a of anchors ) { this._processLink( a as HTMLAnchorElement ); } } private _dispatchPageRequest( lastPage:string, nextPage:string ) { let pageRequestEvent = new PageRequestEvent(); pageRequestEvent.lastPage = lastPage; pageRequestEvent.nextPage = nextPage; this.onPageRequest.dispatch( pageRequestEvent ); } createAbsoluteLink( link:Link ) { let clone = link.clone(); if ( LinkType.ABSOLUTE === link.type ) { return clone; } clone.type = LinkType.ABSOLUTE; if ( LinkType.ROOT === link.type ) { clone.path = link.path.replace( "::", window.location.host ); } if ( LinkType.RELATIVE === link.type ) { clone.path = new URL( link.path, window.location.href ).href; } return clone; } createRootLink( link:Link ) { if ( link.type === LinkType.ROOT ) { return link.clone(); } let absoluteLink = this.createAbsoluteLink( link ); let clone = absoluteLink.clone(); clone.type = LinkType.ROOT; clone.path = RootPathResolver.rootToken + ( new URL( absoluteLink.path ).pathname ); return clone; } getRealPagePath( page:string ) { page = page.replace( /^\//, "" ); if ( this._pages.has( page ) ) { return page; } let phpExtensionPage = page + ".php"; if ( this._pages.has( phpExtensionPage ) ) { return page; } let directoryWithIndexPage = page + "/index.php"; if ( this._pages.get( directoryWithIndexPage ) ) { return directoryWithIndexPage; } console.warn( "Page not found:", page ); return null; } createRelativeLink( link:Link ) { if ( link.type === LinkType.RELATIVE ) { return link.clone(); } let clone = this.createRootLink( link ).clone(); clone.type = LinkType.RELATIVE; let replaced = link.path.replace( RootPathResolver.rootToken, "" ); let file = RegExpUtility.fileNameOrLastPath( replaced ); let pathDirectory = RegExpUtility.parentPath( this._normalizePageLink( replaced ) ); let currentDirectory = this.currentPath; let isDirectoryWithoutSlash = false; let directoryPrefix = ""; if ( ! window.location.pathname.endsWith( "/" ) ) { let windowPath = window.location.pathname.replace( /^\//, "" ); let windowWithoutSlashPath = windowPath + "/index.php"; let normalizedPath = this._normalizePageLink( windowPath ); isDirectoryWithoutSlash = normalizedPath === windowWithoutSlashPath; console.log( "CREATED RELATIVE INFO", windowPath, normalizedPath, isDirectoryWithoutSlash ); if ( isDirectoryWithoutSlash ) { directoryPrefix = RegExpUtility.fileNameOrLastPath( windowPath ); } } let path = RegExpUtility.createRelativeDirectoryPath( currentDirectory, pathDirectory ); if ( path != "" && ! path.endsWith( "/" ) ) { path += "/"; } path += file; if ( isDirectoryWithoutSlash ) { path = directoryPrefix + "/" + path; } console.log( "CREATED RELATIVE LINK:", link.path, ">> from ", currentDirectory, isDirectoryWithoutSlash, "to", pathDirectory, ">>", path ); clone.path = path; return clone; } convertLink( link:Link, type:LinkType ) { if ( link.type === type ) { return link.clone(); } if ( LinkType.ABSOLUTE == type ) { return this.createAbsoluteLink( link ); } if ( LinkType.ROOT == type ) { return this.createRootLink( link ); } if ( LinkType.RELATIVE == type ) { return this.createRelativeLink( link ); } return null; } setPage( href:string, loadPage:boolean = true ) { let normalizedPath:string = null; if ( RootPathResolver.isRootPath( href ) ) { let resolvedRootPath = RootPathResolver.clearRootPath( href ); normalizedPath = this._normalizePageLink( resolvedRootPath ); this._pages.get( normalizedPath ).scrollTop = 0; console.log( "LOADING ROOT ADRESS", { href, resolvedRootPath, normalizedPath } ); let sourceDirectory = this.currentPath; let targetDirectory = RegExpUtility.parentPath( normalizedPath ); let relativePath = RegExpUtility.createRelativeDirectoryPath( sourceDirectory, targetDirectory ); let targetFile = RegExpUtility.fileNameOrLastPath( normalizedPath ); let historyAdress = "." + RegExpUtility.join( relativePath, targetFile ); let historyAdressShort = historyAdress.replace( /\/?index\.php$/, "" ); historyAdressShort = historyAdressShort.replace( /\.php$/, "" ); console.log( "PUSHING STATE", historyAdress, historyAdressShort ); history.pushState( { historyAdressShort }, "", historyAdressShort ); if ( ! loadPage ) { this._nextPage = historyAdressShort; this._followUpPage = null; } } else { let pageAdress = this.resolveRelativeLink( href ); normalizedPath = this._normalizePageLink( pageAdress ); console.log( "LOADING RELATIVE ADRESS", { href, pageAdress, normalizedPath } ); this._pages.get( normalizedPath ).scrollTop = 0; history.pushState( { href }, "", href ); if ( ! loadPage ) { this._nextPage = href; this._followUpPage = null; } } } static pageHandlerLink = new ElementAttribute( "page-handler-link" ); makePageHandleLink( a:HTMLAnchorElement ) { let originalHref = ElementAttribute.href.from( a, "" ); if ( originalHref.startsWith( "https://" ) || originalHref.startsWith( "https://" ) ) { AttributeValue.target_blank.set( a ); return; } else { AttributeValue.target_blank.removeFrom( a ); } if ( PageHandler.pageHandlerLink.in( a ) ) { return; } PageHandler.pageHandlerLink.to( a ); let pageHandlingAttribute = "data-page-handling"; a.addEventListener( "mousedown", ( e:MouseEvent )=> { let hasRequest = e.button === 0; a.setAttribute( pageHandlingAttribute, hasRequest + "" ); } ); a.addEventListener( "click", ( e:MouseEvent)=> { let href = a.getAttribute( "href" ); let hasRequest = a.getAttribute( pageHandlingAttribute ) === "true"; if ( hasRequest ) { a.removeAttribute( "href" ); /* let pageAdress = this.resolveRelativeLink( href ); pageAdress = this._normalizePageLink( pageAdress ); console.log( "href", `"${href}"`, "pageAdress", `"${pageAdress}"` ); this._pages.get( pageAdress ).scrollTop = 0; history.pushState( { href }, "", href ); */ this.setPage( href ); e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); setTimeout( ()=> { a.setAttribute( "href", href ); }, 200 ) } } ) } static isFunctionHandleLink( link:string ) { return link.startsWith( "#[" ) && link.endsWith( "]" ); } static makeFunctionHandleLink( a:HTMLAnchorElement ) { let element = a as FunctionHandleAnchor; if ( element._assignedClickListener ) { return; } element._assignedClickListener = true; let hrefValue = ElementAttribute.href.from( a ); OnMouseDown.add( a, ( e:MouseEvent )=> { ElementAttribute.href.removeFrom( a ); } ); OnMouseEnter.add( a, ( e:MouseEvent )=> { ElementAttribute.href.to( a, hrefValue ); } ); /* OnContextMenu.add( a, ( e:MouseEvent )=> { e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); } ); */ OnClick.add( a, ( e:MouseEvent)=> { console.log( "FUNCTION HANDLE CLICK", e ); e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); if ( element._functionCallback ) { element._functionCallback(); } setTimeout( ()=> { ElementAttribute.href.to( a, hrefValue ); }, 200 ) } ) } static setFunctionHandleLink( e:Element, name:string, callback:()=>void ) { let a = e as HTMLAnchorElement; this.setFunctionHandleName( a, name ); this.setFunctionHandleCallback( a, callback ); this.makeFunctionHandleLink( a ); } static setFunctionHandleName( a:HTMLAnchorElement, name:string ) { ElementAttribute.href.to( a, `#[ ${name} ]`); } static setFunctionHandleCallback( a:HTMLAnchorElement, callback:()=>void ) { let handle = a as FunctionHandleAnchor; handle._functionCallback = callback; } private _processLink( a:HTMLAnchorElement ) { let hrefValue = ElementAttribute.href.from( a ); if ( ! hrefValue ) { return; } if ( hrefValue.startsWith( "http" ) || hrefValue.startsWith( "mailto" ) ) { return; } if ( hrefValue.startsWith( "#" ) ) { return; } if ( PageHandler.isFunctionHandleLink( hrefValue ) ) { PageHandler.makeFunctionHandleLink( a ); return; } if ( AttributeValue.target_blank.in( a ) ) { console.log( "Not processing blank link:", ElementAttribute.href.from( a ), a ); return; } else { console.log( "Processing blank link:", ElementAttribute.href.from( a ), a ); } this.makePageHandleLink( a ); } private get currentPath():string { let relativePagePath = this.getRootPath( this.trimmedLocation ); let currentPath = this._normalizePageLink( relativePagePath ); if ( currentPath.endsWith( ".html" ) || currentPath.endsWith( ".php" ) ) { let end = currentPath.lastIndexOf( "/" ); if ( end === -1 ) { console.log( "No slash in path", currentPath ); return ""; } currentPath = currentPath.substring( 0, end ); } if ( currentPath.startsWith( "/" ) ) { currentPath = currentPath.substring( 1 ); } return currentPath; } private resolveRelativeLink( link:string ) { let currentPath = this.currentPath; if ( ! window.location.pathname.endsWith( "/" ) ) { let windowPath = window.location.pathname.replace( /^\//, "" ); let windowExtendedPath = windowPath + "/index.php"; let normalizedPath = this._normalizePageLink( windowPath ); if ( normalizedPath === windowExtendedPath ) { currentPath = RegExpUtility.parentPath( currentPath ); console.log( "CREATED SHORTER PATH:", this.currentPath, ">>", currentPath ); } else { console.log( "CREATED PATH IS OK:", { windowPath, windowExtendedPath, normalizedPath, currentPath } ); } } let pathFragments = currentPath === "" ? [] : currentPath.split( "/" ); let linkPathFragments = link.split( "/" ); for ( let i = 0; i < linkPathFragments.length; i++ ) { if ( linkPathFragments[ i ] === ".." ) { pathFragments.pop(); } else { pathFragments.push( linkPathFragments[ i ] ); } } let resolvedPath = pathFragments.join( "/" ); console.log( "resolving: ", this.currentPath, link, ">>", resolvedPath ); return resolvedPath; } setPageWithoutLoading( rootPath:string ) { //this._nextPage = page; //this._followUpPage = page; //this.loadPage( page ); rootPath = rootPath.replace( /\.php$/, "" ); let page = "::/" + rootPath; let relativeLink = this.createRelativeLink( Link.Root( page ) ); let relative = relativeLink.path; this._nextPage = relativeLink.path; console.log( "setPageWithoutLoading", { relative, page, rootPath } ); this.setPage( relative, false ); } getRootPathNextPage() { let link = Link.Relative( this._nextPage ); let abs = this.createRootLink( link ); abs.path = abs.path.replace( /^\:\:\/\//, "" ); //console.log( link.path, abs.path ); return this._normalizePageLink( abs.path ); } private _update() { if ( this._isLoading ) { return; } let location = this.getRootPath( this.trimmedLocation ); location = this._normalizePageLink( location ); let rootPathNextPage = this.getRootPathNextPage(); if ( rootPathNextPage !== location && this._nextPage !== location ) { console.log( { current: location, next: this._nextPage, normalizedNext: rootPathNextPage } ); this._loadPage( location ); } else { let pageData = this._pages.get( this._currentPage ); if ( pageData ) { pageData.scrollTop = this.currentScrollTop; } else { if ( ! PageHandler.isHiddenPage() ) { console.log( this._currentPage, this.currentPage, "NO PAGE DATA" ); } } } } private _scrollToY( y:number ) { document.documentElement.scrollTop = y; document.body.scrollTop = y; } }