Update DOM node properties and listeners on renderer update (#117)

* Update DOM properties and listeners on renderer update

* Fix listeners not passed in EmptyView init
This commit is contained in:
Max Desiatov 2020-06-29 00:09:28 +01:00 committed by GitHub
parent b7d7b125b2
commit 9bed8e0cb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 44 additions and 18 deletions

View File

@ -20,10 +20,31 @@ import TokamakCore
public final class DOMNode: Target { public final class DOMNode: Target {
let ref: JSObjectRef let ref: JSObjectRef
private var listeners: [String: JSClosure]
init<V: View>(_ view: V, _ ref: JSObjectRef) { init<V: View>(_ view: V, _ ref: JSObjectRef, _ listeners: [String: Listener] = [:]) {
self.ref = ref self.ref = ref
self.listeners = [:]
super.init(view) super.init(view)
reinstall(listeners)
}
/// Removes all existing event listeners on this DOM node and install new ones from
/// the `listeners` argument
func reinstall(_ listeners: [String: Listener]) {
for (event, jsClosure) in self.listeners {
_ = ref.removeEventListener!(event, jsClosure)
}
self.listeners = [:]
for (event, listener) in listeners {
let jsClosure = JSClosure {
listener($0[0].object!)
return .undefined
}
_ = ref.addEventListener!(event, jsClosure)
self.listeners[event] = jsClosure
}
} }
} }
@ -36,7 +57,11 @@ public final class DOMRenderer: Renderer {
public init<V: View>(_ view: V, _ ref: JSObjectRef) { public init<V: View>(_ view: V, _ ref: JSObjectRef) {
rootRef = ref rootRef = ref
reconciler = StackReconciler(view: view, target: DOMNode(view, ref), renderer: self) { closure in reconciler = StackReconciler(
view: view,
target: DOMNode(view, ref),
renderer: self
) { closure in
let fn = JSClosure { _ in let fn = JSClosure { _ in
closure() closure()
return .undefined return .undefined
@ -67,14 +92,7 @@ public final class DOMRenderer: Renderer {
let lastChild = children[Int(length) - 1].object let lastChild = children[Int(length) - 1].object
else { return nil } else { return nil }
for (event, listener) in listeners { return DOMNode(host.view, lastChild, listeners)
_ = lastChild.addEventListener!(event, JSClosure {
listener($0[0].object!)
return .undefined
})
}
return DOMNode(host.view, lastChild)
} }
public func update(target: DOMNode, with host: MountedHost) { public func update(target: DOMNode, with host: MountedHost) {
@ -83,7 +101,7 @@ public final class DOMRenderer: Renderer {
transform: { (html: AnyHTML) in html } transform: { (html: AnyHTML) in html }
) else { return } ) else { return }
html.update(dom: target.ref) html.update(dom: target)
} }
public func unmount( public func unmount(

View File

@ -42,10 +42,21 @@ extension AnyHTML {
""" """
} }
func update(dom: JSObjectRef) { func update(dom: DOMNode) {
// FIXME: handle attributes and listeners here // FIXME: is there a sensible way to diff attributes and listeners to avoid
// crossing the JavaScript bridge and touching DOM if not needed?
// @carson-katri: For diffing, could you build a Set from the keys and values of the dictionary,
// then use the standard lib to get the difference?
for (attribute, value) in attributes {
_ = dom.ref[dynamicMember: attribute] = JSValue(stringLiteral: value)
}
dom.reinstall(listeners)
guard let innerHTML = innerHTML else { return } guard let innerHTML = innerHTML else { return }
dom.innerHTML = .string(innerHTML) dom.ref.innerHTML = .string(innerHTML)
} }
} }
@ -80,10 +91,7 @@ extension HTML where Content == EmptyView {
_ attributes: [String: String] = [:], _ attributes: [String: String] = [:],
listeners: [String: Listener] = [:] listeners: [String: Listener] = [:]
) { ) {
self.tag = tag self = HTML(tag, attributes, listeners: listeners) { EmptyView() }
self.attributes = attributes
self.listeners = listeners
content = EmptyView()
} }
} }