Added a basic DOM diff algorithm

This commit is contained in:
Eduard Urbach 2017-06-21 18:44:20 +02:00
parent 9bd5e71205
commit b4a4e78c96
6 changed files with 77 additions and 14 deletions

View File

@ -1,4 +1,5 @@
import { Application } from "./Application" import { Application } from "./Application"
import { Diff } from "./Diff"
import { findAll } from "./utils" import { findAll } from "./utils"
import * as actions from "./actions" import * as actions from "./actions"
@ -23,6 +24,14 @@ export class AnimeNotifier {
this.app.run() this.app.run()
} }
reloadContent() {
return fetch("/_" + this.app.currentPath, {
credentials: "same-origin"
})
.then(response => response.text())
.then(html => Diff.innerHTML(this.app.content, html))
}
loading(isLoading: boolean) { loading(isLoading: boolean) {
if(isLoading) { if(isLoading) {
this.app.loading.classList.remove(this.app.fadeOutClass) this.app.loading.classList.remove(this.app.fadeOutClass)

View File

@ -109,6 +109,7 @@ export class Application {
} }
setContent(html: string) { setContent(html: string) {
// Diff.innerHTML(this.content, html)
this.content.innerHTML = html this.content.innerHTML = html
this.ajaxify(this.content) this.ajaxify(this.content)
this.markActiveLinks(this.content) this.markActiveLinks(this.content)

View File

@ -1,5 +1,63 @@
export class Diff { export class Diff {
static update(element: HTMLElement, html: string) { static childNodes(aRoot: HTMLElement, bRoot: HTMLElement) {
element.innerHTML = html let aChild = [...aRoot.childNodes]
let bChild = [...bRoot.childNodes]
let numNodes = Math.max(aChild.length, bChild.length)
for(let i = 0; i < numNodes; i++) {
let a = aChild[i] as HTMLElement
if(i >= bChild.length) {
aRoot.removeChild(a)
continue
}
let b = bChild[i] as HTMLElement
if(i >= aChild.length) {
aRoot.appendChild(b)
continue
}
if(a.nodeName !== b.nodeName || a.nodeType !== b.nodeType) {
aRoot.replaceChild(b, a)
continue
}
if(a.nodeType === Node.ELEMENT_NODE) {
let removeAttributes: Attr[] = []
for(let x = 0; x < a.attributes.length; x++) {
let attrib = a.attributes[x]
if(attrib.specified) {
if(!b.hasAttribute(attrib.name)) {
removeAttributes.push(attrib)
}
}
}
for(let attr of removeAttributes) {
a.removeAttributeNode(attr)
}
for(let x = 0; x < b.attributes.length; x++) {
let attrib = b.attributes[x]
if(attrib.specified) {
a.setAttribute(attrib.name, b.getAttribute(attrib.name))
}
}
}
Diff.childNodes(a, b)
}
}
static innerHTML(aRoot: HTMLElement, html: string) {
let bRoot = document.createElement("main")
bRoot.innerHTML = html
Diff.childNodes(aRoot, bRoot)
} }
} }

View File

@ -1,6 +1,5 @@
import { Application } from "./Application" import { Application } from "./Application"
import { AnimeNotifier } from "./AnimeNotifier" import { AnimeNotifier } from "./AnimeNotifier"
import { Diff } from "./Diff"
// Save new data from an input field // Save new data from an input field
export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaElement) { export function save(arn: AnimeNotifier, input: HTMLInputElement | HTMLTextAreaElement) {
@ -108,11 +107,7 @@ export function addAnimeToCollection(arn: AnimeNotifier, button: HTMLElement) {
throw body throw body
} }
return fetch("/_" + arn.app.currentPath, { return arn.reloadContent()
credentials: "same-origin"
})
.then(response => response.text())
.then(html => Diff.update(arn.app.content, html))
}) })
.catch(console.error) .catch(console.error)
.then(() => arn.loading(false)) .then(() => arn.loading(false))

View File

@ -4,9 +4,7 @@ import { AnimeNotifier } from "./AnimeNotifier"
let app = new Application() let app = new Application()
let arn = new AnimeNotifier(app) let arn = new AnimeNotifier(app)
document.addEventListener("DOMContentLoaded", arn.onContentLoaded.bind(arn))
document.addEventListener("readystatechange", arn.onReadyStateChange.bind(arn)) document.addEventListener("readystatechange", arn.onReadyStateChange.bind(arn))
document.addEventListener("DOMContentLoaded", arn.onContentLoaded.bind(arn))
document.addEventListener("keydown", arn.onKeyDown.bind(arn), false) document.addEventListener("keydown", arn.onKeyDown.bind(arn), false)
window.addEventListener("popstate", arn.onPopState.bind(arn)) window.addEventListener("popstate", arn.onPopState.bind(arn))
// window.addEventListener("resize", arn.onResize.bind(arn))

View File

@ -1,5 +1,7 @@
export function* findAll(className: string) { export function* findAll(className: string) {
let elements = document.getElementsByClassName(className) // getElementsByClassName failed for some reason.
// TODO: Test getElementsByClassName again.
let elements = document.querySelectorAll("." + className)
for(let i = 0; i < elements.length; ++i) { for(let i = 0; i < elements.length; ++i) {
yield elements[i] as HTMLElement yield elements[i] as HTMLElement