├── .gitignore
├── favicon.ico
├── index.html
├── lib
└── demo-step-by-step
│ ├── dom.1.js
│ ├── dom.10.js
│ ├── dom.11.js
│ ├── dom.12.js
│ ├── dom.13.js
│ ├── dom.14.js
│ ├── dom.15.js
│ ├── dom.16.js
│ ├── dom.17.js
│ ├── dom.18.js
│ ├── dom.19.js
│ ├── dom.2.js
│ ├── dom.3.js
│ ├── dom.4.js
│ ├── dom.5.js
│ ├── dom.6.js
│ ├── dom.7.js
│ ├── dom.8.js
│ ├── dom.9.js
│ └── dom.js
├── package-lock.json
├── package.json
├── scripts
└── babel.min.js
├── server.js
├── style
├── app.css
├── fontawesome
│ └── all.css
└── webfonts
│ ├── fa-brands-400.eot
│ ├── fa-brands-400.svg
│ ├── fa-brands-400.ttf
│ ├── fa-brands-400.woff
│ ├── fa-brands-400.woff2
│ ├── fa-regular-400.eot
│ ├── fa-regular-400.svg
│ ├── fa-regular-400.ttf
│ ├── fa-regular-400.woff
│ ├── fa-regular-400.woff2
│ ├── fa-solid-900.eot
│ ├── fa-solid-900.svg
│ ├── fa-solid-900.ttf
│ ├── fa-solid-900.woff
│ └── fa-solid-900.woff2
├── todo-app.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # editor
7 | .vscode
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/favicon.ico
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Code your own ReactJS
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.1.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 |
3 | const TinyReact = (function () {
4 | function createElement(type, attributes = {}, ...children) {
5 |
6 | }
7 |
8 | return {
9 | createElement
10 | }
11 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.10.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component (todo)
11 |
12 | const TinyReact = (function () {
13 | function createElement(type, attributes = {}, ...children) {
14 | let childElements = [].concat(...children).reduce(
15 | (acc, child) => {
16 | if (child != null && child !== true && child !== false) {
17 | if (child instanceof Object) {
18 | acc.push(child);
19 | } else {
20 | acc.push(createElement("text", {
21 | textContent: child
22 | }));
23 | }
24 | }
25 | return acc;
26 | }
27 | , []);
28 | return {
29 | type,
30 | children: childElements,
31 | props: Object.assign({ children: childElements }, attributes)
32 | }
33 | }
34 |
35 | const render = function (vdom, container, oldDom = container.firstChild) {
36 | diff(vdom, container, oldDom);
37 | }
38 |
39 | const diff = function (vdom, container, oldDom) {
40 | let oldvdom = oldDom && oldDom._virtualElement;
41 |
42 | if (!oldDom) {
43 | mountElement(vdom, container, oldDom);
44 | }
45 | else if (typeof vdom.type === "function") {
46 | diffComponent(vdom, null, container, oldDom);
47 | }
48 | else if (oldvdom && oldvdom.type === vdom.type) {
49 | if (oldvdom.type === "text") {
50 | updateTextNode(oldDom, vdom, oldvdom);
51 | } else {
52 | updateDomElement(oldDom, vdom, oldvdom);
53 | }
54 |
55 | // Set a reference to updated vdom
56 | oldDom._virtualElement = vdom;
57 |
58 | // Recursively diff children..
59 | // Doing an index by index diffing (because we don't have keys yet)
60 | vdom.children.forEach((child, i) => {
61 | diff(child, oldDom, oldDom.childNodes[i]);
62 | });
63 |
64 | // Remove old dom nodes
65 | let oldNodes = oldDom.childNodes;
66 | if (oldNodes.length > vdom.children.length) {
67 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
68 | let nodeToBeRemoved = oldNodes[i];
69 | unmountNode(nodeToBeRemoved, oldDom);
70 | }
71 | }
72 |
73 | }
74 | }
75 |
76 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
77 | if (!oldComponent) {
78 | mountElement(newVirtualElement, container, domElement);
79 | }
80 | }
81 |
82 | const mountElement = function (vdom, container, oldDom) {
83 | if (isFunction(vdom)) {
84 | return mountComponent(vdom, container, oldDom);
85 | } else {
86 | return mountSimpleNode(vdom, container, oldDom);
87 | }
88 | }
89 |
90 | function isFunction(obj) {
91 | return obj && 'function' === typeof obj.type;
92 | }
93 |
94 | function isFunctionalComponent(vnode) {
95 | let nodeType = vnode && vnode.type;
96 | return nodeType && isFunction(vnode)
97 | && !(nodeType.prototype && nodeType.prototype.render);
98 | }
99 |
100 | function buildFunctionalComponent(vnode, context) {
101 | return vnode.type(vnode.props || {});
102 | }
103 |
104 |
105 | function mountComponent(vdom, container, oldDomElement) {
106 | let nextvDom = null, component = null, newDomElement = null;
107 | if (isFunctionalComponent(vdom)) {
108 | nextvDom = buildFunctionalComponent(vdom);
109 | }
110 |
111 | // Recursively render child components
112 | if (isFunction(nextvDom)) {
113 | return mountComponent(nextvDom, container, oldDomElement);
114 | } else {
115 | newDomElement = mountElement(nextvDom, container, oldDomElement);
116 | }
117 | return newDomElement;
118 |
119 | }
120 |
121 | function unmountNode(domElement, parentComponent) {
122 | domElement.remove();
123 | }
124 |
125 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
126 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
127 | domElement.textContent = newVirtualElement.props.textContent;
128 | }
129 | // Set a reference to the newvddom in oldDom
130 | domElement._virtualElement = newVirtualElement;
131 | }
132 |
133 |
134 |
135 |
136 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
137 | let newDomElement = null;
138 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
139 |
140 | if (vdom.type === "text") {
141 | newDomElement = document.createTextNode(vdom.props.textContent);
142 | } else {
143 | newDomElement = document.createElement(vdom.type);
144 | updateDomElement(newDomElement, vdom);
145 | }
146 |
147 | // Setting reference to vdom to dom
148 | newDomElement._virtualElement = vdom;
149 | if (nextSibling) {
150 | container.insertBefore(newDomElement, nextSibling);
151 | } else {
152 | container.appendChild(newDomElement);
153 | }
154 |
155 | vdom.children.forEach(child => {
156 | mountElement(child, newDomElement);
157 | });
158 |
159 | }
160 |
161 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
162 | const newProps = newVirtualElement.props || {};
163 | const oldProps = oldVirtualElement.props || {};
164 | Object.keys(newProps).forEach(propName => {
165 | const newProp = newProps[propName];
166 | const oldProp = oldProps[propName];
167 | if (newProp !== oldProp) {
168 | if (propName.slice(0, 2) === "on") {
169 | // prop is an event handler
170 | const eventName = propName.toLowerCase().slice(2);
171 | domElement.addEventListener(eventName, newProp, false);
172 | if (oldProp) {
173 | domElement.removeEventListener(eventName, oldProp, false);
174 | }
175 | } else if (propName === "value" || propName === "checked") {
176 | // this are special attributes that cannot be set
177 | // using setAttribute
178 | domElement[propName] = newProp;
179 | } else if (propName !== "children") {
180 | // ignore the 'children' prop
181 | if (propName === "className") {
182 | domElement.setAttribute("class", newProps[propName]);
183 | } else {
184 | domElement.setAttribute(propName, newProps[propName]);
185 | }
186 | }
187 | }
188 | });
189 | // remove oldProps
190 | Object.keys(oldProps).forEach(propName => {
191 | const newProp = newProps[propName];
192 | const oldProp = oldProps[propName];
193 | if (!newProp) {
194 | if (propName.slice(0, 2) === "on") {
195 | // prop is an event handler
196 | domElement.removeEventListener(propName, oldProp, false);
197 | } else if (propName !== "children") {
198 | // ignore the 'children' prop
199 | domElement.removeAttribute(propName);
200 | }
201 | }
202 | });
203 | }
204 |
205 |
206 | return {
207 | createElement,
208 | render
209 | }
210 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.11.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component (todo)
11 | // 11. functional component diffing (remove extra nodes)
12 |
13 | const TinyReact = (function () {
14 | function createElement(type, attributes = {}, ...children) {
15 | let childElements = [].concat(...children).reduce(
16 | (acc, child) => {
17 | if (child != null && child !== true && child !== false) {
18 | if (child instanceof Object) {
19 | acc.push(child);
20 | } else {
21 | acc.push(createElement("text", {
22 | textContent: child
23 | }));
24 | }
25 | }
26 | return acc;
27 | }
28 | , []);
29 | return {
30 | type,
31 | children: childElements,
32 | props: Object.assign({ children: childElements }, attributes)
33 | }
34 | }
35 |
36 | const render = function (vdom, container, oldDom = container.firstChild) {
37 | diff(vdom, container, oldDom);
38 | }
39 |
40 | const diff = function (vdom, container, oldDom) {
41 | let oldvdom = oldDom && oldDom._virtualElement;
42 |
43 | if (!oldDom) {
44 | mountElement(vdom, container, oldDom);
45 | }
46 | else if (typeof vdom.type === "function") {
47 | diffComponent(vdom, null, container, oldDom);
48 | }
49 | else if (oldvdom && oldvdom.type === vdom.type) {
50 | if (oldvdom.type === "text") {
51 | updateTextNode(oldDom, vdom, oldvdom);
52 | } else {
53 | updateDomElement(oldDom, vdom, oldvdom);
54 | }
55 |
56 | // Set a reference to updated vdom
57 | oldDom._virtualElement = vdom;
58 |
59 | // Recursively diff children..
60 | // Doing an index by index diffing (because we don't have keys yet)
61 | vdom.children.forEach((child, i) => {
62 | diff(child, oldDom, oldDom.childNodes[i]);
63 | });
64 |
65 | // Remove old dom nodes
66 | let oldNodes = oldDom.childNodes;
67 | if (oldNodes.length > vdom.children.length) {
68 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
69 | let nodeToBeRemoved = oldNodes[i];
70 | unmountNode(nodeToBeRemoved, oldDom);
71 | }
72 | }
73 |
74 | }
75 | }
76 |
77 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
78 |
79 | if (!oldComponent) {
80 | mountElement(newVirtualElement, container, domElement);
81 | }
82 | }
83 |
84 | const mountElement = function (vdom, container, oldDom) {
85 | if (isFunction(vdom)) {
86 | return mountComponent(vdom, container, oldDom);
87 | } else {
88 | return mountSimpleNode(vdom, container, oldDom);
89 | }
90 | }
91 |
92 | function isFunction(obj) {
93 | return obj && 'function' === typeof obj.type;
94 | }
95 |
96 | function isFunctionalComponent(vnode) {
97 | let nodeType = vnode && vnode.type;
98 | return nodeType && isFunction(vnode)
99 | && !(nodeType.prototype && nodeType.prototype.render);
100 | }
101 |
102 | function buildFunctionalComponent(vnode, context) {
103 | return vnode.type(vnode.props || {});
104 | }
105 |
106 |
107 | function mountComponent(vdom, container, oldDomElement) {
108 | let nextvDom = null, component = null, newDomElement = null;
109 | if (isFunctionalComponent(vdom)) {
110 | nextvDom = buildFunctionalComponent(vdom);
111 | }
112 |
113 | // Recursively render child components
114 | if (isFunction(nextvDom)) {
115 | return mountComponent(nextvDom, container, oldDomElement);
116 | } else {
117 | newDomElement = mountElement(nextvDom, container, oldDomElement);
118 | }
119 | return newDomElement;
120 |
121 | }
122 |
123 | function unmountNode(domElement, parentComponent) {
124 | domElement.remove();
125 | }
126 |
127 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
128 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
129 | domElement.textContent = newVirtualElement.props.textContent;
130 | }
131 | // Set a reference to the newvddom in oldDom
132 | domElement._virtualElement = newVirtualElement;
133 | }
134 |
135 |
136 |
137 |
138 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
139 | let newDomElement = null;
140 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
141 |
142 | if (vdom.type === "text") {
143 | newDomElement = document.createTextNode(vdom.props.textContent);
144 | } else {
145 | newDomElement = document.createElement(vdom.type);
146 | updateDomElement(newDomElement, vdom);
147 | }
148 |
149 | // Setting reference to vdom to dom
150 | newDomElement._virtualElement = vdom;
151 |
152 | // TODO: Remove old nodes
153 | if (oldDomElement) {
154 | unmountNode(oldDomElement, parentComponent);
155 | }
156 |
157 | if (nextSibling) {
158 | container.insertBefore(newDomElement, nextSibling);
159 | } else {
160 | container.appendChild(newDomElement);
161 | }
162 |
163 | vdom.children.forEach(child => {
164 | mountElement(child, newDomElement);
165 | });
166 |
167 | }
168 |
169 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
170 | const newProps = newVirtualElement.props || {};
171 | const oldProps = oldVirtualElement.props || {};
172 | Object.keys(newProps).forEach(propName => {
173 | const newProp = newProps[propName];
174 | const oldProp = oldProps[propName];
175 | if (newProp !== oldProp) {
176 | if (propName.slice(0, 2) === "on") {
177 | // prop is an event handler
178 | const eventName = propName.toLowerCase().slice(2);
179 | domElement.addEventListener(eventName, newProp, false);
180 | if (oldProp) {
181 | domElement.removeEventListener(eventName, oldProp, false);
182 | }
183 | } else if (propName === "value" || propName === "checked") {
184 | // this are special attributes that cannot be set
185 | // using setAttribute
186 | domElement[propName] = newProp;
187 | } else if (propName !== "children") {
188 | // ignore the 'children' prop
189 | if (propName === "className") {
190 | domElement.setAttribute("class", newProps[propName]);
191 | } else {
192 | domElement.setAttribute(propName, newProps[propName]);
193 | }
194 | }
195 | }
196 | });
197 | // remove oldProps
198 | Object.keys(oldProps).forEach(propName => {
199 | const newProp = newProps[propName];
200 | const oldProp = oldProps[propName];
201 | if (!newProp) {
202 | if (propName.slice(0, 2) === "on") {
203 | // prop is an event handler
204 | domElement.removeEventListener(propName, oldProp, false);
205 | } else if (propName !== "children") {
206 | // ignore the 'children' prop
207 | domElement.removeAttribute(propName);
208 | }
209 | }
210 | });
211 | }
212 |
213 |
214 | return {
215 | createElement,
216 | render
217 | }
218 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.12.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component
11 | // 11. functional component diffing (remove extra nodes)
12 | // 12. rendering stateful component (todo)
13 |
14 | const TinyReact = (function () {
15 | function createElement(type, attributes = {}, ...children) {
16 | let childElements = [].concat(...children).reduce(
17 | (acc, child) => {
18 | if (child != null && child !== true && child !== false) {
19 | if (child instanceof Object) {
20 | acc.push(child);
21 | } else {
22 | acc.push(createElement("text", {
23 | textContent: child
24 | }));
25 | }
26 | }
27 | return acc;
28 | }
29 | , []);
30 | return {
31 | type,
32 | children: childElements,
33 | props: Object.assign({ children: childElements }, attributes)
34 | }
35 | }
36 |
37 | const render = function (vdom, container, oldDom = container.firstChild) {
38 | diff(vdom, container, oldDom);
39 | }
40 |
41 | const diff = function (vdom, container, oldDom) {
42 | let oldvdom = oldDom && oldDom._virtualElement;
43 |
44 | if (!oldDom) {
45 | mountElement(vdom, container, oldDom);
46 | }
47 | else if (typeof vdom.type === "function") {
48 | diffComponent(vdom, null, container, oldDom);
49 | }
50 | else if (oldvdom && oldvdom.type === vdom.type) {
51 | if (oldvdom.type === "text") {
52 | updateTextNode(oldDom, vdom, oldvdom);
53 | } else {
54 | updateDomElement(oldDom, vdom, oldvdom);
55 | }
56 |
57 | // Set a reference to updated vdom
58 | oldDom._virtualElement = vdom;
59 |
60 | // Recursively diff children..
61 | // Doing an index by index diffing (because we don't have keys yet)
62 | vdom.children.forEach((child, i) => {
63 | diff(child, oldDom, oldDom.childNodes[i]);
64 | });
65 |
66 | // Remove old dom nodes
67 | let oldNodes = oldDom.childNodes;
68 | if (oldNodes.length > vdom.children.length) {
69 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
70 | let nodeToBeRemoved = oldNodes[i];
71 | unmountNode(nodeToBeRemoved, oldDom);
72 | }
73 | }
74 |
75 | }
76 | }
77 |
78 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
79 |
80 | if (!oldComponent) {
81 | mountElement(newVirtualElement, container, domElement);
82 | }
83 | }
84 |
85 | const mountElement = function (vdom, container, oldDom) {
86 | if (isFunction(vdom)) {
87 | return mountComponent(vdom, container, oldDom);
88 | } else {
89 | return mountSimpleNode(vdom, container, oldDom);
90 | }
91 | }
92 |
93 | function isFunction(obj) {
94 | return obj && 'function' === typeof obj.type;
95 | }
96 |
97 | function isFunctionalComponent(vnode) {
98 | let nodeType = vnode && vnode.type;
99 | return nodeType && isFunction(vnode)
100 | && !(nodeType.prototype && nodeType.prototype.render);
101 | }
102 |
103 | function buildFunctionalComponent(vnode, context) {
104 | return vnode.type(vnode.props || {});
105 | }
106 |
107 |
108 | function buildStatefulComponent(virtualElement) {
109 | const component = new virtualElement.type();
110 | const nextElement = component.render();
111 | nextElement.component = component;
112 | return nextElement;
113 | }
114 |
115 | function mountComponent(vdom, container, oldDomElement) {
116 | let nextvDom = null, component = null, newDomElement = null;
117 | if (isFunctionalComponent(vdom)) {
118 | nextvDom = buildFunctionalComponent(vdom);
119 | } else {
120 | nextvDom = buildStatefulComponent(vdom);
121 | }
122 |
123 | // Recursively render child components
124 | if (isFunction(nextvDom)) {
125 | return mountComponent(nextvDom, container, oldDomElement);
126 | } else {
127 | newDomElement = mountElement(nextvDom, container, oldDomElement);
128 | }
129 | return newDomElement;
130 |
131 | }
132 |
133 | function unmountNode(domElement, parentComponent) {
134 | domElement.remove();
135 | }
136 |
137 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
138 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
139 | domElement.textContent = newVirtualElement.props.textContent;
140 | }
141 | // Set a reference to the newvddom in oldDom
142 | domElement._virtualElement = newVirtualElement;
143 | }
144 |
145 |
146 |
147 |
148 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
149 | let newDomElement = null;
150 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
151 |
152 | if (vdom.type === "text") {
153 | newDomElement = document.createTextNode(vdom.props.textContent);
154 | } else {
155 | newDomElement = document.createElement(vdom.type);
156 | updateDomElement(newDomElement, vdom);
157 | }
158 |
159 | // Setting reference to vdom to dom
160 | newDomElement._virtualElement = vdom;
161 |
162 | // TODO: Remove old nodes
163 | if (oldDomElement) {
164 | unmountNode(oldDomElement, parentComponent);
165 | }
166 |
167 | if (nextSibling) {
168 | container.insertBefore(newDomElement, nextSibling);
169 | } else {
170 | container.appendChild(newDomElement);
171 | }
172 |
173 | vdom.children.forEach(child => {
174 | mountElement(child, newDomElement);
175 | });
176 |
177 | }
178 |
179 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
180 | const newProps = newVirtualElement.props || {};
181 | const oldProps = oldVirtualElement.props || {};
182 | Object.keys(newProps).forEach(propName => {
183 | const newProp = newProps[propName];
184 | const oldProp = oldProps[propName];
185 | if (newProp !== oldProp) {
186 | if (propName.slice(0, 2) === "on") {
187 | // prop is an event handler
188 | const eventName = propName.toLowerCase().slice(2);
189 | domElement.addEventListener(eventName, newProp, false);
190 | if (oldProp) {
191 | domElement.removeEventListener(eventName, oldProp, false);
192 | }
193 | } else if (propName === "value" || propName === "checked") {
194 | // this are special attributes that cannot be set
195 | // using setAttribute
196 | domElement[propName] = newProp;
197 | } else if (propName !== "children") {
198 | // ignore the 'children' prop
199 | if (propName === "className") {
200 | domElement.setAttribute("class", newProps[propName]);
201 | } else {
202 | domElement.setAttribute(propName, newProps[propName]);
203 | }
204 | }
205 | }
206 | });
207 | // remove oldProps
208 | Object.keys(oldProps).forEach(propName => {
209 | const newProp = newProps[propName];
210 | const oldProp = oldProps[propName];
211 | if (!newProp) {
212 | if (propName.slice(0, 2) === "on") {
213 | // prop is an event handler
214 | domElement.removeEventListener(propName, oldProp, false);
215 | } else if (propName !== "children") {
216 | // ignore the 'children' prop
217 | domElement.removeAttribute(propName);
218 | }
219 | }
220 | });
221 | }
222 |
223 | class Component {
224 | constructor(props) {
225 |
226 | }
227 | }
228 |
229 |
230 | return {
231 | createElement,
232 | render,
233 | Component
234 | }
235 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.13.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component
11 | // 11. functional component diffing (remove extra nodes)
12 | // 12. rendering stateful component
13 | // 13. passing props to stateful component
14 |
15 | const TinyReact = (function () {
16 | function createElement(type, attributes = {}, ...children) {
17 | let childElements = [].concat(...children).reduce(
18 | (acc, child) => {
19 | if (child != null && child !== true && child !== false) {
20 | if (child instanceof Object) {
21 | acc.push(child);
22 | } else {
23 | acc.push(createElement("text", {
24 | textContent: child
25 | }));
26 | }
27 | }
28 | return acc;
29 | }
30 | , []);
31 | return {
32 | type,
33 | children: childElements,
34 | props: Object.assign({ children: childElements }, attributes)
35 | }
36 | }
37 |
38 | const render = function (vdom, container, oldDom = container.firstChild) {
39 | diff(vdom, container, oldDom);
40 | }
41 |
42 | const diff = function (vdom, container, oldDom) {
43 | let oldvdom = oldDom && oldDom._virtualElement;
44 |
45 | if (!oldDom) {
46 | mountElement(vdom, container, oldDom);
47 | }
48 | else if (typeof vdom.type === "function") {
49 | diffComponent(vdom, null, container, oldDom);
50 | }
51 | else if (oldvdom && oldvdom.type === vdom.type) {
52 | if (oldvdom.type === "text") {
53 | updateTextNode(oldDom, vdom, oldvdom);
54 | } else {
55 | updateDomElement(oldDom, vdom, oldvdom);
56 | }
57 |
58 | // Set a reference to updated vdom
59 | oldDom._virtualElement = vdom;
60 |
61 | // Recursively diff children..
62 | // Doing an index by index diffing (because we don't have keys yet)
63 | vdom.children.forEach((child, i) => {
64 | diff(child, oldDom, oldDom.childNodes[i]);
65 | });
66 |
67 | // Remove old dom nodes
68 | let oldNodes = oldDom.childNodes;
69 | if (oldNodes.length > vdom.children.length) {
70 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
71 | let nodeToBeRemoved = oldNodes[i];
72 | unmountNode(nodeToBeRemoved, oldDom);
73 | }
74 | }
75 |
76 | }
77 | }
78 |
79 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
80 |
81 | if (!oldComponent) {
82 | mountElement(newVirtualElement, container, domElement);
83 | }
84 | }
85 |
86 | const mountElement = function (vdom, container, oldDom) {
87 | if (isFunction(vdom)) {
88 | return mountComponent(vdom, container, oldDom);
89 | } else {
90 | return mountSimpleNode(vdom, container, oldDom);
91 | }
92 | }
93 |
94 | function isFunction(obj) {
95 | return obj && 'function' === typeof obj.type;
96 | }
97 |
98 | function isFunctionalComponent(vnode) {
99 | let nodeType = vnode && vnode.type;
100 | return nodeType && isFunction(vnode)
101 | && !(nodeType.prototype && nodeType.prototype.render);
102 | }
103 |
104 | function buildFunctionalComponent(vnode, context) {
105 | return vnode.type(vnode.props || {});
106 | }
107 |
108 |
109 | function buildStatefulComponent(virtualElement) {
110 | const component = new virtualElement.type(virtualElement.props);
111 | const nextElement = component.render();
112 | nextElement.component = component;
113 | return nextElement;
114 | }
115 |
116 | function mountComponent(vdom, container, oldDomElement) {
117 | let nextvDom = null, component = null, newDomElement = null;
118 | if (isFunctionalComponent(vdom)) {
119 | nextvDom = buildFunctionalComponent(vdom);
120 | } else {
121 | nextvDom = buildStatefulComponent(vdom);
122 | }
123 |
124 | // Recursively render child components
125 | if (isFunction(nextvDom)) {
126 | return mountComponent(nextvDom, container, oldDomElement);
127 | } else {
128 | newDomElement = mountElement(nextvDom, container, oldDomElement);
129 | }
130 | return newDomElement;
131 |
132 | }
133 |
134 | function unmountNode(domElement, parentComponent) {
135 | domElement.remove();
136 | }
137 |
138 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
139 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
140 | domElement.textContent = newVirtualElement.props.textContent;
141 | }
142 | // Set a reference to the newvddom in oldDom
143 | domElement._virtualElement = newVirtualElement;
144 | }
145 |
146 |
147 |
148 |
149 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
150 | let newDomElement = null;
151 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
152 |
153 | if (vdom.type === "text") {
154 | newDomElement = document.createTextNode(vdom.props.textContent);
155 | } else {
156 | newDomElement = document.createElement(vdom.type);
157 | updateDomElement(newDomElement, vdom);
158 | }
159 |
160 | // Setting reference to vdom to dom
161 | newDomElement._virtualElement = vdom;
162 |
163 | // TODO: Remove old nodes
164 | if (oldDomElement) {
165 | unmountNode(oldDomElement, parentComponent);
166 | }
167 |
168 | if (nextSibling) {
169 | container.insertBefore(newDomElement, nextSibling);
170 | } else {
171 | container.appendChild(newDomElement);
172 | }
173 |
174 | vdom.children.forEach(child => {
175 | mountElement(child, newDomElement);
176 | });
177 |
178 | }
179 |
180 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
181 | const newProps = newVirtualElement.props || {};
182 | const oldProps = oldVirtualElement.props || {};
183 | Object.keys(newProps).forEach(propName => {
184 | const newProp = newProps[propName];
185 | const oldProp = oldProps[propName];
186 | if (newProp !== oldProp) {
187 | if (propName.slice(0, 2) === "on") {
188 | // prop is an event handler
189 | const eventName = propName.toLowerCase().slice(2);
190 | domElement.addEventListener(eventName, newProp, false);
191 | if (oldProp) {
192 | domElement.removeEventListener(eventName, oldProp, false);
193 | }
194 | } else if (propName === "value" || propName === "checked") {
195 | // this are special attributes that cannot be set
196 | // using setAttribute
197 | domElement[propName] = newProp;
198 | } else if (propName !== "children") {
199 | // ignore the 'children' prop
200 | if (propName === "className") {
201 | domElement.setAttribute("class", newProps[propName]);
202 | } else {
203 | domElement.setAttribute(propName, newProps[propName]);
204 | }
205 | }
206 | }
207 | });
208 | // remove oldProps
209 | Object.keys(oldProps).forEach(propName => {
210 | const newProp = newProps[propName];
211 | const oldProp = oldProps[propName];
212 | if (!newProp) {
213 | if (propName.slice(0, 2) === "on") {
214 | // prop is an event handler
215 | domElement.removeEventListener(propName, oldProp, false);
216 | } else if (propName !== "children") {
217 | // ignore the 'children' prop
218 | domElement.removeAttribute(propName);
219 | }
220 | }
221 | });
222 | }
223 |
224 | class Component {
225 | constructor(props) {
226 | this.props = props;
227 | }
228 | }
229 |
230 |
231 | return {
232 | createElement,
233 | render,
234 | Component
235 | }
236 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.14.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component
11 | // 11. functional component diffing (remove extra nodes)
12 | // 12. rendering stateful component
13 | // 13. passing props to stateful component
14 | // 14. implement setState() method
15 |
16 |
17 | const TinyReact = (function () {
18 | function createElement(type, attributes = {}, ...children) {
19 | let childElements = [].concat(...children).reduce(
20 | (acc, child) => {
21 | if (child != null && child !== true && child !== false) {
22 | if (child instanceof Object) {
23 | acc.push(child);
24 | } else {
25 | acc.push(createElement("text", {
26 | textContent: child
27 | }));
28 | }
29 | }
30 | return acc;
31 | }
32 | , []);
33 | return {
34 | type,
35 | children: childElements,
36 | props: Object.assign({ children: childElements }, attributes)
37 | }
38 | }
39 |
40 | const render = function (vdom, container, oldDom = container.firstChild) {
41 | diff(vdom, container, oldDom);
42 | }
43 |
44 | const diff = function (vdom, container, oldDom) {
45 | let oldvdom = oldDom && oldDom._virtualElement;
46 |
47 | if (!oldDom) {
48 | mountElement(vdom, container, oldDom);
49 | }
50 | else if (typeof vdom.type === "function") {
51 | diffComponent(vdom, null, container, oldDom);
52 | }
53 | else if (oldvdom && oldvdom.type === vdom.type) {
54 | if (oldvdom.type === "text") {
55 | updateTextNode(oldDom, vdom, oldvdom);
56 | } else {
57 | updateDomElement(oldDom, vdom, oldvdom);
58 | }
59 |
60 | // Set a reference to updated vdom
61 | oldDom._virtualElement = vdom;
62 |
63 | // Recursively diff children..
64 | // Doing an index by index diffing (because we don't have keys yet)
65 | vdom.children.forEach((child, i) => {
66 | diff(child, oldDom, oldDom.childNodes[i]);
67 | });
68 |
69 | // Remove old dom nodes
70 | let oldNodes = oldDom.childNodes;
71 | if (oldNodes.length > vdom.children.length) {
72 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
73 | let nodeToBeRemoved = oldNodes[i];
74 | unmountNode(nodeToBeRemoved, oldDom);
75 | }
76 | }
77 |
78 | }
79 | }
80 |
81 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
82 |
83 | if (!oldComponent) {
84 | mountElement(newVirtualElement, container, domElement);
85 | }
86 | }
87 |
88 | const mountElement = function (vdom, container, oldDom) {
89 | if (isFunction(vdom)) {
90 | return mountComponent(vdom, container, oldDom);
91 | } else {
92 | return mountSimpleNode(vdom, container, oldDom);
93 | }
94 | }
95 |
96 | function isFunction(obj) {
97 | return obj && 'function' === typeof obj.type;
98 | }
99 |
100 | function isFunctionalComponent(vnode) {
101 | let nodeType = vnode && vnode.type;
102 | return nodeType && isFunction(vnode)
103 | && !(nodeType.prototype && nodeType.prototype.render);
104 | }
105 |
106 | function buildFunctionalComponent(vnode, context) {
107 | return vnode.type(vnode.props || {});
108 | }
109 |
110 |
111 | function buildStatefulComponent(virtualElement) {
112 | const component = new virtualElement.type(virtualElement.props);
113 | const nextElement = component.render();
114 | nextElement.component = component;
115 | return nextElement;
116 | }
117 |
118 | function mountComponent(vdom, container, oldDomElement) {
119 | let nextvDom = null, component = null, newDomElement = null;
120 | if (isFunctionalComponent(vdom)) {
121 | nextvDom = buildFunctionalComponent(vdom);
122 | } else {
123 | nextvDom = buildStatefulComponent(vdom);
124 | }
125 |
126 | // Recursively render child components
127 | if (isFunction(nextvDom)) {
128 | return mountComponent(nextvDom, container, oldDomElement);
129 | } else {
130 | newDomElement = mountElement(nextvDom, container, oldDomElement);
131 | }
132 | return newDomElement;
133 |
134 | }
135 |
136 | function unmountNode(domElement, parentComponent) {
137 | domElement.remove();
138 | }
139 |
140 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
141 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
142 | domElement.textContent = newVirtualElement.props.textContent;
143 | }
144 | // Set a reference to the newvddom in oldDom
145 | domElement._virtualElement = newVirtualElement;
146 | }
147 |
148 |
149 |
150 |
151 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
152 | let newDomElement = null;
153 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
154 |
155 | if (vdom.type === "text") {
156 | newDomElement = document.createTextNode(vdom.props.textContent);
157 | } else {
158 | newDomElement = document.createElement(vdom.type);
159 | updateDomElement(newDomElement, vdom);
160 | }
161 |
162 | // Setting reference to vdom to dom
163 | newDomElement._virtualElement = vdom;
164 |
165 | // TODO: Remove old nodes
166 | if (oldDomElement) {
167 | unmountNode(oldDomElement, parentComponent);
168 | }
169 |
170 | if (nextSibling) {
171 | container.insertBefore(newDomElement, nextSibling);
172 | } else {
173 | container.appendChild(newDomElement);
174 | }
175 |
176 | let component = vdom.component;
177 | if (component) {
178 | component.setDomElement(newDomElement);
179 | }
180 |
181 | vdom.children.forEach(child => {
182 | mountElement(child, newDomElement);
183 | });
184 |
185 | }
186 |
187 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
188 | const newProps = newVirtualElement.props || {};
189 | const oldProps = oldVirtualElement.props || {};
190 | Object.keys(newProps).forEach(propName => {
191 | const newProp = newProps[propName];
192 | const oldProp = oldProps[propName];
193 | if (newProp !== oldProp) {
194 | if (propName.slice(0, 2) === "on") {
195 | // prop is an event handler
196 | const eventName = propName.toLowerCase().slice(2);
197 | domElement.addEventListener(eventName, newProp, false);
198 | if (oldProp) {
199 | domElement.removeEventListener(eventName, oldProp, false);
200 | }
201 | } else if (propName === "value" || propName === "checked") {
202 | // this are special attributes that cannot be set
203 | // using setAttribute
204 | domElement[propName] = newProp;
205 | } else if (propName !== "children") {
206 | // ignore the 'children' prop
207 | if (propName === "className") {
208 | domElement.setAttribute("class", newProps[propName]);
209 | } else {
210 | domElement.setAttribute(propName, newProps[propName]);
211 | }
212 | }
213 | }
214 | });
215 | // remove oldProps
216 | Object.keys(oldProps).forEach(propName => {
217 | const newProp = newProps[propName];
218 | const oldProp = oldProps[propName];
219 | if (!newProp) {
220 | if (propName.slice(0, 2) === "on") {
221 | // prop is an event handler
222 | domElement.removeEventListener(propName, oldProp, false);
223 | } else if (propName !== "children") {
224 | // ignore the 'children' prop
225 | domElement.removeAttribute(propName);
226 | }
227 | }
228 | });
229 | }
230 |
231 | class Component {
232 | constructor(props) {
233 | this.props = props;
234 | this.state = {};
235 | this.prevState = {};
236 | }
237 |
238 | setState(nextState) {
239 | if (!this.prevState) this.prevState = this.state;
240 |
241 | this.state = Object.assign({}, this.state, nextState);
242 |
243 | let dom = this.getDomElement();
244 | let container = dom.parentNode;
245 |
246 | let newvdom = this.render();
247 |
248 | // Recursively diff
249 | diff(newvdom, container, dom);
250 |
251 | }
252 |
253 | // Helper methods
254 | setDomElement(dom) {
255 | this._dom = dom;
256 | }
257 |
258 | getDomElement() {
259 | return this._dom;
260 | }
261 | }
262 |
263 |
264 | return {
265 | createElement,
266 | render,
267 | Component
268 | }
269 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.15.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component
11 | // 11. functional component diffing (remove extra nodes)
12 | // 12. rendering stateful component
13 | // 13. passing props to stateful component
14 | // 14. implement setState() method -> parameter as object
15 | // 15. implement the stub for lifecycle method (TODO)
16 |
17 |
18 | const TinyReact = (function () {
19 | function createElement(type, attributes = {}, ...children) {
20 | let childElements = [].concat(...children).reduce(
21 | (acc, child) => {
22 | if (child != null && child !== true && child !== false) {
23 | if (child instanceof Object) {
24 | acc.push(child);
25 | } else {
26 | acc.push(createElement("text", {
27 | textContent: child
28 | }));
29 | }
30 | }
31 | return acc;
32 | }
33 | , []);
34 | return {
35 | type,
36 | children: childElements,
37 | props: Object.assign({ children: childElements }, attributes)
38 | }
39 | }
40 |
41 | const render = function (vdom, container, oldDom = container.firstChild) {
42 | diff(vdom, container, oldDom);
43 | }
44 |
45 | const diff = function (vdom, container, oldDom) {
46 | let oldvdom = oldDom && oldDom._virtualElement;
47 |
48 | if (!oldDom) {
49 | mountElement(vdom, container, oldDom);
50 | }
51 | else if (typeof vdom.type === "function") {
52 | diffComponent(vdom, null, container, oldDom);
53 | }
54 | else if (oldvdom && oldvdom.type === vdom.type) {
55 | if (oldvdom.type === "text") {
56 | updateTextNode(oldDom, vdom, oldvdom);
57 | } else {
58 | updateDomElement(oldDom, vdom, oldvdom);
59 | }
60 |
61 | // Set a reference to updated vdom
62 | oldDom._virtualElement = vdom;
63 |
64 | // Recursively diff children..
65 | // Doing an index by index diffing (because we don't have keys yet)
66 | vdom.children.forEach((child, i) => {
67 | diff(child, oldDom, oldDom.childNodes[i]);
68 | });
69 |
70 | // Remove old dom nodes
71 | let oldNodes = oldDom.childNodes;
72 | if (oldNodes.length > vdom.children.length) {
73 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
74 | let nodeToBeRemoved = oldNodes[i];
75 | unmountNode(nodeToBeRemoved, oldDom);
76 | }
77 | }
78 |
79 | }
80 | }
81 |
82 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
83 |
84 | if (!oldComponent) {
85 | mountElement(newVirtualElement, container, domElement);
86 | }
87 | }
88 |
89 | const mountElement = function (vdom, container, oldDom) {
90 | if (isFunction(vdom)) {
91 | return mountComponent(vdom, container, oldDom);
92 | } else {
93 | return mountSimpleNode(vdom, container, oldDom);
94 | }
95 | }
96 |
97 | function isFunction(obj) {
98 | return obj && 'function' === typeof obj.type;
99 | }
100 |
101 | function isFunctionalComponent(vnode) {
102 | let nodeType = vnode && vnode.type;
103 | return nodeType && isFunction(vnode)
104 | && !(nodeType.prototype && nodeType.prototype.render);
105 | }
106 |
107 | function buildFunctionalComponent(vnode, context) {
108 | return vnode.type(vnode.props || {});
109 | }
110 |
111 |
112 | function buildStatefulComponent(virtualElement) {
113 | const component = new virtualElement.type(virtualElement.props);
114 | const nextElement = component.render();
115 | nextElement.component = component;
116 | return nextElement;
117 | }
118 |
119 | function mountComponent(vdom, container, oldDomElement) {
120 | let nextvDom = null, component = null, newDomElement = null;
121 | if (isFunctionalComponent(vdom)) {
122 | nextvDom = buildFunctionalComponent(vdom);
123 | } else {
124 | nextvDom = buildStatefulComponent(vdom);
125 | }
126 |
127 | // Recursively render child components
128 | if (isFunction(nextvDom)) {
129 | return mountComponent(nextvDom, container, oldDomElement);
130 | } else {
131 | newDomElement = mountElement(nextvDom, container, oldDomElement);
132 | }
133 | return newDomElement;
134 |
135 | }
136 |
137 | function unmountNode(domElement, parentComponent) {
138 | domElement.remove();
139 | }
140 |
141 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
142 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
143 | domElement.textContent = newVirtualElement.props.textContent;
144 | }
145 | // Set a reference to the newvddom in oldDom
146 | domElement._virtualElement = newVirtualElement;
147 | }
148 |
149 |
150 |
151 |
152 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
153 | let newDomElement = null;
154 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
155 |
156 | if (vdom.type === "text") {
157 | newDomElement = document.createTextNode(vdom.props.textContent);
158 | } else {
159 | newDomElement = document.createElement(vdom.type);
160 | updateDomElement(newDomElement, vdom);
161 | }
162 |
163 | // Setting reference to vdom to dom
164 | newDomElement._virtualElement = vdom;
165 |
166 | // TODO: Remove old nodes
167 | if (oldDomElement) {
168 | unmountNode(oldDomElement, parentComponent);
169 | }
170 |
171 | if (nextSibling) {
172 | container.insertBefore(newDomElement, nextSibling);
173 | } else {
174 | container.appendChild(newDomElement);
175 | }
176 |
177 | let component = vdom.component;
178 | if (component) {
179 | component.setDomElement(newDomElement);
180 | }
181 |
182 | vdom.children.forEach(child => {
183 | mountElement(child, newDomElement);
184 | });
185 |
186 | }
187 |
188 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
189 | const newProps = newVirtualElement.props || {};
190 | const oldProps = oldVirtualElement.props || {};
191 | Object.keys(newProps).forEach(propName => {
192 | const newProp = newProps[propName];
193 | const oldProp = oldProps[propName];
194 | if (newProp !== oldProp) {
195 | if (propName.slice(0, 2) === "on") {
196 | // prop is an event handler
197 | const eventName = propName.toLowerCase().slice(2);
198 | domElement.addEventListener(eventName, newProp, false);
199 | if (oldProp) {
200 | domElement.removeEventListener(eventName, oldProp, false);
201 | }
202 | } else if (propName === "value" || propName === "checked") {
203 | // this are special attributes that cannot be set
204 | // using setAttribute
205 | domElement[propName] = newProp;
206 | } else if (propName !== "children") {
207 | // ignore the 'children' prop
208 | if (propName === "className") {
209 | domElement.setAttribute("class", newProps[propName]);
210 | } else {
211 | domElement.setAttribute(propName, newProps[propName]);
212 | }
213 | }
214 | }
215 | });
216 | // remove oldProps
217 | Object.keys(oldProps).forEach(propName => {
218 | const newProp = newProps[propName];
219 | const oldProp = oldProps[propName];
220 | if (!newProp) {
221 | if (propName.slice(0, 2) === "on") {
222 | // prop is an event handler
223 | domElement.removeEventListener(propName, oldProp, false);
224 | } else if (propName !== "children") {
225 | // ignore the 'children' prop
226 | domElement.removeAttribute(propName);
227 | }
228 | }
229 | });
230 | }
231 |
232 | class Component {
233 | constructor(props) {
234 | this.props = props;
235 | this.state = {};
236 | this.prevState = {};
237 | }
238 |
239 | setState(nextState) {
240 | if (!this.prevState) this.prevState = this.state;
241 |
242 | this.state = Object.assign({}, this.state, nextState);
243 |
244 | let dom = this.getDomElement();
245 | let container = dom.parentNode;
246 |
247 | let newvdom = this.render();
248 |
249 | // Recursively diff
250 | diff(newvdom, container, dom);
251 |
252 | }
253 |
254 | // Helper methods
255 | setDomElement(dom) {
256 | this._dom = dom;
257 | }
258 |
259 | getDomElement() {
260 | return this._dom;
261 | }
262 |
263 | // Lifecycle methods
264 | componentWillMount() { }
265 | componentDidMount() { }
266 | componentWillReceiveProps(nextProps) { }
267 |
268 | shouldComponentUpdate(nextProps, nextState) {
269 | return nextProps != this.props || nextState != this.state;
270 | }
271 |
272 | componentWillUpdate(nextProps, nextState) { }
273 |
274 | componentDidUpdate(prevProps, prevState) { }
275 |
276 | componentWillUnmount() { }
277 | }
278 |
279 |
280 | return {
281 | createElement,
282 | render,
283 | Component
284 | }
285 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.16.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component
11 | // 11. functional component diffing (remove extra nodes)
12 | // 12. rendering stateful component
13 | // 13. passing props to stateful component
14 | // 14. implement setState() method -> parameter as object
15 | // 15. implement the stub for lifecycle method (TODO)
16 | // 16. diffing stateful components (ISSUE: new props coming as #text->object should be toString() in app)
17 |
18 |
19 | const TinyReact = (function () {
20 | function createElement(type, attributes = {}, ...children) {
21 | let childElements = [].concat(...children).reduce(
22 | (acc, child) => {
23 | if (child != null && child !== true && child !== false) {
24 | if (child instanceof Object) {
25 | acc.push(child);
26 | } else {
27 | acc.push(createElement("text", {
28 | textContent: child
29 | }));
30 | }
31 | }
32 | return acc;
33 | }
34 | , []);
35 | return {
36 | type,
37 | children: childElements,
38 | props: Object.assign({ children: childElements }, attributes)
39 | }
40 | }
41 |
42 | const render = function (vdom, container, oldDom = container.firstChild) {
43 | diff(vdom, container, oldDom);
44 | }
45 |
46 | const diff = function (vdom, container, oldDom) {
47 | let oldvdom = oldDom && oldDom._virtualElement;
48 | let oldComponent = oldvdom && oldvdom.component;
49 |
50 | if (!oldDom) {
51 | mountElement(vdom, container, oldDom);
52 | }
53 | else if (typeof vdom.type === "function") {
54 | diffComponent(vdom, oldComponent, container, oldDom);
55 | }
56 | else if (oldvdom && oldvdom.type === vdom.type) {
57 | if (oldvdom.type === "text") {
58 | updateTextNode(oldDom, vdom, oldvdom);
59 | } else {
60 | updateDomElement(oldDom, vdom, oldvdom);
61 | }
62 |
63 | // Set a reference to updated vdom
64 | oldDom._virtualElement = vdom;
65 |
66 | // Recursively diff children..
67 | // Doing an index by index diffing (because we don't have keys yet)
68 | vdom.children.forEach((child, i) => {
69 | diff(child, oldDom, oldDom.childNodes[i]);
70 | });
71 |
72 | // Remove old dom nodes
73 | let oldNodes = oldDom.childNodes;
74 | if (oldNodes.length > vdom.children.length) {
75 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
76 | let nodeToBeRemoved = oldNodes[i];
77 | unmountNode(nodeToBeRemoved, oldDom);
78 | }
79 | }
80 |
81 | }
82 | }
83 |
84 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
85 |
86 | if (isSameComponentType(oldComponent, newVirtualElement)) {
87 | updateComponent(newVirtualElement, oldComponent, container, domElement);
88 | } else {
89 | mountElement(newVirtualElement, container, domElement);
90 | }
91 | }
92 |
93 | function updateComponent(newVirtualElement, oldComponent, container, domElement) {
94 |
95 | // Lifecycle method
96 | oldComponent.componentWillReceiveProps(newVirtualElement.props);
97 |
98 | // Lifecycle method
99 | if (oldComponent.shouldComponentUpdate(newVirtualElement.props)) {
100 | const prevProps = oldComponent.props;
101 |
102 | // Invoke LifeCycle
103 | oldComponent.componentWillUpdate(
104 | newVirtualElement.props,
105 | oldComponent.state
106 | );
107 |
108 | // Update component
109 | oldComponent.updateProps(newVirtualElement.props);
110 |
111 | // Call Render
112 | // Generate new vdom
113 | const nextElement = oldComponent.render();
114 | nextElement.component = oldComponent;
115 |
116 | // Recursively diff again
117 | diff(nextElement, container, domElement, oldComponent);
118 |
119 | // Invoke LifeCycle
120 | oldComponent.componentDidUpdate(prevProps);
121 |
122 | }
123 |
124 | }
125 |
126 | function isSameComponentType(oldComponent, newVirtualElement) {
127 | return oldComponent && newVirtualElement.type === oldComponent.constructor;
128 | }
129 |
130 | const mountElement = function (vdom, container, oldDom) {
131 | if (isFunction(vdom)) {
132 | return mountComponent(vdom, container, oldDom);
133 | } else {
134 | return mountSimpleNode(vdom, container, oldDom);
135 | }
136 | }
137 |
138 | function isFunction(obj) {
139 | return obj && 'function' === typeof obj.type;
140 | }
141 |
142 | function isFunctionalComponent(vnode) {
143 | let nodeType = vnode && vnode.type;
144 | return nodeType && isFunction(vnode)
145 | && !(nodeType.prototype && nodeType.prototype.render);
146 | }
147 |
148 | function buildFunctionalComponent(vnode, context) {
149 | return vnode.type(vnode.props || {});
150 | }
151 |
152 |
153 | function buildStatefulComponent(virtualElement) {
154 | const component = new virtualElement.type(virtualElement.props);
155 | const nextElement = component.render();
156 | nextElement.component = component;
157 | return nextElement;
158 | }
159 |
160 | function mountComponent(vdom, container, oldDomElement) {
161 | let nextvDom = null, component = null, newDomElement = null;
162 | if (isFunctionalComponent(vdom)) {
163 | nextvDom = buildFunctionalComponent(vdom);
164 | } else {
165 | nextvDom = buildStatefulComponent(vdom);
166 | }
167 |
168 | // Recursively render child components
169 | if (isFunction(nextvDom)) {
170 | return mountComponent(nextvDom, container, oldDomElement);
171 | } else {
172 | newDomElement = mountElement(nextvDom, container, oldDomElement);
173 | }
174 | return newDomElement;
175 |
176 | }
177 |
178 | function unmountNode(domElement, parentComponent) {
179 | domElement.remove();
180 | }
181 |
182 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
183 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
184 | domElement.textContent = newVirtualElement.props.textContent;
185 | }
186 | // Set a reference to the newvddom in oldDom
187 | domElement._virtualElement = newVirtualElement;
188 | }
189 |
190 |
191 |
192 |
193 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
194 | let newDomElement = null;
195 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
196 |
197 | if (vdom.type === "text") {
198 | newDomElement = document.createTextNode(vdom.props.textContent);
199 | } else {
200 | newDomElement = document.createElement(vdom.type);
201 | updateDomElement(newDomElement, vdom);
202 | }
203 |
204 | // Setting reference to vdom to dom
205 | newDomElement._virtualElement = vdom;
206 |
207 | // TODO: Remove old nodes
208 | if (oldDomElement) {
209 | unmountNode(oldDomElement, parentComponent);
210 | }
211 |
212 | if (nextSibling) {
213 | container.insertBefore(newDomElement, nextSibling);
214 | } else {
215 | container.appendChild(newDomElement);
216 | }
217 |
218 | let component = vdom.component;
219 | if (component) {
220 | component.setDomElement(newDomElement);
221 | }
222 |
223 | vdom.children.forEach(child => {
224 | mountElement(child, newDomElement);
225 | });
226 |
227 | }
228 |
229 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
230 | const newProps = newVirtualElement.props || {};
231 | const oldProps = oldVirtualElement.props || {};
232 | Object.keys(newProps).forEach(propName => {
233 | const newProp = newProps[propName];
234 | const oldProp = oldProps[propName];
235 | if (newProp !== oldProp) {
236 | if (propName.slice(0, 2) === "on") {
237 | // prop is an event handler
238 | const eventName = propName.toLowerCase().slice(2);
239 | domElement.addEventListener(eventName, newProp, false);
240 | if (oldProp) {
241 | domElement.removeEventListener(eventName, oldProp, false);
242 | }
243 | } else if (propName === "value" || propName === "checked") {
244 | // this are special attributes that cannot be set
245 | // using setAttribute
246 | domElement[propName] = newProp;
247 | } else if (propName !== "children") {
248 | // ignore the 'children' prop
249 | if (propName === "className") {
250 | domElement.setAttribute("class", newProps[propName]);
251 | } else {
252 | domElement.setAttribute(propName, newProps[propName]);
253 | }
254 | }
255 | }
256 | });
257 | // remove oldProps
258 | Object.keys(oldProps).forEach(propName => {
259 | const newProp = newProps[propName];
260 | const oldProp = oldProps[propName];
261 | if (!newProp) {
262 | if (propName.slice(0, 2) === "on") {
263 | // prop is an event handler
264 | domElement.removeEventListener(propName, oldProp, false);
265 | } else if (propName !== "children") {
266 | // ignore the 'children' prop
267 | domElement.removeAttribute(propName);
268 | }
269 | }
270 | });
271 | }
272 |
273 | class Component {
274 | constructor(props) {
275 | this.props = props;
276 | this.state = {};
277 | this.prevState = {};
278 | }
279 |
280 | setState(nextState) {
281 | if (!this.prevState) this.prevState = this.state;
282 |
283 | this.state = Object.assign({}, this.state, nextState);
284 |
285 | let dom = this.getDomElement();
286 | let container = dom.parentNode;
287 |
288 | let newvdom = this.render();
289 |
290 | // Recursively diff
291 | diff(newvdom, container, dom);
292 |
293 | }
294 |
295 | // Helper methods
296 | setDomElement(dom) {
297 | this._dom = dom;
298 | }
299 |
300 | getDomElement() {
301 | return this._dom;
302 | }
303 |
304 | updateProps(props) {
305 | this.props = props;
306 | }
307 |
308 | // Lifecycle methods
309 | componentWillMount() { }
310 | componentDidMount() { }
311 | componentWillReceiveProps(nextProps) { }
312 |
313 | shouldComponentUpdate(nextProps, nextState) {
314 | return nextProps != this.props || nextState != this.state;
315 | }
316 |
317 | componentWillUpdate(nextProps, nextState) { }
318 |
319 | componentDidUpdate(prevProps, prevState) { }
320 |
321 | componentWillUnmount() { }
322 | }
323 |
324 |
325 | return {
326 | createElement,
327 | render,
328 | Component
329 | }
330 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.17.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component
11 | // 11. functional component diffing (remove extra nodes)
12 | // 12. rendering stateful component
13 | // 13. passing props to stateful component
14 | // 14. implement setState() method -> parameter as object
15 | // 15. implement the stub for lifecycle method (TODO)
16 | // 16. diffing stateful components (ISSUE: new props coming as #text->object should be toString() in app)
17 | // 17. Adding support for ref (todo)
18 |
19 | const TinyReact = (function () {
20 | function createElement(type, attributes = {}, ...children) {
21 | let childElements = [].concat(...children).reduce(
22 | (acc, child) => {
23 | if (child != null && child !== true && child !== false) {
24 | if (child instanceof Object) {
25 | acc.push(child);
26 | } else {
27 | acc.push(createElement("text", {
28 | textContent: child
29 | }));
30 | }
31 | }
32 | return acc;
33 | }
34 | , []);
35 | return {
36 | type,
37 | children: childElements,
38 | props: Object.assign({ children: childElements }, attributes)
39 | }
40 | }
41 |
42 | const render = function (vdom, container, oldDom = container.firstChild) {
43 | diff(vdom, container, oldDom);
44 | }
45 |
46 | const diff = function (vdom, container, oldDom) {
47 | let oldvdom = oldDom && oldDom._virtualElement;
48 | let oldComponent = oldvdom && oldvdom.component;
49 |
50 | if (!oldDom) {
51 | mountElement(vdom, container, oldDom);
52 | }
53 | else if (typeof vdom.type === "function") {
54 | diffComponent(vdom, oldComponent, container, oldDom);
55 | }
56 | else if (oldvdom && oldvdom.type === vdom.type) {
57 | if (oldvdom.type === "text") {
58 | updateTextNode(oldDom, vdom, oldvdom);
59 | } else {
60 | updateDomElement(oldDom, vdom, oldvdom);
61 | }
62 |
63 | // Set a reference to updated vdom
64 | oldDom._virtualElement = vdom;
65 |
66 | // Recursively diff children..
67 | // Doing an index by index diffing (because we don't have keys yet)
68 | vdom.children.forEach((child, i) => {
69 | diff(child, oldDom, oldDom.childNodes[i]);
70 | });
71 |
72 | // Remove old dom nodes
73 | let oldNodes = oldDom.childNodes;
74 | if (oldNodes.length > vdom.children.length) {
75 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
76 | let nodeToBeRemoved = oldNodes[i];
77 | unmountNode(nodeToBeRemoved, oldDom);
78 | }
79 | }
80 |
81 | }
82 | }
83 |
84 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
85 |
86 | if (isSameComponentType(oldComponent, newVirtualElement)) {
87 | updateComponent(newVirtualElement, oldComponent, container, domElement);
88 | } else {
89 | mountElement(newVirtualElement, container, domElement);
90 | }
91 | }
92 |
93 | function updateComponent(newVirtualElement, oldComponent, container, domElement) {
94 |
95 | // Lifecycle method
96 | oldComponent.componentWillReceiveProps(newVirtualElement.props);
97 |
98 | // Lifecycle method
99 | if (oldComponent.shouldComponentUpdate(newVirtualElement.props)) {
100 | const prevProps = oldComponent.props;
101 |
102 | // Invoke LifeCycle
103 | oldComponent.componentWillUpdate(
104 | newVirtualElement.props,
105 | oldComponent.state
106 | );
107 |
108 | // Update component
109 | oldComponent.updateProps(newVirtualElement.props);
110 |
111 | // Call Render
112 | // Generate new vdom
113 | const nextElement = oldComponent.render();
114 | nextElement.component = oldComponent;
115 |
116 | // Recursively diff again
117 | diff(nextElement, container, domElement, oldComponent);
118 |
119 | // Invoke LifeCycle
120 | oldComponent.componentDidUpdate(prevProps);
121 |
122 | }
123 |
124 | }
125 |
126 | function isSameComponentType(oldComponent, newVirtualElement) {
127 | return oldComponent && newVirtualElement.type === oldComponent.constructor;
128 | }
129 |
130 | const mountElement = function (vdom, container, oldDom) {
131 | if (isFunction(vdom)) {
132 | return mountComponent(vdom, container, oldDom);
133 | } else {
134 | return mountSimpleNode(vdom, container, oldDom);
135 | }
136 | }
137 |
138 | function isFunction(obj) {
139 | return obj && 'function' === typeof obj.type;
140 | }
141 |
142 | function isFunctionalComponent(vnode) {
143 | let nodeType = vnode && vnode.type;
144 | return nodeType && isFunction(vnode)
145 | && !(nodeType.prototype && nodeType.prototype.render);
146 | }
147 |
148 | function buildFunctionalComponent(vnode, context) {
149 | return vnode.type(vnode.props || {});
150 | }
151 |
152 |
153 | function buildStatefulComponent(virtualElement) {
154 | const component = new virtualElement.type(virtualElement.props);
155 | const nextElement = component.render();
156 | nextElement.component = component;
157 | return nextElement;
158 | }
159 |
160 | function mountComponent(vdom, container, oldDomElement) {
161 | let nextvDom = null, component = null, newDomElement = null;
162 | if (isFunctionalComponent(vdom)) {
163 | nextvDom = buildFunctionalComponent(vdom);
164 | } else {
165 | nextvDom = buildStatefulComponent(vdom);
166 | component = nextvDom.component;
167 | }
168 |
169 | // Recursively render child components
170 | if (isFunction(nextvDom)) {
171 | return mountComponent(nextvDom, container, oldDomElement);
172 | } else {
173 | newDomElement = mountElement(nextvDom, container, oldDomElement);
174 | }
175 |
176 | if (component) {
177 | component.componentDidMount(); // Life cycle method
178 | if (component.props.ref) {
179 | component.props.ref(component);
180 | }
181 | }
182 |
183 | return newDomElement;
184 | }
185 |
186 | function unmountNode(domElement, parentComponent) {
187 | const virtualElement = domElement._virtualElement;
188 | if (!virtualElement) {
189 | domElement.remove();
190 | return;
191 | }
192 |
193 | if (virtualElement.props && virtualElement.props.ref) {
194 | virtualElement.props.ref(null);
195 | }
196 |
197 | }
198 |
199 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
200 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
201 | domElement.textContent = newVirtualElement.props.textContent;
202 | }
203 | // Set a reference to the newvddom in oldDom
204 | domElement._virtualElement = newVirtualElement;
205 | }
206 |
207 |
208 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
209 | let newDomElement = null;
210 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
211 |
212 | if (vdom.type === "text") {
213 | newDomElement = document.createTextNode(vdom.props.textContent);
214 | } else {
215 | newDomElement = document.createElement(vdom.type);
216 | updateDomElement(newDomElement, vdom);
217 | }
218 |
219 | // Setting reference to vdom to dom
220 | newDomElement._virtualElement = vdom;
221 |
222 | // TODO: Remove old nodes
223 | if (oldDomElement) {
224 | unmountNode(oldDomElement, parentComponent);
225 | }
226 |
227 | if (nextSibling) {
228 | container.insertBefore(newDomElement, nextSibling);
229 | } else {
230 | container.appendChild(newDomElement);
231 | }
232 |
233 | let component = vdom.component;
234 | if (component) {
235 | component.setDomElement(newDomElement);
236 | }
237 |
238 | vdom.children.forEach(child => {
239 | mountElement(child, newDomElement);
240 | });
241 |
242 | if (vdom.props && vdom.props.ref) {
243 | vdom.props.ref(newDomElement);
244 | }
245 |
246 | }
247 |
248 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
249 | const newProps = newVirtualElement.props || {};
250 | const oldProps = oldVirtualElement.props || {};
251 | Object.keys(newProps).forEach(propName => {
252 | const newProp = newProps[propName];
253 | const oldProp = oldProps[propName];
254 | if (newProp !== oldProp) {
255 | if (propName.slice(0, 2) === "on") {
256 | // prop is an event handler
257 | const eventName = propName.toLowerCase().slice(2);
258 | domElement.addEventListener(eventName, newProp, false);
259 | if (oldProp) {
260 | domElement.removeEventListener(eventName, oldProp, false);
261 | }
262 | } else if (propName === "value" || propName === "checked") {
263 | // this are special attributes that cannot be set
264 | // using setAttribute
265 | domElement[propName] = newProp;
266 | } else if (propName !== "children") {
267 | // ignore the 'children' prop
268 | if (propName === "className") {
269 | domElement.setAttribute("class", newProps[propName]);
270 | } else {
271 | domElement.setAttribute(propName, newProps[propName]);
272 | }
273 | }
274 | }
275 | });
276 | // remove oldProps
277 | Object.keys(oldProps).forEach(propName => {
278 | const newProp = newProps[propName];
279 | const oldProp = oldProps[propName];
280 | if (!newProp) {
281 | if (propName.slice(0, 2) === "on") {
282 | // prop is an event handler
283 | domElement.removeEventListener(propName, oldProp, false);
284 | } else if (propName !== "children") {
285 | // ignore the 'children' prop
286 | domElement.removeAttribute(propName);
287 | }
288 | }
289 | });
290 | }
291 |
292 | class Component {
293 | constructor(props) {
294 | this.props = props;
295 | this.state = {};
296 | this.prevState = {};
297 | }
298 |
299 | setState(nextState) {
300 | if (!this.prevState) this.prevState = this.state;
301 |
302 | this.state = Object.assign({}, this.state, nextState);
303 |
304 | let dom = this.getDomElement();
305 | let container = dom.parentNode;
306 |
307 | let newvdom = this.render();
308 |
309 | // Recursively diff
310 | diff(newvdom, container, dom);
311 |
312 | }
313 |
314 | // Helper methods
315 | setDomElement(dom) {
316 | this._dom = dom;
317 | }
318 |
319 | getDomElement() {
320 | return this._dom;
321 | }
322 |
323 | updateProps(props) {
324 | this.props = props;
325 | }
326 |
327 | // Lifecycle methods
328 | componentWillMount() { }
329 | componentDidMount() { }
330 | componentWillReceiveProps(nextProps) { }
331 |
332 | shouldComponentUpdate(nextProps, nextState) {
333 | return nextProps != this.props || nextState != this.state;
334 | }
335 |
336 | componentWillUpdate(nextProps, nextState) { }
337 |
338 | componentDidUpdate(prevProps, prevState) { }
339 |
340 | componentWillUnmount() { }
341 | }
342 |
343 |
344 | return {
345 | createElement,
346 | render,
347 | Component
348 | }
349 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.18.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component
11 | // 11. functional component diffing (remove extra nodes)
12 | // 12. rendering stateful component
13 | // 13. passing props to stateful component
14 | // 14. implement setState() method -> parameter as object
15 | // 15. implement the stub for lifecycle method (TODO)
16 | // 16. diffing stateful components (ISSUE: new props coming as #text->object should be toString() in app)
17 | // 17. Adding support for ref (todo)
18 | // 18. More test cases, old and new types are different.
19 |
20 |
21 | const TinyReact = (function () {
22 | function createElement(type, attributes = {}, ...children) {
23 | let childElements = [].concat(...children).reduce(
24 | (acc, child) => {
25 | if (child != null && child !== true && child !== false) {
26 | if (child instanceof Object) {
27 | acc.push(child);
28 | } else {
29 | acc.push(createElement("text", {
30 | textContent: child
31 | }));
32 | }
33 | }
34 | return acc;
35 | }
36 | , []);
37 | return {
38 | type,
39 | children: childElements,
40 | props: Object.assign({ children: childElements }, attributes)
41 | }
42 | }
43 |
44 | const render = function (vdom, container, oldDom = container.firstChild) {
45 | diff(vdom, container, oldDom);
46 | }
47 |
48 | const diff = function (vdom, container, oldDom) {
49 | let oldvdom = oldDom && oldDom._virtualElement;
50 | let oldComponent = oldvdom && oldvdom.component;
51 |
52 | if (!oldDom) {
53 | mountElement(vdom, container, oldDom);
54 | }
55 | else if ((vdom.type !== oldvdom.type) && (typeof vdom.type !== "function")) {
56 | let newDomElement = createDomElement(vdom); // todo:
57 | oldDom.parentNode.replaceChild(newDomElement, oldDom);
58 | }
59 | else if (typeof vdom.type === "function") {
60 | diffComponent(vdom, oldComponent, container, oldDom);
61 | }
62 | else if (oldvdom && oldvdom.type === vdom.type) {
63 | if (oldvdom.type === "text") {
64 | updateTextNode(oldDom, vdom, oldvdom);
65 | } else {
66 | updateDomElement(oldDom, vdom, oldvdom);
67 | }
68 |
69 | // Set a reference to updated vdom
70 | oldDom._virtualElement = vdom;
71 |
72 | // Recursively diff children..
73 | // Doing an index by index diffing (because we don't have keys yet)
74 | vdom.children.forEach((child, i) => {
75 | diff(child, oldDom, oldDom.childNodes[i]);
76 | });
77 |
78 | // Remove old dom nodes
79 | let oldNodes = oldDom.childNodes;
80 | if (oldNodes.length > vdom.children.length) {
81 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
82 | let nodeToBeRemoved = oldNodes[i];
83 | unmountNode(nodeToBeRemoved, oldDom);
84 | }
85 | }
86 |
87 | }
88 | }
89 |
90 | function createDomElement(vdom) {
91 | let newDomElement = null;
92 | if (vdom.type === "text") {
93 | newDomElement = document.createTextNode(vdom.props.textContent);
94 | } else {
95 | newDomElement = document.createElement(vdom.type);
96 | updateDomElement(newDomElement, vdom);
97 | }
98 |
99 | newDomElement._virtualElement = vdom;
100 | vdom.children.forEach((child) => {
101 | newDomElement.appendChild(createDomElement(child));
102 | });
103 |
104 | // Set refs
105 | if (vdom.props && vdom.props.ref) {
106 | vdom.props.ref(newDomElement);
107 | }
108 |
109 | return newDomElement;
110 | }
111 |
112 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
113 |
114 | if (isSameComponentType(oldComponent, newVirtualElement)) {
115 | updateComponent(newVirtualElement, oldComponent, container, domElement);
116 | } else {
117 | mountElement(newVirtualElement, container, domElement);
118 | }
119 | }
120 |
121 | function updateComponent(newVirtualElement, oldComponent, container, domElement) {
122 |
123 | // Lifecycle method
124 | oldComponent.componentWillReceiveProps(newVirtualElement.props);
125 |
126 | // Lifecycle method
127 | if (oldComponent.shouldComponentUpdate(newVirtualElement.props)) {
128 | const prevProps = oldComponent.props;
129 |
130 | // Invoke LifeCycle
131 | oldComponent.componentWillUpdate(
132 | newVirtualElement.props,
133 | oldComponent.state
134 | );
135 |
136 | // Update component
137 | oldComponent.updateProps(newVirtualElement.props);
138 |
139 | // Call Render
140 | // Generate new vdom
141 | const nextElement = oldComponent.render();
142 | nextElement.component = oldComponent;
143 |
144 | // Recursively diff again
145 | diff(nextElement, container, domElement, oldComponent);
146 |
147 | // Invoke LifeCycle
148 | oldComponent.componentDidUpdate(prevProps);
149 |
150 | }
151 |
152 | }
153 |
154 | function isSameComponentType(oldComponent, newVirtualElement) {
155 | return oldComponent && newVirtualElement.type === oldComponent.constructor;
156 | }
157 |
158 | const mountElement = function (vdom, container, oldDom) {
159 | if (isFunction(vdom)) {
160 | return mountComponent(vdom, container, oldDom);
161 | } else {
162 | return mountSimpleNode(vdom, container, oldDom);
163 | }
164 | }
165 |
166 | function isFunction(obj) {
167 | return obj && 'function' === typeof obj.type;
168 | }
169 |
170 | function isFunctionalComponent(vnode) {
171 | let nodeType = vnode && vnode.type;
172 | return nodeType && isFunction(vnode)
173 | && !(nodeType.prototype && nodeType.prototype.render);
174 | }
175 |
176 | function buildFunctionalComponent(vnode, context) {
177 | return vnode.type(vnode.props || {});
178 | }
179 |
180 |
181 | function buildStatefulComponent(virtualElement) {
182 | const component = new virtualElement.type(virtualElement.props);
183 | const nextElement = component.render();
184 | nextElement.component = component;
185 | return nextElement;
186 | }
187 |
188 | function mountComponent(vdom, container, oldDomElement) {
189 | let nextvDom = null, component = null, newDomElement = null;
190 | if (isFunctionalComponent(vdom)) {
191 | nextvDom = buildFunctionalComponent(vdom);
192 | } else {
193 | nextvDom = buildStatefulComponent(vdom);
194 | component = nextvDom.component;
195 | }
196 |
197 | // Recursively render child components
198 | if (isFunction(nextvDom)) {
199 | return mountComponent(nextvDom, container, oldDomElement);
200 | } else {
201 | newDomElement = mountElement(nextvDom, container, oldDomElement);
202 | }
203 |
204 | if (component) {
205 | component.componentDidMount(); // Life cycle method
206 | if (component.props.ref) {
207 | component.props.ref(component);
208 | }
209 | }
210 |
211 | return newDomElement;
212 | }
213 |
214 | function unmountNode(domElement, parentComponent) {
215 | const virtualElement = domElement._virtualElement;
216 | if (!virtualElement) {
217 | domElement.remove();
218 | return;
219 | }
220 |
221 | // If component exist
222 | let oldComponent = domElement._virtualElement.component;
223 | if (oldComponent) {
224 | oldComponent.componentWillUnmount();
225 | }
226 |
227 | // Recursive calls agains
228 | while (domElement.childNodes.length > 0) {
229 | unmountNode(domElement.firstChild);
230 | }
231 |
232 | if (virtualElement.props && virtualElement.props.ref) {
233 | virtualElement.props.ref(null);
234 | }
235 |
236 | // Clear out event handlers
237 | Object.keys(virtualElement.props).forEach(propName => {
238 | if (propName.slice(0, 2) === "on") {
239 | const event = propName.toLowerCase().slice(2);
240 | const handler = virtualElement.props[propName];
241 | domElement.removeEventListener(event, handler);
242 | }
243 | });
244 |
245 | domElement.remove();
246 | }
247 |
248 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
249 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
250 | domElement.textContent = newVirtualElement.props.textContent;
251 | }
252 | // Set a reference to the newvddom in oldDom
253 | domElement._virtualElement = newVirtualElement;
254 | }
255 |
256 |
257 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
258 | let newDomElement = null;
259 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
260 |
261 | if (vdom.type === "text") {
262 | newDomElement = document.createTextNode(vdom.props.textContent);
263 | } else {
264 | newDomElement = document.createElement(vdom.type);
265 | updateDomElement(newDomElement, vdom);
266 | }
267 |
268 | // Setting reference to vdom to dom
269 | newDomElement._virtualElement = vdom;
270 |
271 | // TODO: Remove old nodes
272 | if (oldDomElement) {
273 | unmountNode(oldDomElement, parentComponent);
274 | }
275 |
276 | if (nextSibling) {
277 | container.insertBefore(newDomElement, nextSibling);
278 | } else {
279 | container.appendChild(newDomElement);
280 | }
281 |
282 | let component = vdom.component;
283 | if (component) {
284 | component.setDomElement(newDomElement);
285 | }
286 |
287 | vdom.children.forEach(child => {
288 | mountElement(child, newDomElement);
289 | });
290 |
291 | if (vdom.props && vdom.props.ref) {
292 | vdom.props.ref(newDomElement);
293 | }
294 |
295 | }
296 |
297 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
298 | const newProps = newVirtualElement.props || {};
299 | const oldProps = oldVirtualElement.props || {};
300 | Object.keys(newProps).forEach(propName => {
301 | const newProp = newProps[propName];
302 | const oldProp = oldProps[propName];
303 | if (newProp !== oldProp) {
304 | if (propName.slice(0, 2) === "on") {
305 | // prop is an event handler
306 | const eventName = propName.toLowerCase().slice(2);
307 | domElement.addEventListener(eventName, newProp, false);
308 | if (oldProp) {
309 | domElement.removeEventListener(eventName, oldProp, false);
310 | }
311 | } else if (propName === "value" || propName === "checked") {
312 | // this are special attributes that cannot be set
313 | // using setAttribute
314 | domElement[propName] = newProp;
315 | } else if (propName !== "children") {
316 | // ignore the 'children' prop
317 | if (propName === "className") {
318 | domElement.setAttribute("class", newProps[propName]);
319 | } else {
320 | domElement.setAttribute(propName, newProps[propName]);
321 | }
322 | }
323 | }
324 | });
325 | // remove oldProps
326 | Object.keys(oldProps).forEach(propName => {
327 | const newProp = newProps[propName];
328 | const oldProp = oldProps[propName];
329 | if (!newProp) {
330 | if (propName.slice(0, 2) === "on") {
331 | // prop is an event handler
332 | domElement.removeEventListener(propName, oldProp, false);
333 | } else if (propName !== "children") {
334 | // ignore the 'children' prop
335 | domElement.removeAttribute(propName);
336 | }
337 | }
338 | });
339 | }
340 |
341 | class Component {
342 | constructor(props) {
343 | this.props = props;
344 | this.state = {};
345 | this.prevState = {};
346 | }
347 |
348 | setState(nextState) {
349 | if (!this.prevState) this.prevState = this.state;
350 |
351 | this.state = Object.assign({}, this.state, nextState);
352 |
353 | let dom = this.getDomElement();
354 | let container = dom.parentNode;
355 |
356 | let newvdom = this.render();
357 |
358 | // Recursively diff
359 | diff(newvdom, container, dom);
360 |
361 | }
362 |
363 | // Helper methods
364 | setDomElement(dom) {
365 | this._dom = dom;
366 | }
367 |
368 | getDomElement() {
369 | return this._dom;
370 | }
371 |
372 | updateProps(props) {
373 | this.props = props;
374 | }
375 |
376 | // Lifecycle methods
377 | componentWillMount() { }
378 | componentDidMount() { }
379 | componentWillReceiveProps(nextProps) { }
380 |
381 | shouldComponentUpdate(nextProps, nextState) {
382 | return nextProps != this.props || nextState != this.state;
383 | }
384 |
385 | componentWillUpdate(nextProps, nextState) { }
386 |
387 | componentDidUpdate(prevProps, prevState) { }
388 |
389 | componentWillUnmount() { }
390 | }
391 |
392 |
393 | return {
394 | createElement,
395 | render,
396 | Component
397 | }
398 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.19.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component
11 | // 11. functional component diffing (remove extra nodes)
12 | // 12. rendering stateful component
13 | // 13. passing props to stateful component
14 | // 14. implement setState() method -> parameter as object
15 | // 15. implement the stub for lifecycle method (TODO)
16 | // 16. diffing stateful components (ISSUE: new props coming as #text->object should be toString() in app)
17 | // 17. Adding support for ref (todo)
18 | // 18. More test cases, old and new types are different.
19 | // 19. Add support for passing style attributes as objects
20 |
21 |
22 | const TinyReact = (function () {
23 | function createElement(type, attributes = {}, ...children) {
24 | let childElements = [].concat(...children).reduce(
25 | (acc, child) => {
26 | if (child != null && child !== true && child !== false) {
27 | if (child instanceof Object) {
28 | acc.push(child);
29 | } else {
30 | acc.push(createElement("text", {
31 | textContent: child
32 | }));
33 | }
34 | }
35 | return acc;
36 | }
37 | , []);
38 | return {
39 | type,
40 | children: childElements,
41 | props: Object.assign({ children: childElements }, attributes)
42 | }
43 | }
44 |
45 | const render = function (vdom, container, oldDom = container.firstChild) {
46 | diff(vdom, container, oldDom);
47 | }
48 |
49 | const diff = function (vdom, container, oldDom) {
50 | let oldvdom = oldDom && oldDom._virtualElement;
51 | let oldComponent = oldvdom && oldvdom.component;
52 |
53 | if (!oldDom) {
54 | mountElement(vdom, container, oldDom);
55 | }
56 | else if ((vdom.type !== oldvdom.type) && (typeof vdom.type !== "function")) {
57 | let newDomElement = createDomElement(vdom); // todo:
58 | oldDom.parentNode.replaceChild(newDomElement, oldDom);
59 | }
60 | else if (typeof vdom.type === "function") {
61 | diffComponent(vdom, oldComponent, container, oldDom);
62 | }
63 | else if (oldvdom && oldvdom.type === vdom.type) {
64 | if (oldvdom.type === "text") {
65 | updateTextNode(oldDom, vdom, oldvdom);
66 | } else {
67 | updateDomElement(oldDom, vdom, oldvdom);
68 | }
69 |
70 | // Set a reference to updated vdom
71 | oldDom._virtualElement = vdom;
72 |
73 | // Lets create a collection of keyed elements
74 | let keyedElements = {};
75 | for (let i = 0; i < oldDom.childNodes.length; i += 1) {
76 | const domElement = oldDom.childNodes[i];
77 | const key = domElement._virtualElement.props.key;
78 |
79 | if (key) {
80 | keyedElements[key] = {
81 | domElement,
82 | index: i
83 | };
84 | }
85 |
86 | }
87 |
88 |
89 | // Recursively diff children..
90 | // Doing an index by index diffing (because we don't have keys yet)
91 | if (Object.keys(keyedElements).length === 0) {
92 | vdom.children.forEach((child, i) => {
93 | diff(child, oldDom, oldDom.childNodes[i]);
94 | });
95 | } else {
96 | // Reconciliation based on keys
97 | vdom.children.forEach((virtualElement, i) => {
98 | const key = virtualElement.props.key;
99 | if (key) {
100 | const keyedDomElement = keyedElements[key];
101 | if (keyedDomElement) {
102 | // Position the new element correctly based on key/index
103 | if (oldDom.childNodes[i] && !oldDom.childNodes[i].isSameNode(keyedDomElement.domElement)) {
104 | oldDom.insertBefore(keyedDomElement.domElement,
105 | oldDom.childNodes[i]);
106 | }
107 | diff(virtualElement, oldDom, keyedDomElement.domElement);
108 | }
109 | else {
110 | mountElement(virtualElement, oldDom);
111 | }
112 | }
113 | });
114 | }
115 |
116 | // Remove old dom nodes
117 | let oldNodes = oldDom.childNodes;
118 | if (Object.keys(keyedElements).length === 0) {
119 | if (oldNodes.length > vdom.children.length) {
120 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
121 | let nodeToBeRemoved = oldNodes[i];
122 | unmountNode(nodeToBeRemoved, oldDom);
123 | }
124 | }
125 | } else {
126 | if (oldNodes.length > vdom.children.length) {
127 | for (let i = 0; i < oldDom.childNodes.length; i += 1) {
128 | let oldChild = oldDom.childNodes[i];
129 | let oldKey = oldChild.getAttribute("key");
130 |
131 | let found = false;
132 | for (let n = 0; n < vdom.children.length; n += 1) {
133 | if (vdom.children[n].props.key == oldKey) {
134 | found = true;
135 | break;
136 | }
137 | }
138 |
139 | if (!found) {
140 | unmountNode(oldChild, oldDom);
141 | }
142 |
143 | }
144 | }
145 | }
146 |
147 | }
148 | }
149 |
150 | function createDomElement(vdom) {
151 | let newDomElement = null;
152 | if (vdom.type === "text") {
153 | newDomElement = document.createTextNode(vdom.props.textContent);
154 | } else {
155 | newDomElement = document.createElement(vdom.type);
156 | updateDomElement(newDomElement, vdom);
157 | }
158 |
159 | newDomElement._virtualElement = vdom;
160 | vdom.children.forEach((child) => {
161 | newDomElement.appendChild(createDomElement(child));
162 | });
163 |
164 | // Set refs
165 | if (vdom.props && vdom.props.ref) {
166 | vdom.props.ref(newDomElement);
167 | }
168 |
169 | return newDomElement;
170 | }
171 |
172 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
173 |
174 | if (isSameComponentType(oldComponent, newVirtualElement)) {
175 | updateComponent(newVirtualElement, oldComponent, container, domElement);
176 | } else {
177 | mountElement(newVirtualElement, container, domElement);
178 | }
179 | }
180 |
181 | function updateComponent(newVirtualElement, oldComponent, container, domElement) {
182 |
183 | // Lifecycle method
184 | oldComponent.componentWillReceiveProps(newVirtualElement.props);
185 |
186 | // Lifecycle method
187 | if (oldComponent.shouldComponentUpdate(newVirtualElement.props)) {
188 | const prevProps = oldComponent.props;
189 |
190 | // Invoke LifeCycle
191 | oldComponent.componentWillUpdate(
192 | newVirtualElement.props,
193 | oldComponent.state
194 | );
195 |
196 | // Update component
197 | oldComponent.updateProps(newVirtualElement.props);
198 |
199 | // Call Render
200 | // Generate new vdom
201 | const nextElement = oldComponent.render();
202 | nextElement.component = oldComponent;
203 |
204 | // Recursively diff again
205 | diff(nextElement, container, domElement, oldComponent);
206 |
207 | // Invoke LifeCycle
208 | oldComponent.componentDidUpdate(prevProps);
209 |
210 | }
211 |
212 | }
213 |
214 | function isSameComponentType(oldComponent, newVirtualElement) {
215 | return oldComponent && newVirtualElement.type === oldComponent.constructor;
216 | }
217 |
218 | const mountElement = function (vdom, container, oldDom) {
219 | if (isFunction(vdom)) {
220 | return mountComponent(vdom, container, oldDom);
221 | } else {
222 | return mountSimpleNode(vdom, container, oldDom);
223 | }
224 | }
225 |
226 | function isFunction(obj) {
227 | return obj && 'function' === typeof obj.type;
228 | }
229 |
230 | function isFunctionalComponent(vnode) {
231 | let nodeType = vnode && vnode.type;
232 | return nodeType && isFunction(vnode)
233 | && !(nodeType.prototype && nodeType.prototype.render);
234 | }
235 |
236 | function buildFunctionalComponent(vnode, context) {
237 | return vnode.type(vnode.props || {});
238 | }
239 |
240 |
241 | function buildStatefulComponent(virtualElement) {
242 | const component = new virtualElement.type(virtualElement.props);
243 | const nextElement = component.render();
244 | nextElement.component = component;
245 | return nextElement;
246 | }
247 |
248 | function mountComponent(vdom, container, oldDomElement) {
249 | let nextvDom = null, component = null, newDomElement = null;
250 | if (isFunctionalComponent(vdom)) {
251 | nextvDom = buildFunctionalComponent(vdom);
252 | } else {
253 | nextvDom = buildStatefulComponent(vdom);
254 | component = nextvDom.component;
255 | }
256 |
257 | // Recursively render child components
258 | if (isFunction(nextvDom)) {
259 | return mountComponent(nextvDom, container, oldDomElement);
260 | } else {
261 | newDomElement = mountElement(nextvDom, container, oldDomElement);
262 | }
263 |
264 | if (component) {
265 | component.componentDidMount(); // Life cycle method
266 | if (component.props.ref) {
267 | component.props.ref(component);
268 | }
269 | }
270 |
271 | return newDomElement;
272 | }
273 |
274 | function unmountNode(domElement, parentComponent) {
275 | const virtualElement = domElement._virtualElement;
276 | if (!virtualElement) {
277 | domElement.remove();
278 | return;
279 | }
280 |
281 | // If component exist
282 | let oldComponent = domElement._virtualElement.component;
283 | if (oldComponent) {
284 | oldComponent.componentWillUnmount();
285 | }
286 |
287 | // Recursive calls agains
288 | while (domElement.childNodes.length > 0) {
289 | unmountNode(domElement.firstChild);
290 | }
291 |
292 | if (virtualElement.props && virtualElement.props.ref) {
293 | virtualElement.props.ref(null);
294 | }
295 |
296 | // Clear out event handlers
297 | Object.keys(virtualElement.props).forEach(propName => {
298 | if (propName.slice(0, 2) === "on") {
299 | const event = propName.toLowerCase().slice(2);
300 | const handler = virtualElement.props[propName];
301 | domElement.removeEventListener(event, handler);
302 | }
303 | });
304 |
305 | domElement.remove();
306 | }
307 |
308 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
309 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
310 | domElement.textContent = newVirtualElement.props.textContent;
311 | }
312 | // Set a reference to the newvddom in oldDom
313 | domElement._virtualElement = newVirtualElement;
314 | }
315 |
316 |
317 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
318 | let newDomElement = null;
319 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
320 |
321 | if (vdom.type === "text") {
322 | newDomElement = document.createTextNode(vdom.props.textContent);
323 | } else {
324 | newDomElement = document.createElement(vdom.type);
325 | updateDomElement(newDomElement, vdom);
326 | }
327 |
328 | // Setting reference to vdom to dom
329 | newDomElement._virtualElement = vdom;
330 |
331 | // TODO: Remove old nodes
332 | if (oldDomElement) {
333 | unmountNode(oldDomElement, parentComponent);
334 | }
335 |
336 | if (nextSibling) {
337 | container.insertBefore(newDomElement, nextSibling);
338 | } else {
339 | container.appendChild(newDomElement);
340 | }
341 |
342 | let component = vdom.component;
343 | if (component) {
344 | component.setDomElement(newDomElement);
345 | }
346 |
347 | vdom.children.forEach(child => {
348 | mountElement(child, newDomElement);
349 | });
350 |
351 | if (vdom.props && vdom.props.ref) {
352 | vdom.props.ref(newDomElement);
353 | }
354 |
355 | }
356 |
357 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
358 | const newProps = newVirtualElement.props || {};
359 | const oldProps = oldVirtualElement.props || {};
360 | Object.keys(newProps).forEach(propName => {
361 | const newProp = newProps[propName];
362 | const oldProp = oldProps[propName];
363 | if (newProp !== oldProp) {
364 | if (propName.slice(0, 2) === "on") {
365 | // prop is an event handler
366 | const eventName = propName.toLowerCase().slice(2);
367 | domElement.addEventListener(eventName, newProp, false);
368 | if (oldProp) {
369 | domElement.removeEventListener(eventName, oldProp, false);
370 | }
371 | } else if (propName === "value" || propName === "checked") {
372 | // this are special attributes that cannot be set
373 | // using setAttribute
374 | domElement[propName] = newProp;
375 | } else if (propName !== "children") {
376 |
377 | // ignore the 'children' prop
378 | if (propName === "className") {
379 | domElement.setAttribute("class", newProps[propName]);
380 | } else if (propName === "style" && !newProps[propName].substring) {
381 | let styleText = styleObjToCss(newProps[propName]);
382 | domElement.style = styleText;
383 | }
384 | else {
385 | domElement.setAttribute(propName, newProps[propName]);
386 | }
387 | }
388 | }
389 | });
390 | // remove oldProps
391 | Object.keys(oldProps).forEach(propName => {
392 | const newProp = newProps[propName];
393 | const oldProp = oldProps[propName];
394 | if (!newProp) {
395 | if (propName.slice(0, 2) === "on") {
396 | // prop is an event handler
397 | domElement.removeEventListener(propName, oldProp, false);
398 | } else if (propName !== "children") {
399 | // ignore the 'children' prop
400 | domElement.removeAttribute(propName);
401 | }
402 | }
403 | });
404 | }
405 |
406 | function styleObjToCss(styleObj) {
407 | // I am not checking for non-dimensional props here
408 | // Assuming the correct dimensional values are passed for e.g. 10px etc
409 |
410 | let styleCss = "", sep = ":", term = ";";
411 |
412 | for (let prop in styleObj) {
413 | if (styleObj.hasOwnProperty(prop)) {
414 | let val = styleObj[prop];
415 | styleCss += `${jsToCss(prop)} : ${val} ${term}`;
416 | }
417 | }
418 |
419 | return styleCss;
420 |
421 | }
422 |
423 | function jsToCss(s) {
424 | // OLd preact code base.
425 | let transformedText = s.replace(/([A-Z])/, '-$1').toLowerCase();
426 | // borderBottom transform to border-bottom
427 | return transformedText;
428 | }
429 |
430 | class Component {
431 | constructor(props) {
432 | this.props = props;
433 | this.state = {};
434 | this.prevState = {};
435 | }
436 |
437 | setState(nextState) {
438 | if (!this.prevState) this.prevState = this.state;
439 |
440 | this.state = Object.assign({}, this.state, nextState);
441 |
442 | let dom = this.getDomElement();
443 | let container = dom.parentNode;
444 |
445 | let newvdom = this.render();
446 |
447 | // Recursively diff
448 | diff(newvdom, container, dom);
449 |
450 | }
451 |
452 | // Helper methods
453 | setDomElement(dom) {
454 | this._dom = dom;
455 | }
456 |
457 | getDomElement() {
458 | return this._dom;
459 | }
460 |
461 | updateProps(props) {
462 | this.props = props;
463 | }
464 |
465 | // Lifecycle methods
466 | componentWillMount() { }
467 | componentDidMount() { }
468 | componentWillReceiveProps(nextProps) { }
469 |
470 | shouldComponentUpdate(nextProps, nextState) {
471 | return nextProps != this.props || nextState != this.state;
472 | }
473 |
474 | componentWillUpdate(nextProps, nextState) { }
475 |
476 | componentDidUpdate(prevProps, prevState) { }
477 |
478 | componentWillUnmount() { }
479 | }
480 |
481 |
482 | return {
483 | createElement,
484 | render,
485 | Component
486 | }
487 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.2.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement simple implementation
3 |
4 | const TinyReact = (function () {
5 | function createElement(type, attributes = {}, ...children) {
6 | const childElements = [].concat(...children).map(
7 | child =>
8 | child instanceof Object
9 | ? child
10 | : createElement("text", {
11 | textContent: child
12 | })
13 | );
14 | return {
15 | type,
16 | children: childElements,
17 | props: Object.assign({ children: childElements }, attributes)
18 | }
19 | }
20 |
21 | return {
22 | createElement
23 | }
24 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.3.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 |
5 | const TinyReact = (function () {
6 | function createElement(type, attributes = {}, ...children) {
7 | const childElements = [].concat(...children).map(
8 | child => {
9 | if (child != null && child !== true && child !== false) {
10 | return child instanceof Object
11 | ? child
12 | : createElement("text", {
13 | textContent: child
14 | })
15 | }
16 | }
17 | );
18 | return {
19 | type,
20 | children: childElements,
21 | props: Object.assign({ children: childElements }, attributes)
22 | }
23 | }
24 |
25 | return {
26 | createElement
27 | }
28 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.4.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 |
6 | const TinyReact = (function () {
7 | function createElement(type, attributes = {}, ...children) {
8 | let childElements = [].concat(...children).reduce(
9 | (acc, child) => {
10 | if (child != null && child !== true && child !== false) {
11 | if (child instanceof Object) {
12 | acc.push(child);
13 | } else {
14 | acc.push(createElement("text", {
15 | textContent: child
16 | }));
17 | }
18 | }
19 | return acc;
20 | }
21 | , []);
22 | return {
23 | type,
24 | children: childElements,
25 | props: Object.assign({ children: childElements }, attributes)
26 | }
27 | }
28 |
29 |
30 | return {
31 | createElement
32 | }
33 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.5.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 |
7 | const TinyReact = (function () {
8 | function createElement(type, attributes = {}, ...children) {
9 | let childElements = [].concat(...children).reduce(
10 | (acc, child) => {
11 | if (child != null && child !== true && child !== false) {
12 | if (child instanceof Object) {
13 | acc.push(child);
14 | } else {
15 | acc.push(createElement("text", {
16 | textContent: child
17 | }));
18 | }
19 | }
20 | return acc;
21 | }
22 | , []);
23 | return {
24 | type,
25 | children: childElements,
26 | props: Object.assign({ children: childElements }, attributes)
27 | }
28 | }
29 |
30 | const render = function (vdom, container, oldDom = container.firstChild) {
31 | if (!oldDom) {
32 | mountElement(vdom, container, oldDom);
33 | }
34 | }
35 |
36 | const mountElement = function (vdom, container, oldDom) {
37 | // Native DOM elements as well as functions.
38 | return mountSimpleNode(vdom, container, oldDom);
39 | }
40 |
41 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
42 | let newDomElement = null;
43 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
44 |
45 | if (vdom.type === "text") {
46 | newDomElement = document.createTextNode(vdom.props.textContent);
47 | } else {
48 | newDomElement = document.createElement(vdom.type);
49 | }
50 |
51 | // Setting reference to vdom to dom
52 | newDomElement._virtualElement = vdom;
53 | if (nextSibling) {
54 | container.insertBefore(newDomElement, nextSibling);
55 | } else {
56 | container.appendChild(newDomElement);
57 | }
58 |
59 | //TODO: Render children
60 | vdom.children.forEach(child => {
61 | mountElement(child, newDomElement);
62 | });
63 |
64 | }
65 |
66 |
67 | return {
68 | createElement,
69 | render
70 | }
71 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.6.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 |
8 | const TinyReact = (function () {
9 | function createElement(type, attributes = {}, ...children) {
10 | let childElements = [].concat(...children).reduce(
11 | (acc, child) => {
12 | if (child != null && child !== true && child !== false) {
13 | if (child instanceof Object) {
14 | acc.push(child);
15 | } else {
16 | acc.push(createElement("text", {
17 | textContent: child
18 | }));
19 | }
20 | }
21 | return acc;
22 | }
23 | , []);
24 | return {
25 | type,
26 | children: childElements,
27 | props: Object.assign({ children: childElements }, attributes)
28 | }
29 | }
30 |
31 | const render = function (vdom, container, oldDom = container.firstChild) {
32 | if (!oldDom) {
33 | mountElement(vdom, container, oldDom);
34 | }
35 | }
36 |
37 | const mountElement = function (vdom, container, oldDom) {
38 | // Native DOM elements as well as functions.
39 | return mountSimpleNode(vdom, container, oldDom);
40 | }
41 |
42 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
43 | let newDomElement = null;
44 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
45 |
46 | if (vdom.type === "text") {
47 | newDomElement = document.createTextNode(vdom.props.textContent);
48 | } else {
49 | newDomElement = document.createElement(vdom.type);
50 | updateDomElement(newDomElement, vdom);
51 | }
52 |
53 | // Setting reference to vdom to dom
54 | newDomElement._virtualElement = vdom;
55 | if (nextSibling) {
56 | container.insertBefore(newDomElement, nextSibling);
57 | } else {
58 | container.appendChild(newDomElement);
59 | }
60 |
61 | //TODO: Render children
62 | vdom.children.forEach(child => {
63 | mountElement(child, newDomElement);
64 | });
65 |
66 | }
67 |
68 | //TODO: Set DOM attributes and events
69 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
70 | const newProps = newVirtualElement.props || {};
71 | const oldProps = oldVirtualElement.props || {};
72 | Object.keys(newProps).forEach(propName => {
73 | const newProp = newProps[propName];
74 | const oldProp = oldProps[propName];
75 | if (newProp !== oldProp) {
76 | if (propName.slice(0, 2) === "on") {
77 | // prop is an event handler
78 | const eventName = propName.toLowerCase().slice(2);
79 | domElement.addEventListener(eventName, newProp, false);
80 | if (oldProp) {
81 | domElement.removeEventListener(eventName, oldProp, false);
82 | }
83 | } else if (propName === "value" || propName === "checked") {
84 | // this are special attributes that cannot be set
85 | // using setAttribute
86 | domElement[propName] = newProp;
87 | } else if (propName !== "children") {
88 | // ignore the 'children' prop
89 | if (propName === "className") {
90 | domElement.setAttribute("class", newProps[propName]);
91 | } else {
92 | domElement.setAttribute(propName, newProps[propName]);
93 | }
94 | }
95 | }
96 | });
97 | // remove oldProps
98 | Object.keys(oldProps).forEach(propName => {
99 | const newProp = newProps[propName];
100 | const oldProp = oldProps[propName];
101 | if (!newProp) {
102 | if (propName.slice(0, 2) === "on") {
103 | // prop is an event handler
104 | domElement.removeEventListener(propName, oldProp, false);
105 | } else if (propName !== "children") {
106 | // ignore the 'children' prop
107 | domElement.removeAttribute(propName);
108 | }
109 | }
110 | });
111 | }
112 |
113 |
114 | return {
115 | createElement,
116 | render
117 | }
118 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.7.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 |
9 | const TinyReact = (function () {
10 | function createElement(type, attributes = {}, ...children) {
11 | let childElements = [].concat(...children).reduce(
12 | (acc, child) => {
13 | if (child != null && child !== true && child !== false) {
14 | if (child instanceof Object) {
15 | acc.push(child);
16 | } else {
17 | acc.push(createElement("text", {
18 | textContent: child
19 | }));
20 | }
21 | }
22 | return acc;
23 | }
24 | , []);
25 | return {
26 | type,
27 | children: childElements,
28 | props: Object.assign({ children: childElements }, attributes)
29 | }
30 | }
31 |
32 | const render = function (vdom, container, oldDom = container.firstChild) {
33 | diff(vdom, container, oldDom);
34 | }
35 |
36 | const diff = function (vdom, container, oldDom) {
37 | let oldvdom = oldDom && oldDom._virtualElement;
38 |
39 | if (!oldDom) {
40 | mountElement(vdom, container, oldDom);
41 | }
42 | else if (oldvdom && oldvdom.type === vdom.type) {
43 | if (oldvdom.type === "text") {
44 | updateTextNode(oldDom, vdom, oldvdom);
45 | } else {
46 | updateDomElement(oldDom, vdom, oldvdom);
47 | }
48 |
49 | // Set a reference to updated vdom
50 | oldDom._virtualElement = vdom;
51 |
52 | // Recursively diff children..
53 | // Doing an index by index diffing (because we don't have keys yet)
54 | vdom.children.forEach((child, i) => {
55 | diff(child, oldDom, oldDom.childNodes[i]);
56 | });
57 |
58 | }
59 | }
60 |
61 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
62 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
63 | domElement.textContent = newVirtualElement.props.textContent;
64 | }
65 | // Set a reference to the newvddom in oldDom
66 | domElement._virtualElement = newVirtualElement;
67 | }
68 |
69 |
70 | const mountElement = function (vdom, container, oldDom) {
71 | // Native DOM elements as well as functions.
72 | return mountSimpleNode(vdom, container, oldDom);
73 | }
74 |
75 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
76 | let newDomElement = null;
77 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
78 |
79 | if (vdom.type === "text") {
80 | newDomElement = document.createTextNode(vdom.props.textContent);
81 | } else {
82 | newDomElement = document.createElement(vdom.type);
83 | updateDomElement(newDomElement, vdom);
84 | }
85 |
86 | // Setting reference to vdom to dom
87 | newDomElement._virtualElement = vdom;
88 | if (nextSibling) {
89 | container.insertBefore(newDomElement, nextSibling);
90 | } else {
91 | container.appendChild(newDomElement);
92 | }
93 |
94 | //TODO: Render children
95 | vdom.children.forEach(child => {
96 | mountElement(child, newDomElement);
97 | });
98 |
99 | }
100 |
101 | //TODO: Set DOM attributes and events
102 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
103 | const newProps = newVirtualElement.props || {};
104 | const oldProps = oldVirtualElement.props || {};
105 | Object.keys(newProps).forEach(propName => {
106 | const newProp = newProps[propName];
107 | const oldProp = oldProps[propName];
108 | if (newProp !== oldProp) {
109 | if (propName.slice(0, 2) === "on") {
110 | // prop is an event handler
111 | const eventName = propName.toLowerCase().slice(2);
112 | domElement.addEventListener(eventName, newProp, false);
113 | if (oldProp) {
114 | domElement.removeEventListener(eventName, oldProp, false);
115 | }
116 | } else if (propName === "value" || propName === "checked") {
117 | // this are special attributes that cannot be set
118 | // using setAttribute
119 | domElement[propName] = newProp;
120 | } else if (propName !== "children") {
121 | // ignore the 'children' prop
122 | if (propName === "className") {
123 | domElement.setAttribute("class", newProps[propName]);
124 | } else {
125 | domElement.setAttribute(propName, newProps[propName]);
126 | }
127 | }
128 | }
129 | });
130 | // remove oldProps
131 | Object.keys(oldProps).forEach(propName => {
132 | const newProp = newProps[propName];
133 | const oldProp = oldProps[propName];
134 | if (!newProp) {
135 | if (propName.slice(0, 2) === "on") {
136 | // prop is an event handler
137 | domElement.removeEventListener(propName, oldProp, false);
138 | } else if (propName !== "children") {
139 | // ignore the 'children' prop
140 | domElement.removeAttribute(propName);
141 | }
142 | }
143 | });
144 | }
145 |
146 |
147 | return {
148 | createElement,
149 | render
150 | }
151 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.8.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 |
10 | const TinyReact = (function () {
11 | function createElement(type, attributes = {}, ...children) {
12 | let childElements = [].concat(...children).reduce(
13 | (acc, child) => {
14 | if (child != null && child !== true && child !== false) {
15 | if (child instanceof Object) {
16 | acc.push(child);
17 | } else {
18 | acc.push(createElement("text", {
19 | textContent: child
20 | }));
21 | }
22 | }
23 | return acc;
24 | }
25 | , []);
26 | return {
27 | type,
28 | children: childElements,
29 | props: Object.assign({ children: childElements }, attributes)
30 | }
31 | }
32 |
33 | const render = function (vdom, container, oldDom = container.firstChild) {
34 | diff(vdom, container, oldDom);
35 | }
36 |
37 | const diff = function (vdom, container, oldDom) {
38 | let oldvdom = oldDom && oldDom._virtualElement;
39 |
40 | if (!oldDom) {
41 | mountElement(vdom, container, oldDom);
42 | }
43 | else if (oldvdom && oldvdom.type === vdom.type) {
44 | if (oldvdom.type === "text") {
45 | updateTextNode(oldDom, vdom, oldvdom);
46 | } else {
47 | updateDomElement(oldDom, vdom, oldvdom);
48 | }
49 |
50 | // Set a reference to updated vdom
51 | oldDom._virtualElement = vdom;
52 |
53 | // Recursively diff children..
54 | // Doing an index by index diffing (because we don't have keys yet)
55 | vdom.children.forEach((child, i) => {
56 | diff(child, oldDom, oldDom.childNodes[i]);
57 | });
58 |
59 | // Remove old dom nodes
60 | let oldNodes = oldDom.childNodes;
61 | if (oldNodes.length > vdom.children.length) {
62 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
63 | let nodeToBeRemoved = oldNodes[i];
64 | unmountNode(nodeToBeRemoved, oldDom);
65 | }
66 | }
67 |
68 | }
69 | }
70 |
71 | function unmountNode(domElement, parentComponent) {
72 | domElement.remove();
73 | }
74 |
75 |
76 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
77 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
78 | domElement.textContent = newVirtualElement.props.textContent;
79 | }
80 | // Set a reference to the newvddom in oldDom
81 | domElement._virtualElement = newVirtualElement;
82 | }
83 |
84 |
85 | const mountElement = function (vdom, container, oldDom) {
86 | // Native DOM elements as well as functions.
87 | return mountSimpleNode(vdom, container, oldDom);
88 | }
89 |
90 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
91 | let newDomElement = null;
92 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
93 |
94 | if (vdom.type === "text") {
95 | newDomElement = document.createTextNode(vdom.props.textContent);
96 | } else {
97 | newDomElement = document.createElement(vdom.type);
98 | updateDomElement(newDomElement, vdom);
99 | }
100 |
101 | // Setting reference to vdom to dom
102 | newDomElement._virtualElement = vdom;
103 | if (nextSibling) {
104 | container.insertBefore(newDomElement, nextSibling);
105 | } else {
106 | container.appendChild(newDomElement);
107 | }
108 |
109 | //TODO: Render children
110 | vdom.children.forEach(child => {
111 | mountElement(child, newDomElement);
112 | });
113 |
114 | }
115 |
116 | //TODO: Set DOM attributes and events
117 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
118 | const newProps = newVirtualElement.props || {};
119 | const oldProps = oldVirtualElement.props || {};
120 | Object.keys(newProps).forEach(propName => {
121 | const newProp = newProps[propName];
122 | const oldProp = oldProps[propName];
123 | if (newProp !== oldProp) {
124 | if (propName.slice(0, 2) === "on") {
125 | // prop is an event handler
126 | const eventName = propName.toLowerCase().slice(2);
127 | domElement.addEventListener(eventName, newProp, false);
128 | if (oldProp) {
129 | domElement.removeEventListener(eventName, oldProp, false);
130 | }
131 | } else if (propName === "value" || propName === "checked") {
132 | // this are special attributes that cannot be set
133 | // using setAttribute
134 | domElement[propName] = newProp;
135 | } else if (propName !== "children") {
136 | // ignore the 'children' prop
137 | if (propName === "className") {
138 | domElement.setAttribute("class", newProps[propName]);
139 | } else {
140 | domElement.setAttribute(propName, newProps[propName]);
141 | }
142 | }
143 | }
144 | });
145 | // remove oldProps
146 | Object.keys(oldProps).forEach(propName => {
147 | const newProp = newProps[propName];
148 | const oldProp = oldProps[propName];
149 | if (!newProp) {
150 | if (propName.slice(0, 2) === "on") {
151 | // prop is an event handler
152 | domElement.removeEventListener(propName, oldProp, false);
153 | } else if (propName !== "children") {
154 | // ignore the 'children' prop
155 | domElement.removeAttribute(propName);
156 | }
157 | }
158 | });
159 | }
160 |
161 |
162 | return {
163 | createElement,
164 | render
165 | }
166 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.9.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 |
11 | const TinyReact = (function () {
12 | function createElement(type, attributes = {}, ...children) {
13 | let childElements = [].concat(...children).reduce(
14 | (acc, child) => {
15 | if (child != null && child !== true && child !== false) {
16 | if (child instanceof Object) {
17 | acc.push(child);
18 | } else {
19 | acc.push(createElement("text", {
20 | textContent: child
21 | }));
22 | }
23 | }
24 | return acc;
25 | }
26 | , []);
27 | return {
28 | type,
29 | children: childElements,
30 | props: Object.assign({ children: childElements }, attributes)
31 | }
32 | }
33 |
34 | const render = function (vdom, container, oldDom = container.firstChild) {
35 | diff(vdom, container, oldDom);
36 | }
37 |
38 | const diff = function (vdom, container, oldDom) {
39 | let oldvdom = oldDom && oldDom._virtualElement;
40 |
41 | if (!oldDom) {
42 | mountElement(vdom, container, oldDom);
43 | }
44 | else if (typeof vdom.type === "function") {
45 | diffComponent(vdom, null, container, oldDom);
46 | }
47 | else if (oldvdom && oldvdom.type === vdom.type) {
48 | if (oldvdom.type === "text") {
49 | updateTextNode(oldDom, vdom, oldvdom);
50 | } else {
51 | updateDomElement(oldDom, vdom, oldvdom);
52 | }
53 |
54 | // Set a reference to updated vdom
55 | oldDom._virtualElement = vdom;
56 |
57 | // Recursively diff children..
58 | // Doing an index by index diffing (because we don't have keys yet)
59 | vdom.children.forEach((child, i) => {
60 | diff(child, oldDom, oldDom.childNodes[i]);
61 | });
62 |
63 | // Remove old dom nodes
64 | let oldNodes = oldDom.childNodes;
65 | if (oldNodes.length > vdom.children.length) {
66 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
67 | let nodeToBeRemoved = oldNodes[i];
68 | unmountNode(nodeToBeRemoved, oldDom);
69 | }
70 | }
71 |
72 | }
73 | }
74 |
75 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
76 | if (!oldComponent) {
77 | mountElement(newVirtualElement, container, domElement);
78 | }
79 | }
80 |
81 | const mountElement = function (vdom, container, oldDom) {
82 | if (isFunction(vdom)) {
83 | return mountComponent(vdom, container, oldDom);
84 | } else {
85 | return mountSimpleNode(vdom, container, oldDom);
86 | }
87 | }
88 |
89 | function isFunction(obj) {
90 | return obj && 'function' === typeof obj.type;
91 | }
92 |
93 | function isFunctionalComponent(vnode) {
94 | let nodeType = vnode && vnode.type;
95 | return nodeType && isFunction(vnode)
96 | && !(nodeType.prototype && nodeType.prototype.render);
97 | }
98 |
99 | function buildFunctionalComponent(vnode, context) {
100 | return vnode.type();
101 | }
102 |
103 |
104 | function mountComponent(vdom, container, oldDomElement) {
105 | let nextvDom = null, component = null, newDomElement = null;
106 | if (isFunctionalComponent(vdom)) {
107 | nextvDom = buildFunctionalComponent(vdom);
108 | }
109 |
110 | // Recursively render child components
111 | if (isFunction(nextvDom)) {
112 | return mountComponent(nextvDom, container, oldDomElement);
113 | } else {
114 | newDomElement = mountElement(nextvDom, container, oldDomElement);
115 | }
116 | return newDomElement;
117 |
118 | }
119 |
120 | function unmountNode(domElement, parentComponent) {
121 | domElement.remove();
122 | }
123 |
124 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
125 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
126 | domElement.textContent = newVirtualElement.props.textContent;
127 | }
128 | // Set a reference to the newvddom in oldDom
129 | domElement._virtualElement = newVirtualElement;
130 | }
131 |
132 |
133 |
134 |
135 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
136 | let newDomElement = null;
137 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
138 |
139 | if (vdom.type === "text") {
140 | newDomElement = document.createTextNode(vdom.props.textContent);
141 | } else {
142 | newDomElement = document.createElement(vdom.type);
143 | updateDomElement(newDomElement, vdom);
144 | }
145 |
146 | // Setting reference to vdom to dom
147 | newDomElement._virtualElement = vdom;
148 | if (nextSibling) {
149 | container.insertBefore(newDomElement, nextSibling);
150 | } else {
151 | container.appendChild(newDomElement);
152 | }
153 |
154 | //TODO: Render children
155 | vdom.children.forEach(child => {
156 | mountElement(child, newDomElement);
157 | });
158 |
159 | }
160 |
161 | //TODO: Set DOM attributes and events
162 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
163 | const newProps = newVirtualElement.props || {};
164 | const oldProps = oldVirtualElement.props || {};
165 | Object.keys(newProps).forEach(propName => {
166 | const newProp = newProps[propName];
167 | const oldProp = oldProps[propName];
168 | if (newProp !== oldProp) {
169 | if (propName.slice(0, 2) === "on") {
170 | // prop is an event handler
171 | const eventName = propName.toLowerCase().slice(2);
172 | domElement.addEventListener(eventName, newProp, false);
173 | if (oldProp) {
174 | domElement.removeEventListener(eventName, oldProp, false);
175 | }
176 | } else if (propName === "value" || propName === "checked") {
177 | // this are special attributes that cannot be set
178 | // using setAttribute
179 | domElement[propName] = newProp;
180 | } else if (propName !== "children") {
181 | // ignore the 'children' prop
182 | if (propName === "className") {
183 | domElement.setAttribute("class", newProps[propName]);
184 | } else {
185 | domElement.setAttribute(propName, newProps[propName]);
186 | }
187 | }
188 | }
189 | });
190 | // remove oldProps
191 | Object.keys(oldProps).forEach(propName => {
192 | const newProp = newProps[propName];
193 | const oldProp = oldProps[propName];
194 | if (!newProp) {
195 | if (propName.slice(0, 2) === "on") {
196 | // prop is an event handler
197 | domElement.removeEventListener(propName, oldProp, false);
198 | } else if (propName !== "children") {
199 | // ignore the 'children' prop
200 | domElement.removeAttribute(propName);
201 | }
202 | }
203 | });
204 | }
205 |
206 |
207 | return {
208 | createElement,
209 | render
210 | }
211 | }());
--------------------------------------------------------------------------------
/lib/demo-step-by-step/dom.js:
--------------------------------------------------------------------------------
1 | // 1. createElement Stub
2 | // 2. createElement Basic Implementation
3 | // 3. createElement Handle true/false short circuiting
4 | // 4. createElement remove undefined nodes
5 | // 5. rendering native DOM elements along with children
6 | // 6. set dom attributes and events
7 | // 7. diffing two trees of native DOM elements
8 | // 8. removing extra nodes
9 | // 9. rendering functional component
10 | // 10. passing props to functional component
11 | // 11. functional component diffing (remove extra nodes)
12 | // 12. rendering stateful component
13 | // 13. passing props to stateful component
14 | // 14. implement setState() method -> parameter as object
15 | // 15. implement the stub for lifecycle method (TODO)
16 | // 16. diffing stateful components (ISSUE: new props coming as #text->object should be toString() in app)
17 | // 17. Adding support for ref (todo)
18 | // 18. More test cases, old and new types are different.
19 | // 19. Add support for passing style attributes as objects
20 |
21 |
22 | const TinyReact = (function () {
23 | function createElement(type, attributes = {}, ...children) {
24 | let childElements = [].concat(...children).reduce(
25 | (acc, child) => {
26 | if (child != null && child !== true && child !== false) {
27 | if (child instanceof Object) {
28 | acc.push(child);
29 | } else {
30 | acc.push(createElement("text", {
31 | textContent: child
32 | }));
33 | }
34 | }
35 | return acc;
36 | }
37 | , []);
38 | return {
39 | type,
40 | children: childElements,
41 | props: Object.assign({ children: childElements }, attributes)
42 | }
43 | }
44 |
45 | const render = function (vdom, container, oldDom = container.firstChild) {
46 | diff(vdom, container, oldDom);
47 | }
48 |
49 | const diff = function (vdom, container, oldDom) {
50 | let oldvdom = oldDom && oldDom._virtualElement;
51 | let oldComponent = oldvdom && oldvdom.component;
52 |
53 | if (!oldDom) {
54 | mountElement(vdom, container, oldDom);
55 | }
56 | else if ((vdom.type !== oldvdom.type) && (typeof vdom.type !== "function")) {
57 | let newDomElement = createDomElement(vdom); // todo:
58 | oldDom.parentNode.replaceChild(newDomElement, oldDom);
59 | }
60 | else if (typeof vdom.type === "function") {
61 | diffComponent(vdom, oldComponent, container, oldDom);
62 | }
63 | else if (oldvdom && oldvdom.type === vdom.type) {
64 | if (oldvdom.type === "text") {
65 | updateTextNode(oldDom, vdom, oldvdom);
66 | } else {
67 | updateDomElement(oldDom, vdom, oldvdom);
68 | }
69 |
70 | // Set a reference to updated vdom
71 | oldDom._virtualElement = vdom;
72 |
73 | // Lets create a collection of keyed elements
74 | let keyedElements = {};
75 | for (let i = 0; i < oldDom.childNodes.length; i += 1) {
76 | const domElement = oldDom.childNodes[i];
77 | const key = domElement._virtualElement.props.key;
78 |
79 | if (key) {
80 | keyedElements[key] = {
81 | domElement,
82 | index: i
83 | };
84 | }
85 |
86 | }
87 |
88 |
89 | // Recursively diff children..
90 | // Doing an index by index diffing (because we don't have keys yet)
91 | if (Object.keys(keyedElements).length === 0) {
92 | vdom.children.forEach((child, i) => {
93 | diff(child, oldDom, oldDom.childNodes[i]);
94 | });
95 | } else {
96 | // Reconciliation based on keys
97 | vdom.children.forEach((virtualElement, i) => {
98 | const key = virtualElement.props.key;
99 | if (key) {
100 | const keyedDomElement = keyedElements[key];
101 | if (keyedDomElement) {
102 | // Position the new element correctly based on key/index
103 | if (oldDom.childNodes[i] && !oldDom.childNodes[i].isSameNode(keyedDomElement.domElement)) {
104 | oldDom.insertBefore(keyedDomElement.domElement,
105 | oldDom.childNodes[i]);
106 | }
107 | diff(virtualElement, oldDom, keyedDomElement.domElement);
108 | }
109 | else {
110 | mountElement(virtualElement, oldDom);
111 | }
112 | }
113 | });
114 | }
115 |
116 | // Remove old dom nodes
117 | let oldNodes = oldDom.childNodes;
118 | if (Object.keys(keyedElements).length === 0) {
119 | if (oldNodes.length > vdom.children.length) {
120 | for (let i = oldNodes.length - 1; i >= vdom.children.length; i -= 1) {
121 | let nodeToBeRemoved = oldNodes[i];
122 | unmountNode(nodeToBeRemoved, oldDom);
123 | }
124 | }
125 | } else {
126 | if (oldNodes.length > vdom.children.length) {
127 | for (let i = 0; i < oldDom.childNodes.length; i += 1) {
128 | let oldChild = oldDom.childNodes[i];
129 | let oldKey = oldChild.getAttribute("key");
130 |
131 | let found = false;
132 | for (let n = 0; n < vdom.children.length; n += 1) {
133 | if (vdom.children[n].props.key == oldKey) {
134 | found = true;
135 | break;
136 | }
137 | }
138 |
139 | if (!found) {
140 | unmountNode(oldChild, oldDom);
141 | }
142 |
143 | }
144 | }
145 | }
146 |
147 | }
148 | }
149 |
150 | function createDomElement(vdom) {
151 | let newDomElement = null;
152 | if (vdom.type === "text") {
153 | newDomElement = document.createTextNode(vdom.props.textContent);
154 | } else {
155 | newDomElement = document.createElement(vdom.type);
156 | updateDomElement(newDomElement, vdom);
157 | }
158 |
159 | newDomElement._virtualElement = vdom;
160 | vdom.children.forEach((child) => {
161 | newDomElement.appendChild(createDomElement(child));
162 | });
163 |
164 | // Set refs
165 | if (vdom.props && vdom.props.ref) {
166 | vdom.props.ref(newDomElement);
167 | }
168 |
169 | return newDomElement;
170 | }
171 |
172 | function diffComponent(newVirtualElement, oldComponent, container, domElement) {
173 |
174 | if (isSameComponentType(oldComponent, newVirtualElement)) {
175 | updateComponent(newVirtualElement, oldComponent, container, domElement);
176 | } else {
177 | mountElement(newVirtualElement, container, domElement);
178 | }
179 | }
180 |
181 | function updateComponent(newVirtualElement, oldComponent, container, domElement) {
182 |
183 | // Lifecycle method
184 | oldComponent.componentWillReceiveProps(newVirtualElement.props);
185 |
186 | // Lifecycle method
187 | if (oldComponent.shouldComponentUpdate(newVirtualElement.props)) {
188 | const prevProps = oldComponent.props;
189 |
190 | // Invoke LifeCycle
191 | oldComponent.componentWillUpdate(
192 | newVirtualElement.props,
193 | oldComponent.state
194 | );
195 |
196 | // Update component
197 | oldComponent.updateProps(newVirtualElement.props);
198 |
199 | // Call Render
200 | // Generate new vdom
201 | const nextElement = oldComponent.render();
202 | nextElement.component = oldComponent;
203 |
204 | // Recursively diff again
205 | diff(nextElement, container, domElement, oldComponent);
206 |
207 | // Invoke LifeCycle
208 | oldComponent.componentDidUpdate(prevProps);
209 |
210 | }
211 |
212 | }
213 |
214 | function isSameComponentType(oldComponent, newVirtualElement) {
215 | return oldComponent && newVirtualElement.type === oldComponent.constructor;
216 | }
217 |
218 | const mountElement = function (vdom, container, oldDom) {
219 | if (isFunction(vdom)) {
220 | return mountComponent(vdom, container, oldDom);
221 | } else {
222 | return mountSimpleNode(vdom, container, oldDom);
223 | }
224 | }
225 |
226 | function isFunction(obj) {
227 | return obj && 'function' === typeof obj.type;
228 | }
229 |
230 | function isFunctionalComponent(vnode) {
231 | let nodeType = vnode && vnode.type;
232 | return nodeType && isFunction(vnode)
233 | && !(nodeType.prototype && nodeType.prototype.render);
234 | }
235 |
236 | function buildFunctionalComponent(vnode, context) {
237 | return vnode.type(vnode.props || {});
238 | }
239 |
240 |
241 | function buildStatefulComponent(virtualElement) {
242 | const component = new virtualElement.type(virtualElement.props);
243 | const nextElement = component.render();
244 | nextElement.component = component;
245 | return nextElement;
246 | }
247 |
248 | function mountComponent(vdom, container, oldDomElement) {
249 | let nextvDom = null, component = null, newDomElement = null;
250 | if (isFunctionalComponent(vdom)) {
251 | nextvDom = buildFunctionalComponent(vdom);
252 | } else {
253 | nextvDom = buildStatefulComponent(vdom);
254 | component = nextvDom.component;
255 | }
256 |
257 | // Recursively render child components
258 | if (isFunction(nextvDom)) {
259 | return mountComponent(nextvDom, container, oldDomElement);
260 | } else {
261 | newDomElement = mountElement(nextvDom, container, oldDomElement);
262 | }
263 |
264 | if (component) {
265 | component.componentDidMount(); // Life cycle method
266 | if (component.props.ref) {
267 | component.props.ref(component);
268 | }
269 | }
270 |
271 | return newDomElement;
272 | }
273 |
274 | function unmountNode(domElement, parentComponent) {
275 | const virtualElement = domElement._virtualElement;
276 | if (!virtualElement) {
277 | domElement.remove();
278 | return;
279 | }
280 |
281 | // If component exist
282 | let oldComponent = domElement._virtualElement.component;
283 | if (oldComponent) {
284 | oldComponent.componentWillUnmount();
285 | }
286 |
287 | // Recursive calls agains
288 | while (domElement.childNodes.length > 0) {
289 | unmountNode(domElement.firstChild);
290 | }
291 |
292 | if (virtualElement.props && virtualElement.props.ref) {
293 | virtualElement.props.ref(null);
294 | }
295 |
296 | // Clear out event handlers
297 | Object.keys(virtualElement.props).forEach(propName => {
298 | if (propName.slice(0, 2) === "on") {
299 | const event = propName.toLowerCase().slice(2);
300 | const handler = virtualElement.props[propName];
301 | domElement.removeEventListener(event, handler);
302 | }
303 | });
304 |
305 | domElement.remove();
306 | }
307 |
308 | function updateTextNode(domElement, newVirtualElement, oldVirtualElement) {
309 | if (newVirtualElement.props.textContent !== oldVirtualElement.props.textContent) {
310 | domElement.textContent = newVirtualElement.props.textContent;
311 | }
312 | // Set a reference to the newvddom in oldDom
313 | domElement._virtualElement = newVirtualElement;
314 | }
315 |
316 |
317 | const mountSimpleNode = function (vdom, container, oldDomElement, parentComponent) {
318 | let newDomElement = null;
319 | const nextSibling = oldDomElement && oldDomElement.nextSibling;
320 |
321 | if (vdom.type === "text") {
322 | newDomElement = document.createTextNode(vdom.props.textContent);
323 | } else {
324 | newDomElement = document.createElement(vdom.type);
325 | updateDomElement(newDomElement, vdom);
326 | }
327 |
328 | // Setting reference to vdom to dom
329 | newDomElement._virtualElement = vdom;
330 |
331 | // TODO: Remove old nodes
332 | if (oldDomElement) {
333 | unmountNode(oldDomElement, parentComponent);
334 | }
335 |
336 | if (nextSibling) {
337 | container.insertBefore(newDomElement, nextSibling);
338 | } else {
339 | container.appendChild(newDomElement);
340 | }
341 |
342 | let component = vdom.component;
343 | if (component) {
344 | component.setDomElement(newDomElement);
345 | }
346 |
347 | vdom.children.forEach(child => {
348 | mountElement(child, newDomElement);
349 | });
350 |
351 | if (vdom.props && vdom.props.ref) {
352 | vdom.props.ref(newDomElement);
353 | }
354 |
355 | }
356 |
357 | function updateDomElement(domElement, newVirtualElement, oldVirtualElement = {}) {
358 | const newProps = newVirtualElement.props || {};
359 | const oldProps = oldVirtualElement.props || {};
360 | Object.keys(newProps).forEach(propName => {
361 | const newProp = newProps[propName];
362 | const oldProp = oldProps[propName];
363 | if (newProp !== oldProp) {
364 | if (propName.slice(0, 2) === "on") {
365 | // prop is an event handler
366 | const eventName = propName.toLowerCase().slice(2);
367 | domElement.addEventListener(eventName, newProp, false);
368 | if (oldProp) {
369 | domElement.removeEventListener(eventName, oldProp, false);
370 | }
371 | } else if (propName === "value" || propName === "checked") {
372 | // this are special attributes that cannot be set
373 | // using setAttribute
374 | domElement[propName] = newProp;
375 | } else if (propName !== "children") {
376 |
377 | // ignore the 'children' prop
378 | if (propName === "className") {
379 | domElement.setAttribute("class", newProps[propName]);
380 | } else if (propName === "style" && !newProps[propName].substring) {
381 | let styleText = styleObjToCss(newProps[propName]);
382 | domElement.style = styleText;
383 | }
384 | else {
385 | domElement.setAttribute(propName, newProps[propName]);
386 | }
387 | }
388 | }
389 | });
390 | // remove oldProps
391 | Object.keys(oldProps).forEach(propName => {
392 | const newProp = newProps[propName];
393 | const oldProp = oldProps[propName];
394 | if (!newProp) {
395 | if (propName.slice(0, 2) === "on") {
396 | // prop is an event handler
397 | domElement.removeEventListener(propName, oldProp, false);
398 | } else if (propName !== "children") {
399 | // ignore the 'children' prop
400 | domElement.removeAttribute(propName);
401 | }
402 | }
403 | });
404 | }
405 |
406 | function styleObjToCss(styleObj) {
407 | // I am not checking for non-dimensional props here
408 | // Assuming the correct dimensional values are passed for e.g. 10px etc
409 |
410 | let styleCss = "", sep = ":", term = ";";
411 |
412 | for (let prop in styleObj) {
413 | if (styleObj.hasOwnProperty(prop)) {
414 | let val = styleObj[prop];
415 | styleCss += `${jsToCss(prop)} : ${val} ${term}`;
416 | }
417 | }
418 |
419 | return styleCss;
420 |
421 | }
422 |
423 | function jsToCss(s) {
424 | // OLd preact code base.
425 | let transformedText = s.replace(/([A-Z])/, '-$1').toLowerCase();
426 | // borderBottom transform to border-bottom
427 | return transformedText;
428 | }
429 |
430 | class Component {
431 | constructor(props) {
432 | this.props = props;
433 | this.state = {};
434 | this.prevState = {};
435 | }
436 |
437 | setState(nextState) {
438 | if (!this.prevState) this.prevState = this.state;
439 |
440 | this.state = Object.assign({}, this.state, nextState);
441 |
442 | let dom = this.getDomElement();
443 | let container = dom.parentNode;
444 |
445 | let newvdom = this.render();
446 |
447 | // Recursively diff
448 | diff(newvdom, container, dom);
449 |
450 | }
451 |
452 | // Helper methods
453 | setDomElement(dom) {
454 | this._dom = dom;
455 | }
456 |
457 | getDomElement() {
458 | return this._dom;
459 | }
460 |
461 | updateProps(props) {
462 | this.props = props;
463 | }
464 |
465 | // Lifecycle methods
466 | componentWillMount() { }
467 | componentDidMount() { }
468 | componentWillReceiveProps(nextProps) { }
469 |
470 | shouldComponentUpdate(nextProps, nextState) {
471 | return nextProps != this.props || nextState != this.state;
472 | }
473 |
474 | componentWillUpdate(nextProps, nextState) { }
475 |
476 | componentDidUpdate(prevProps, prevState) { }
477 |
478 | componentWillUnmount() { }
479 | }
480 |
481 |
482 | return {
483 | createElement,
484 | render,
485 | Component
486 | }
487 | }());
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "code-your-own-react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "react-mini.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "nodemon server.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "body-parser": "^1.18.3",
15 | "express": "^4.17.1"
16 | },
17 | "devDependencies": {
18 | "nodemon": "^1.18.4"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 |
4 | var port = 3333;
5 |
6 | app.use(express.static("./"));
7 |
8 | // Our first route
9 | app.get('/', function (req, res) {
10 | res.sendFile('./index.html');
11 | });
12 |
13 | // Listen to port 5000
14 | app.listen(port, function () {
15 | console.log(`Server listening on port ${port}!`);
16 | });
--------------------------------------------------------------------------------
/style/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-size: 1.15rem;
3 | background: #edeff0;
4 | padding: 0;
5 | margin: 0;
6 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
7 | height: 100vh;
8 | margin: 5px;
9 | }
10 |
11 | h1 {
12 | font-size: 2.1em;
13 | margin-top: 5px;
14 | margin-bottom: 0;
15 | color: #384047;
16 | line-height: 1;
17 | text-align: center;
18 | }
19 |
20 | h6 {
21 | text-align: center;
22 | margin-top: 6px;
23 | }
24 |
25 | .top-menu {
26 | background-color: skyblue;
27 | padding: 10px;
28 | width: 100%;
29 | text-align: center;
30 | }
31 |
32 | .top-menu a {
33 | display: inline-block;
34 | padding: 5px;
35 | }
36 |
37 | .top-menu a:hover {
38 | background-color: yellow;
39 | }
40 |
41 | .container p {
42 | text-align: center;
43 | }
44 |
45 | .todo-input-container {
46 | text-align: center;
47 | }
48 |
49 | .container {
50 | width: 450px;
51 | box-sizing: border-box;
52 | background: white;
53 | padding: 20px;
54 | border-radius: 5px;
55 | box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1);
56 | margin: 40px auto 0;
57 | }
58 |
59 | .todos {
60 | padding: 12px;
61 | margin-bottom: 20px;
62 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.12);
63 | }
64 |
65 | li {
66 | list-style-type: disc;
67 | margin-bottom: 5px;
68 | display: flex;
69 | align-items: center;
70 | }
71 |
72 | .todo-actions {
73 | margin-left: auto;
74 | }
75 |
76 | input {
77 | margin-right: 7px;
78 | caret-color: blue;
79 | }
80 |
81 | .addItemInput {
82 | width: 14em;
83 | padding: 1px 1px;
84 | margin-right: 7px;
85 | height: 1.5em;
86 | font-size: 90%;
87 | }
88 |
89 | .editItemInput {
90 | width: 14em;
91 | height: 1.6em;
92 | }
93 |
94 | /* Buttons */
95 | button {
96 | color: white;
97 | background: #508abc;
98 | border: solid 1px;
99 | border-color: rgba(0, 0, 0, 0.1);
100 | border-radius: 5px;
101 | cursor: pointer;
102 | font-size: 0.75em;
103 | padding: 0.5em 0.65em;
104 | }
105 |
106 | .btnDelete {
107 | background-color: lightcoral !important;
108 | }
109 |
110 | button+button {
111 | margin-left: 0.5em;
112 | }
113 |
114 | .container button+button {
115 | background: #768da3;
116 | }
117 |
118 | .container li button+button {
119 | background: #508abc;
120 | }
121 |
122 | .upButton {
123 | margin-left: auto;
124 | background: #52bab3;
125 | }
126 |
127 | .container li button:last-child {
128 | background: #768da3;
129 | }
130 |
131 | .strike {
132 | text-decoration: line-through;
133 | color: gray;
134 | }
135 |
136 | /*** */
137 | .todos {
138 | user-select: none;
139 | }
140 |
141 | .todo-actions {
142 | display: inline-block;
143 | padding-left: 40px;
144 | }
145 |
146 | .todo-item {
147 | padding: 5px;
148 | margin-top: 5px;
149 | }
150 |
151 | .todo-item-completed {
152 | text-decoration: line-through;
153 | background-color: darkgreen;
154 | color: white;
155 | }
--------------------------------------------------------------------------------
/style/webfonts/fa-brands-400.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-brands-400.eot
--------------------------------------------------------------------------------
/style/webfonts/fa-brands-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-brands-400.ttf
--------------------------------------------------------------------------------
/style/webfonts/fa-brands-400.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-brands-400.woff
--------------------------------------------------------------------------------
/style/webfonts/fa-brands-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-brands-400.woff2
--------------------------------------------------------------------------------
/style/webfonts/fa-regular-400.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-regular-400.eot
--------------------------------------------------------------------------------
/style/webfonts/fa-regular-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-regular-400.ttf
--------------------------------------------------------------------------------
/style/webfonts/fa-regular-400.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-regular-400.woff
--------------------------------------------------------------------------------
/style/webfonts/fa-regular-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-regular-400.woff2
--------------------------------------------------------------------------------
/style/webfonts/fa-solid-900.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-solid-900.eot
--------------------------------------------------------------------------------
/style/webfonts/fa-solid-900.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-solid-900.ttf
--------------------------------------------------------------------------------
/style/webfonts/fa-solid-900.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-solid-900.woff
--------------------------------------------------------------------------------
/style/webfonts/fa-solid-900.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajeshpillai/udemy-student-create-your-own-react-fasttrack/f07078fe5caa0cad8458318f6afb47e2ab9f6e50/style/webfonts/fa-solid-900.woff2
--------------------------------------------------------------------------------
/todo-app.js:
--------------------------------------------------------------------------------
1 | /** @jsx TinyReact.createElement */
2 |
3 | /*** Step 1,2,3,4 - createElement */
4 |
5 | const root = document.getElementById("root");
6 |
7 | var Step1 = (
8 |
9 |
Hello Tiny React!
10 |
(coding nirvana)
11 |
12 |
(OBSERVE: This will change)
13 | {2 == 1 &&
Render this if 2==1
}
14 | {2 == 2 &&
{2}
}
15 |
This is a text
16 |
17 |
This will be deleted
18 | 2,3
19 |
20 | );
21 |
22 | console.log(Step1);
23 |
24 | // Step 5, 6
25 | //TinyReact.render(Step1, root);
26 |
27 | var Step2 = (
28 |
29 |
Hello Tiny React!
30 |
(coding nirvana)
31 |
32 |
(OBSERVE: I said it!!)
33 | {2 == 1 &&
Render this if 2==1
}
34 | {2 == 2 &&
{2}
}
35 |
Something goes here...
36 |
37 |
38 | );
39 |
40 | // setTimeout(() => {
41 | // alert("Re-rendering...");
42 | // TinyReact.render(Step2, root);
43 | // }, 4000);
44 |
45 | // Functional Component
46 | const Heart = (props) => ♥;
47 |
48 | // function Heart() {
49 | // return (
50 | // ♥
51 | // );
52 | // }
53 |
54 |
55 | //TinyReact.render(, root);
56 |
57 | const Button = (props) => ;
58 |
59 |
60 | // Testing functional components, props, nested components
61 |
62 | const Greeting = function (props) {
63 | return (
64 |
65 |
Welcome {props.message}
66 |
67 |
68 | );
69 | }
70 |
71 | //TinyReact.render(, root);
72 |
73 | // setTimeout(() => {
74 | // alert("Re-rendering...");
75 | // TinyReact.render(, root);
76 | // }, 4000);
77 |
78 | // Stateful component
79 | class Alert extends TinyReact.Component {
80 | constructor(props) {
81 | super(props);
82 | this.state = {
83 | title: "Default title"
84 | };
85 |
86 | this.changeTitle = this.changeTitle.bind(this); // Binding the context to instance of Alert
87 | }
88 |
89 | changeTitle() {
90 | this.setState({ title: new Date().toString() });
91 | }
92 |
93 | render() {
94 | return (
95 |
96 |
{this.state.title}
97 |
98 | {this.props.message}
99 |
100 |
101 |
102 | );
103 | }
104 | }
105 |
106 | //TinyReact.render(, root);
107 |
108 | // Diffing / Reconciliation of two stateful components
109 |
110 | class Stateful extends TinyReact.Component {
111 | constructor(props) {
112 | super(props);
113 | console.log(props);
114 | }
115 | render() {
116 | return (
117 |
118 |
{this.props.title.toString()}
119 |
120 |
121 | );
122 | }
123 | }
124 |
125 | //TinyReact.render(, root);
126 |
127 |
128 | function update() {
129 | TinyReact.render(, root);
130 | }
131 |
132 |
133 | class WishList extends TinyReact.Component {
134 | constructor(props) {
135 | super(props);
136 | this.state = {
137 | wish: {
138 | title: "I wan't to be a programmer"
139 | }
140 | }
141 | this.update = this.update.bind(this);
142 | }
143 |
144 | update() {
145 | let newValue = this.inputWish.value; // here inputWish is the input DOM element
146 | let wish = Object.assign({}, this.state.wish);
147 | // this.state.width = newValue; // BAD PRACTICE as we are mutating the state
148 |
149 | wish.title = newValue;
150 | this.setState({
151 | wish
152 | });
153 | }
154 |
155 | render() {
156 | return (
157 |
158 |
Your wish list
159 |
{ this.inputWish = inputWish }} placeholder="What's your wish?">
160 |
161 |
162 |
163 | {this.state.wish.title}
164 |
165 |
166 | );
167 | }
168 |
169 | }
170 |
171 | //TinyReact.render(, root);
172 |
173 | let newElement = (
174 |
175 |
One
176 |
Two
177 |
178 | );
179 |
180 | //TinyReact.render(, root);
181 |
182 | // TinyReact.render(newElement, root);
183 |
184 | // setTimeout(() => {
185 | // alert("Rerendering");
186 | // TinyReact.render(, root);
187 | // //TinyReact.render(newElement, root);
188 | // }, 4000);
189 |
190 |
191 | //////******* TODO APP ***********/
192 | let Header = props => {
193 | return (
194 |
195 |
{props.text}
196 | (double click on todo to mark as completed)
197 |
198 | );
199 | };
200 |
201 |
202 | class TodoItem extends TinyReact.Component {
203 | constructor(props) {
204 | super(props);
205 | this.logging = true;
206 | }
207 |
208 | log(...args) {
209 | if (this.logging) {
210 | for (let i = 0; i < args.length; i++) {
211 | console.log(args[i]);
212 | }
213 | }
214 | }
215 | componentDidMount() {
216 | this.log("2. TodoItem:cdm");
217 | }
218 | componentWillMount() {
219 | this.log("1. TodoItem:cwm");
220 | }
221 |
222 | // VERY IMPORTANT
223 | shouldComponentUpdate(nextProps, nextState) {
224 | let result = nextProps.task != this.props.task;
225 | return result;
226 | }
227 |
228 | componentWillReceiveProps(nextProps) {
229 | this.log("TodoItem:cwrp: ", JSON.stringify(nextProps));
230 | }
231 | componentWillUnmount() {
232 | this.log("TodoItem:cwu: " + this.props.task.title);
233 | }
234 |
235 | handleEdit = task => {
236 | this.props.onUpdateTask(task.id, this.textInput.value);
237 | };
238 |
239 | editView = props => {
240 | if (props.task.edit) {
241 | return (
242 |
243 | (this.textInput = input)}
248 | />
249 |
255 |
256 | );
257 | }
258 | return props.task.title;
259 | };
260 |
261 | render() {
262 | let className = "todo-item ";
263 | if (this.props.task.completed) {
264 | className += "strike";
265 | }
266 |
267 | let todoItemStyle = {
268 | borderBottom: "1px dashed gray",
269 | color: "red"
270 | };
271 |
272 | return (
273 | this.props.onToggleComplete(this.props.task)}
278 | >
279 | {this.editView(this.props)}
280 |
281 |
287 |
294 |
295 |
296 | );
297 | }
298 | }
299 |
300 | class TodoApp extends TinyReact.Component {
301 | constructor(props) {
302 | super(props);
303 | this.addTodo = this.addTodo.bind(this);
304 | this.deleteTodo = this.deleteTodo.bind(this);
305 | this.onToggleEdit = this.onToggleEdit.bind(this);
306 | this.onUpdateTask = this.onUpdateTask.bind(this);
307 | this.onToggleComplete = this.onToggleComplete.bind(this);
308 | this.onKeyDown = this.onKeyDown.bind(this);
309 |
310 | this.state = {
311 | tasks: [{ id: 1, title: "Task 1", edit: false }],
312 | sortOrder: "asc"
313 | };
314 | }
315 |
316 | onKeyDown(e) {
317 | if (e.which === 13) {
318 | this.addTodo();
319 | }
320 | }
321 | deleteTodo(task) {
322 | var tasks = this.state.tasks.filter(t => {
323 | return t.id != task.id;
324 | });
325 |
326 | this.setState({
327 | header: "# Todos: " + tasks.length,
328 | tasks
329 | });
330 | }
331 |
332 | addTodo() {
333 | if (this.newTodo.value.trim() == "") {
334 | alert("You don't wanna do anything !");
335 | return;
336 | }
337 | let newTodo = {
338 | id: +new Date(),
339 | title: this.newTodo.value,
340 | edit: false
341 | };
342 | this.setState({
343 | tasks: [...this.state.tasks, newTodo]
344 | });
345 |
346 | this.newTodo.value = "";
347 | this.newTodo.focus();
348 | }
349 |
350 | sortToDo = () => {
351 | let tasks = null;
352 | let sortOrder = this.state.sortOrder;
353 | if (!sortOrder) {
354 | tasks = this.state.tasks.sort(
355 | (a, b) => +(a.title > b.title) || -(a.title < b.title)
356 | );
357 | sortOrder = "asc";
358 | } else if (sortOrder === "asc") {
359 | sortOrder = "desc";
360 | tasks = this.state.tasks.sort(
361 | (a, b) => +(b.title > a.title) || -(b.title < a.title)
362 | );
363 | } else {
364 | sortOrder = "asc";
365 | tasks = this.state.tasks.sort(
366 | (a, b) => +(a.title > b.title) || -(a.title < b.title)
367 | );
368 | }
369 | this.setState({
370 | tasks,
371 | sortOrder
372 | });
373 | };
374 |
375 | onUpdateTask(taskId, newTitle) {
376 | var tasks = this.state.tasks.map(t => {
377 | return t.id !== taskId ?
378 | t :
379 | Object.assign({}, t, { title: newTitle, edit: !t.edit });
380 | });
381 |
382 | this.setState({
383 | tasks
384 | });
385 | }
386 |
387 | // Uses setstate with fn argument
388 | onToggleEdit(task) {
389 | let tasks = this.state.tasks.map(t => {
390 | return t.id !== task.id ?
391 | t :
392 | Object.assign({}, t, { edit: !t.edit });
393 | });
394 |
395 | // DONT MUTATE STATE DIRECTLY
396 | // let tasks = this.state.tasks.map(t => {
397 | // if (t.id === task.id) {
398 | // t.edit = !t.edit;
399 | // }
400 | // return t;
401 | // });
402 |
403 | this.setState({
404 | tasks
405 | });
406 | }
407 |
408 | onToggleComplete(task) {
409 | let tasks = this.state.tasks.map(t => {
410 | return t.id !== task.id ?
411 | t :
412 | Object.assign({}, t, { completed: !t.completed });
413 | });
414 |
415 | this.setState({
416 | tasks
417 | });
418 | }
419 |
420 | render() {
421 | let tasksUI = this.state.tasks.map((task, index) => {
422 | return (
423 |
432 | );
433 | });
434 |
435 | let sortIcon = ;
436 | if (this.state.sortOrder === "asc") {
437 | sortIcon = ;
438 | } else {
439 | sortIcon = ;
440 | }
441 |
442 | return (
443 |
444 |
445 |
446 |
447 | (this.newTodo = newTodo)}
452 | placeholder="what do you want to do today?"
453 | />
454 |
462 |
465 |
466 |
467 |
468 | );
469 | }
470 | }
471 |
472 | TinyReact.render(, root);
--------------------------------------------------------------------------------