Skip to content

Commit f9f1a1f

Browse files
committed
Set of demos for vanillajs, redux (and rtk), mobx, mobx-state-tree, rxjs and xstate
0 parents  commit f9f1a1f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+11144
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist
2+
.cache
3+
node_modules

mobx.html

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<html>
2+
3+
<head>
4+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/base.css">
5+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/index.css">
6+
<title>
7+
State Management - MobX
8+
</title>
9+
</head>
10+
11+
<body>
12+
<template id="todo-item">
13+
<li>
14+
<div class="view">
15+
<input class="toggle" type="checkbox">
16+
<label></label>
17+
<button class="destroy"></button>
18+
</div>
19+
<input class="edit">
20+
</li>
21+
</template>
22+
<template id="todo-app">
23+
<section class="todoapp">
24+
<header class="header">
25+
<h1>todos</h1>
26+
<input class="new-todo" placeholder="What needs to be done?" autofocus>
27+
</header>
28+
<section class="main">
29+
<input id="toggle-all" class="toggle-all" type="checkbox">
30+
<label for="toggle-all">
31+
Mark all as complete
32+
</label>
33+
<ul class="todo-list" data-component="todos">
34+
</ul>
35+
</section>
36+
<footer class="footer">
37+
<span class="todo-count" data-component="counter">
38+
</span>
39+
<ul class="filters" data-component="filters">
40+
<li>
41+
<a href="#/">All</a>
42+
</li>
43+
<li>
44+
<a href="#/active">Active</a>
45+
</li>
46+
<li>
47+
<a href="#/completed">Completed</a>
48+
</li>
49+
</ul>
50+
<button class="clear-completed">
51+
Clear completed
52+
</button>
53+
</footer>
54+
</section>
55+
</template>
56+
<div id="root">
57+
<div data-component="app"></div>
58+
</div>
59+
<footer class="info">
60+
<p>Double-click to edit a todo</p>
61+
<p>Created by <a href="http://twitter.com/thestrazz86">Francesco Strazzullo</a></p>
62+
<p>Thanks to <a href="http://todomvc.com">TodoMVC</a></p>
63+
</footer>
64+
<script src="./mobx/index.js"></script>
65+
</body>
66+
67+
</html>

mobx/applyDiff.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const isNodeChanged = (node1, node2) => {
2+
const n1Attributes = node1.attributes
3+
const n2Attributes = node2.attributes
4+
if (n1Attributes.length !== n2Attributes.length) {
5+
return true
6+
}
7+
8+
const differentAttribute = Array
9+
.from(n1Attributes)
10+
.find(attribute => {
11+
const { name } = attribute
12+
const attribute1 = node1
13+
.getAttribute(name)
14+
const attribute2 = node2
15+
.getAttribute(name)
16+
17+
return attribute1 !== attribute2
18+
})
19+
20+
if (differentAttribute) {
21+
return true
22+
}
23+
24+
if (node1.children.length === 0 &&
25+
node2.children.length === 0 &&
26+
node1.textContent !== node2.textContent) {
27+
return true
28+
}
29+
30+
return false
31+
}
32+
33+
const applyDiff = (
34+
parentNode,
35+
realNode,
36+
virtualNode) => {
37+
if (realNode && !virtualNode) {
38+
realNode.remove()
39+
return
40+
}
41+
42+
if (!realNode && virtualNode) {
43+
parentNode.appendChild(virtualNode)
44+
return
45+
}
46+
47+
if (isNodeChanged(virtualNode, realNode)) {
48+
realNode.replaceWith(virtualNode)
49+
return
50+
}
51+
52+
const realChildren = Array.from(realNode.children)
53+
const virtualChildren = Array.from(virtualNode.children)
54+
55+
const max = Math.max(
56+
realChildren.length,
57+
virtualChildren.length
58+
)
59+
for (let i = 0; i < max; i++) {
60+
applyDiff(
61+
realNode,
62+
realChildren[i],
63+
virtualChildren[i]
64+
)
65+
}
66+
}
67+
68+
export default applyDiff

