GEDOM is a framework-like tool that is meant to be used in any web application to simplify DOM Manipulation. It uses the concept of DOM Abstraction to create web components without having to use all those DOM methods.
It also uses a global State Management to have a control over every changes in the application.
It finally uses a Router to make sure the suitable components are rendered in the current adrress.
As an example, GEDOM will be use to create a basic TodoMVC web application.
Click on badges to get to the code...
$ git clone http://learn.zone01dakar.sn/git/jefaye/mini-framework
$ cd mini-framework/
$ node src/server.js.
├── src
│ ├── components
│ │ ├── atoms
│ │ │ ├── atoms.md
│ │ │ ├── button.js
│ │ │ ├── clear-completed.js
│ │ │ ├── input.js
│ │ │ ├── label.js
│ │ │ ├── link.js
│ │ │ ├── text.js
│ │ │ └── todo-count.js
| | |
│ │ ├── molecules
│ │ │ ├── item.js
│ │ │ ├── molecules.md
│ │ │ └── todo-list.js
| | |
│ │ ├── organisms
│ │ │ ├── organisms.md
│ │ │ ├── todo-footer.js
│ │ │ ├── todo-header.js
│ │ │ └── todo-main.js
| | |
│ │ ├── pages
│ │ │ ├── error.js
│ │ │ └── pages.md
| | |
│ │ └── templates
│ │ ├── app-todo.js
│ │ ├── footer.js
│ │ └── templates.md
| |
│ ├── core
│ │ ├── node.js
│ │ ├── router.js
│ │ └── state.js
| |
│ ├── styles
│ │ ├── error.css
│ │ └── index.css
| |
│ ├── app.js
│ ├── index.html
│ └── server.js
|
├── .gitignore
├── audit.todo
├── gitify.sh
├── LICENSE
└── README.md
9 directories, 32 files
The Virtual Node is a class that generates a component, given a 'properties' object as arguments.
Basically, the properties object would be in this configuration:
const properties = {
tag: /* HTML tag name (default: 'div') */ ,
attrs: {
// attribute: value
// ...
},
listeners: {
// event: callback function
// ...
},
children: [
// VirtualNode || Object || String
// ...
]
}Calling the render() method on an instance of a Virtual Node will create the element through the tag field value, then set attributes and listeners, then add all given children of one of these types:
virtual node
const TodoApp = new VirtualNode({
tag: "section",
attrs: {
class: "todoapp",
},
children: [
todoHeader,
todoMain,
todoFooter
],
});object
class TodoHeader extends VirtualNode {
constructor() {
super({
tag: "header",
attrs: {
class: "header",
},
children: [
{
tag: "h1",
children: ["todos"],
},
{
tag: "input",
attrs: {
class: "new-todo",
placeholder: "What needs to be done?",
autofocus: "",
},
listeners: {
onchange: event => {
if (event.target.value.trim() !== "") {
todoList.addTodo(event.target.value);
event.target.value = "";
}
},
},
},
],
});
}
}string
export default new VirtualNode({
tag: "footer",
attrs: {
class: "info",
},
children: [
{
tag: "p",
children: [
"Double-click to edit a todo"
],
},
{
tag: "p",
children: [
"Created by the Todo01 team"
],
},
{
tag: "p",
children: [
"Part of ",
new Link ("https://todomvc.com/", "TodoMVC")
],
},
],
});Finally, the VirtualNode class will returns the created element that can now be append to any element of the DOM.
Additionally, the VirtualNode class has some interesting methods to:
Select an element from a virtual node's children using its index.
select(index) {
let child = this.children[index]
if (!child) {
return
}
if (!(child instanceof VirtualNode) && typeof child !== 'string') {
child = new VirtualNode(child)
}
return child
}Add a node to the current node by appending its element to the current node's element or pushing its node to the current node's children.
add(child) {
if (!this.elem) {
this.children.push(child);
return
}
if (typeof child === 'string') {
this.elem.textContent += child;
return
}
if (!(child instanceof VirtualNode)) {
child = new VirtualNode(child)
}
this.elem.appendChild(child.elem || child.render())
}Replace the node content by removing the entire content and append the given element using the add() method or reallocating the current node's children with the new children.
replace(...children) {
if (this.elem) {
this.elem.innerHTML = '';
children.forEach(child => this.add(child));
return
}
this.children = [...newChild];
}The State of the application is set using an object that can contain any sort of data. The power of the state manager resides in the facts that it is the ONE source of truth for the entire application.
First, after being importing, the State class is initially set with all data of the application that can be changed by the components logic using the set() method.
todoState.set({
todos: [],
filter: "all",
counter: {
active: 0,
completed: 0,
}
});The subscribe() method will basically add any funtion, method or callback that needs to update one or more elements of the application.
todoState.subscribe(todoMain.display.bind(todoMain));
todoState.subscribe(todoFooter.display.bind(todoFooter));
todoState.subscribe(todoList.display.bind(todoList));
todoState.subscribe(clearCompleted.display.bind(clearCompleted));
todoState.subscribe(todoCount.refresh.bind(todoCount));Instead of changing an element directly, the modifications will be done in the State.
todoState.set({
todos: todoState.current.todos.filter(todo => !todo.state.completed),
counter: {
...todoState.current.counter,
completed: 0,
},
});The set() method contains a private method (#notify()) that will call all the subscribed functions, which will update all elements without needing to refresh the entire page/template.
notify() {
this.subscribers.forEach(callback => callback(this.current));
}Optionally, the set() method can take a callback as an argument for more complex operations.
The Router as the name implies is supposed to in conjunction with the State manager in order to determine which components will be rendered given the current URI of the address.
Once the router instance is created, the routes are registered using the add() method that takes the endpoint of the URL and the handler that basically apply the rendering of the allowed components.
const router = new Router()
router.add("/", () => {
todoState.set({ filter: "all" });
})
router.add("/active", () => {
todoState.set({ filter: "active" });
});
router.add("/completed", () => {
todoState.set({ filter: "completed" });
});Whenever the address changes, the router get the resulting URL and change the state of the application so it can take care of the rest.
The URL is supposed to be a hash link so it won't redirect to a different page while avoiding page reload.