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)
|
2017-06-26 18:37:04 +00:00
|
|
|
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
|
|
|
|
|
2017-07-19 02:47:32 +00:00
|
|
|
// 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()
|
2017-07-19 14:56:02 +00:00
|
|
|
|
|
|
|
// if(this.dataset.bubble !== "true") {
|
|
|
|
// e.stopPropagation()
|
|
|
|
// }
|
2017-06-19 14:20:46 +00:00
|
|
|
|
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
|
|
|
}
|