The following code is just the concept of "Virtual DOM" but written in python:
import copy
class Node:
def __init__(self, tag, attributes, children):
self.tag = tag
self.attributes = attributes
self.children = children
class VirtualDOM:
def __init__(self, node):
self.root = node
self.current = node
def render(self):
return self.root.render()
def diff(self, new_node):
patches = []
self._diff(self.root, new_node, patches)
self.root = new_node
self.current = new_node
return patches
def _diff(self, old_node, new_node, patches):
if old_node.tag != new_node.tag:
patches.append({'action': 'replace', 'node': copy.deepcopy(old_node)})
elif old_node.attributes != new_node.attributes:
patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
elif len(old_node.children) != len(new_node.children):
patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
else:
for i in range(len(old_node.children)):
self._diff(old_node.children[i], new_node.children[i], patches)
return patches
And this is how you can test this code:
# Create the root node
attributes = {'class': 'container'}
root = Node('div', attributes, [])
# Create a Virtual DOM object
vdom = VirtualDOM(root)
# Create a new DOM node
new_attributes = {'class': 'container', 'id': 'main'}
new_node = Node('div', new_attributes, [])
# Get the patches
patches = vdom.diff(new_node)
print(patches)
# Output: [{'action': 'update', 'node': <Node tag="div", attributes={'class': 'container'}>}]
And then, we wrote the same concept of virtual DOM with ability to render reliable HTML pages:
import copy
class Node:
def __init__(self, tag, attributes, children):
self.tag = tag
self.attributes = attributes
self.children = children
def render(self):
out = "<" + self.tag
if self.attributes:
for attribute, value in self.attributes.items():
out += " " + attribute + "=" + value
out += ">"
for child in self.children:
out += child.render()
out += "</" + self.tag + ">"
return out
class VirtualDOM:
def __init__(self, node):
self.root = node
self.current = node
def render(self):
return self.root.render()
def diff(self, new_node):
patches = []
self._diff(self.root, new_node, patches)
self.root = new_node
self.current = new_node
return patches
def _diff(self, old_node, new_node, patches):
if old_node.tag != new_node.tag:
patches.append({'action': 'replace', 'node': copy.deepcopy(old_node)})
elif old_node.attributes != new_node.attributes:
patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
elif len(old_node.children) != len(new_node.children):
patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
else:
for i in range(len(old_node.children)):
self._diff(old_node.children[i], new_node.children[i], patches)
return patches
def apply_patches(self, patches):
for patch in patches:
if patch['action'] == 'replace':
self.root = copy.deepcopy(patch['node'])
elif patch['action'] == 'update':
self.root.attributes = copy.deepcopy(patch['node'].attributes)
return self.render()
# Create the root node
attributes = {'class': 'container'}
root = Node('div', attributes, [])
# Create a Virtual DOM object
vdom = VirtualDOM(root)
# Create a new DOM node
new_attributes = {'class': 'container', 'id': 'main'}
new_node = Node('div', new_attributes, [])
# Get the patches
patches = vdom.diff(new_node)
# Apply the patches
print(vdom.apply_patches(patches))
# Output: <div class="container" id="main"></div>
And this is a "hello world" page which uses bootstrap, has a navbar and a container in the middle which says "Hello World" with the same concept:
import copy
class Node:
def __init__(self, tag, attributes, children):
self.tag = tag
self.attributes = attributes
self.children = children
def render(self):
out = "<" + self.tag
if self.attributes:
for attribute, value in self.attributes.items():
out += " " + attribute + "=" + value
out += ">"
for child in self.children:
out += child.render()
out += "</" + self.tag + ">"
return out
class VirtualDOM:
def __init__(self, node):
self.root = node
self.current = node
def render(self):
return self.root.render()
def diff(self, new_node):
patches = []
self._diff(self.root, new_node, patches)
self.root = new_node
self.current = new_node
return patches
def _diff(self, old_node, new_node, patches):
if old_node.tag != new_node.tag:
patches.append({'action': 'replace', 'node': copy.deepcopy(old_node)})
elif old_node.attributes != new_node.attributes:
patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
elif len(old_node.children) != len(new_node.children):
patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
else:
for i in range(len(old_node.children)):
self._diff(old_node.children[i], new_node.children[i], patches)
return patches
def apply_patches(self, patches):
for patch in patches:
if patch['action'] == 'replace':
self.root = copy.deepcopy(patch['node'])
elif patch['action'] == 'update':
self.root.attributes = copy.deepcopy(patch['node'].attributes)
return self.render()
# Create the root node
attributes = {'class': 'container'}
header_attributes = {'class': 'navbar'}
html_attributes = {'lang': 'en'}
root = Node('html', html_attributes, [
Node('head', {}, []),
Node('body', {}, [
Node('header', header_attributes, []),
Node('div', attributes, [
Node('p', {}, ['Hello World!'])
])
])
])
# Create a Virtual DOM object
vdom = VirtualDOM(root)
print(vdom.render())
# Output:
# <html lang="en">
# <head></head>
# <body>
# <header class="navbar"></header>
# <div>
# <p>Hello World!</p>
# </div>
# </body>
# </html>