188 lines
4.1 KiB
TypeScript
Raw Normal View History

2017-06-26 01:57:29 +00:00
import { Diff } from "./Diff"
2017-06-20 13:46:49 +00:00
class LoadOptions {
addToHistory?: boolean
forceReload?: boolean
}
2017-06-19 14:49:24 +00:00
export class Application {
2017-06-19 14:20:46 +00:00
ajaxClass: string
fadeOutClass: string
2017-06-19 14:43:20 +00:00
activeLinkClass: string
2017-06-19 14:20:46 +00:00
content: HTMLElement
loading: HTMLElement
2017-06-20 10:41:26 +00:00
currentPath: string
originalPath: string
2017-06-19 14:20:46 +00:00
lastRequest: XMLHttpRequest
constructor() {
2017-06-20 10:41:26 +00:00
this.currentPath = window.location.pathname
this.originalPath = window.location.pathname
2017-06-19 14:20:46 +00:00
this.ajaxClass = "ajax"
2017-06-19 14:43:20 +00:00
this.activeLinkClass = "active"
2017-06-19 14:20:46 +00:00
this.fadeOutClass = "fade-out"
}
2017-06-19 15:45:27 +00:00
run() {
this.ajaxify()
this.markActiveLinks()
this.loading.classList.add(this.fadeOutClass)
}
2017-06-19 14:20:46 +00:00
find(id: string): HTMLElement {
return document.getElementById(id)
}
get(url: string): Promise<string> {
2017-06-20 20:54:45 +00:00
if(this.lastRequest) {
this.lastRequest.abort()
this.lastRequest = null
}
2017-06-19 14:20:46 +00:00
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest()
request.onerror = () => reject(new Error("You are either offline or the requested page doesn't exist."))
request.ontimeout = () => reject(new Error("The page took too much time to respond."))
request.onload = () => {
if(request.status < 200 || request.status >= 400)
reject(request.responseText)
else
resolve(request.responseText)
}
request.open("GET", url, true)
request.send()
this.lastRequest = request
})
}
2017-06-20 13:46:49 +00:00
load(url: string, options?: LoadOptions) {
2017-06-26 01:57:29 +00:00
// Start sending a network request
let request = this.get("/_" + url).catch(error => error)
// Parse options
2017-06-20 13:46:49 +00:00
if(!options) {
options = new LoadOptions()
}
if(options.addToHistory === undefined) {
options.addToHistory = true
}
2017-06-26 01:57:29 +00:00
// Set current path
2017-06-20 10:41:26 +00:00
this.currentPath = url
2017-06-19 14:20:46 +00:00
2017-06-26 01:57:29 +00:00
// Add to browser history
if(options.addToHistory)
history.pushState(url, null, url)
2017-06-19 14:20:46 +00:00
2017-06-19 14:43:20 +00:00
let onTransitionEnd = e => {
// Ignore transitions of child elements.
// We only care about the transition event on the content element.
if(e.target !== this.content) {
return
}
// Remove listener after we finally got the correct event.
this.content.removeEventListener("transitionend", onTransitionEnd)
// Wait for the network request to end.
2017-06-19 14:20:46 +00:00
request.then(html => {
2017-06-19 14:43:20 +00:00
// Set content
2017-06-26 01:57:29 +00:00
this.setContent(html, false)
this.scrollToTop()
2017-06-19 14:20:46 +00:00
2017-06-19 14:43:20 +00:00
// Fade animations
2017-06-19 14:20:46 +00:00
this.content.classList.remove(this.fadeOutClass)
this.loading.classList.add(this.fadeOutClass)
2017-06-19 15:45:27 +00:00
// Send DOMContentLoaded Event
this.emit("DOMContentLoaded")
2017-06-19 14:20:46 +00:00
})
2017-06-19 14:43:20 +00:00
}
this.content.addEventListener("transitionend", onTransitionEnd)
2017-06-19 14:20:46 +00:00
this.content.classList.add(this.fadeOutClass)
this.loading.classList.remove(this.fadeOutClass)
2017-06-19 14:43:20 +00:00
this.markActiveLinks()
2017-06-20 10:41:26 +00:00
return request
2017-06-19 14:20:46 +00:00
}
2017-06-26 01:57:29 +00:00
setContent(html: string, diff: boolean) {
if(diff) {
Diff.innerHTML(this.content, html)
} else {
this.content.innerHTML = html
}
2017-06-19 14:20:46 +00:00
this.ajaxify(this.content)
2017-06-19 14:43:20 +00:00
this.markActiveLinks(this.content)
}
markActiveLinks(element?: HTMLElement) {
if(element === undefined)
element = document.body
let links = element.querySelectorAll("a")
for(let i = 0; i < links.length; i++) {
let link = links[i]
let href = link.getAttribute("href")
2017-06-20 10:41:26 +00:00
if(href === this.currentPath)
2017-06-19 14:43:20 +00:00
link.classList.add(this.activeLinkClass)
else
link.classList.remove(this.activeLinkClass)
}
2017-06-19 14:20:46 +00:00
}
ajaxify(element?: HTMLElement) {
if(!element)
element = document.body
let links = element.querySelectorAll("." + this.ajaxClass)
for(let i = 0; i < links.length; i++) {
let link = links[i] as HTMLElement
link.classList.remove(this.ajaxClass)
2017-06-19 14:49:24 +00:00
let self = this
2017-06-19 14:20:46 +00:00
link.onclick = function(e) {
// Middle mouse button should have standard behaviour
if(e.which === 2)
return
let url = this.getAttribute("href")
e.preventDefault()
e.stopPropagation()
2017-06-20 10:41:26 +00:00
if(!url || url === self.currentPath)
2017-06-19 14:20:46 +00:00
return
2017-06-19 15:45:27 +00:00
2017-06-19 14:20:46 +00:00
// Load requested page
2017-06-20 13:46:49 +00:00
self.load(url)
2017-06-19 14:20:46 +00:00
}
}
}
scrollToTop() {
let parent = this.content
while(parent = parent.parentElement) {
parent.scrollTop = 0
}
}
2017-06-19 15:45:27 +00:00
emit(eventName: string) {
document.dispatchEvent(new Event(eventName, {
"bubbles": true,
"cancelable": true
}))
}
2017-06-19 14:49:24 +00:00
}