mobx/connect.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mobx/index.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import todosView from './view/todos.js'
2+
import counterView from './view/counter.js'
3+
import filtersView from './view/filters.js'
4+
import appView from './view/app.js'
5+
import applyDiff from './applyDiff.js'
6+
7+
import registry from './registry.js'
8+
import { observable, computed, observe, action } from "mobx"
9+
10+
import connect from './connect.js'
11+
12+
// UI MAPPING
13+
const counterMapper = (state) => state.itemCount
14+
15+
16+
registry.add('app', appView)
17+
registry.add('todos', todosView)
18+
registry.add('counter', connect(counterMapper)(counterView))
19+
registry.add('filters', filtersView)
20+
21+
// STATE
22+
const todos = observable([])
23+
const state = {
24+
todos,
25+
currentFilter: 'All',
26+
itemCount: computed(() => todos.filter(todo => !todo.completed)
27+
.length)
28+
}
29+
30+
// SIDE EFFECTS
31+
const someAsyncCall = () =>
32+
new Promise((resolve) => {
33+
setTimeout(() => resolve(), 1000)
34+
})
35+
36+
const saveToServer = (todos) => {
37+
someAsyncCall(todos).then(() => {
38+
console.log("Todos saved!")
39+
})
40+
}
41+
42+
// EVENTS / TRANSITIONS
43+
const transitions = {
44+
deleteItem: action((index) => {
45+
state.todos.splice(index, 1)
46+
// side effects: start
47+
console.log("item " + index + " deleted")
48+
saveToServer(state.todos)
49+
// side effects: end
50+
}),
51+
addItem: action(text => {
52+
state.todos.push({
53+
text,
54+
completed: false
55+
})
56+
// side effects: start
57+
console.log("item " + text + " added")
58+
saveToServer(state.todos)
59+
// side effects: end
60+
})
61+
}
62+
63+
const render = () => {
64+
window.requestAnimationFrame(() => {
65+
const main = document.querySelector('#root')
66+
67+
const newMain = registry.renderRoot(
68+
main,
69+
state,
70+
transitions)
71+
72+
applyDiff(document.body, main, newMain)
73+
})
74+
}
75+
76+
render()
77+
78+
// UI MAPPING
79+
observe(state.todos, undefined, change => {
80+
render()
81+
})

mobx/registry.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const registry = {}
2+
3+
const renderWrapper = component => {
4+
return (targetElement, state, events) => {
5+
const element = component(targetElement, state, events)
6+
7+
const childComponents = element
8+
.querySelectorAll('[data-component]')
9+
10+
Array
11+
.from(childComponents)
12+
.forEach(target => {
13+
const name = target
14+
.dataset
15+
.component
16+
17+
const child = registry[name]
18+
if (!child) {
19+
return
20+
}
21+
22+
target.replaceWith(child(target, state, events))
23+
})
24+
25+
return element
26+
}
27+
}
28+
29+
const add = (name, component) => {
30+
registry[name] = renderWrapper(component)
31+
}
32+
33+
const renderRoot = (root, state, events) => {
34+
const cloneComponent = root => {
35+
return root.cloneNode(true)
36+
}
37+
38+
return renderWrapper(cloneComponent)(root, state, events)
39+
}
40+
41+
export default {
42+
add,
43+
renderRoot
44+
}

mobx/view/app.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
let template
2+
3+
const getTemplate = () => {
4+
if (!template) {
5+
template = document.getElementById('todo-app')
6+
}
7+
8+
return template
9+
.content
10+
.firstElementChild
11+
.cloneNode(true)
12+
}
13+
14+
const addEvents = (targetElement, events) => {
15+
targetElement
16+
.querySelector('.new-todo')
17+
.addEventListener('keypress', e => {
18+
if (e.key === 'Enter') {
19+
events.addItem(e.target.value)
20+
e.target.value = ''
21+
}
22+
})
23+
}
24+
25+
export default (targetElement, state, events) => {
26+
const newApp = targetElement.cloneNode(true)
27+
28+
newApp.innerHTML = ''
29+
newApp.appendChild(getTemplate())
30+
31+
addEvents(newApp, events)
32+
33+
return newApp
34+
}

mobx/view/counter.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const getTodoCount = length => {
2+
if (length === 1) {
3+
return '1 Item left'
4+
}
5+
6+
return `${length} Items left`
7+
}
8+
9+
export default (targetElement, length) => {
10+
const newCounter = targetElement.cloneNode(true)
11+
newCounter.textContent = getTodoCount(length)
12+
return newCounter
13+
}

mobx/view/counter.test.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import counterView from './counter.js'
2+
3+
let targetElement
4+
5+
describe('counterView', () => {
6+
beforeEach(() => {
7+
targetElement = document.createElement('div')
8+
})
9+
10+
test('should put the number of not completed todo in a new DOM elements', () => {
11+
const newCounter = counterView(targetElement, {
12+
todos: [
13+
{
14+
text: 'First',
15+
completed: true
16+
},
17+
{
18+
text: 'Second',
19+
completed: false
20+
},
21+
{
22+
text: 'Third',
23+
completed: false
24+
}
25+
]
26+
})
27+
expect(newCounter.textContent).toBe('2 Items left')
28+
})
29+
30+
test('should consider the singular form when only one item is left', () => {
31+
const newCounter = counterView(targetElement, {
32+
todos: [
33+
{
34+
text: 'First',
35+
completed: true
36+
},
37+
{
38+
text: 'Third',
39+
completed: false
40+
}
41+
]
42+
})
43+
expect(newCounter.textContent).toBe('1 Item left')
44+
})
45+
})

mobx/view/filters.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export default (targetElement, { currentFilter }) => {
2+
const newCounter = targetElement.cloneNode(true)
3+
Array
4+
.from(newCounter.querySelectorAll('li a'))
5+
.forEach(a => {
6+
if (a.textContent === currentFilter) {
7+
a.classList.add('selected')
8+
} else {
9+
a.classList.remove('selected')
10+
}
11+
})
12+
return newCounter
13+
}

0 commit comments

Comments
 (0)