Last active
October 5, 2022 22:23
-
-
Save iamwill123/1b1222282e2d99a29e14e5fd35a51454 to your computer and use it in GitHub Desktop.
Creating components w/ es6 vanilla javascript - Reactive UI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is my es6 re-write of the fiddle below. | |
// http://jsfiddle.net/cferdinandi/nb40j6rf/6/?mc_cid=1d481e891a&mc_eid=a3f6fd745a | |
// still a working progress but here is a live example: | |
// https://repl.it/@iamwill123/Creating-components-with-es6-vanilla-javascript-Reactive-UI | |
/** | |
* A vanilla JS helper for creating state-based components | |
* @param {String|Node} elem The element to make into a component | |
* @param {Object} options The component options | |
*/ | |
const Component = ((win, doc, log, si, ci, sto, loc, undefined) => { | |
'use strict'; | |
/** | |
* Create the Component object | |
* @param {String|Node} elem The element to make into a component | |
* @param {Object} options The component options | |
*/ | |
doc.componentRegistry = {}; | |
doc.nextId = 0; | |
class Component { | |
constructor(props) { | |
if (!props.elem) throw 'Component: You did not provide an element to make into a component.'; | |
this._id = ++doc.nextId; | |
doc.componentRegistry[this._id] = this; | |
this.state = { | |
elem: props.elem, | |
data: props.data || null, | |
// template: props.template || null | |
} | |
} | |
// Add the `setState()` method | |
setState(props) { | |
// Shallow merge new properties into state object | |
for (var key in props) { | |
if (props.hasOwnProperty(key)) { | |
this.state.data[key] = props[key]; | |
} | |
} | |
// re-render | |
this.render(); | |
} | |
/** | |
* Sanitize and encode all HTML in a user-submitted string | |
* @param {String} str The user-submitted string | |
* @return {String} The sanitized string | |
*/ | |
sanitize(str) { | |
let temp = doc.createElement('div'); | |
temp.textContent = str; | |
return temp.innerHTML; | |
}; | |
template(props) { | |
let template = ` | |
<div>Hello</div> | |
`; | |
return template ? template : null; | |
} | |
/** | |
* Render a template into the DOM | |
* @return {[type]} The element | |
*/ | |
render() { | |
const { elem, data } = this.state; | |
// Make sure there's a template | |
if (!this.template) throw 'ComponentJS: No template was provided.'; | |
// If elem is an element, use it. | |
// If it's a selector, get it. | |
let _elem = typeof elem === 'string' | |
? doc.querySelector(elem) | |
: elem; | |
if (!elem) return; | |
// Get the template, data will be passed as props to the template. | |
let _template = typeof this.template === 'function' | |
? this.template(data) | |
: this.template; | |
// array indexOf === -1 true if index value is not found. | |
if (['string', 'number'].indexOf(typeof _template) === -1) return; | |
// Render the template into the element | |
if (_elem.innerHTML === _template) return; // if they're the same, do nothing | |
_elem.innerHTML = _template; // else update with new template | |
// Dispatch a render event -> https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent | |
if (typeof win.CustomEvent === 'function') { | |
let event = new CustomEvent('render', { | |
bubbles: true | |
}); | |
_elem.dispatchEvent(event); | |
} | |
// Return the _elem for use elsewhere | |
return _elem; | |
} // render | |
}; // class Component | |
return Component; | |
})(window, document, console, setInterval, clearInterval, setTimeout, location); | |
/** | |
* Setup the navbar on page load | |
*/ | |
const setup_navbar = () => { | |
// Create the stopwatch | |
class Navbar extends Component { | |
constructor(props) { | |
super(props); | |
this.state = { ...props }; | |
} | |
template(props) { | |
const { heading } = props; | |
let template = ` | |
<div class="nav"> | |
${heading} | |
</div> | |
`; | |
return template; | |
} | |
}; | |
const INITIAL_STATE = { | |
elem: '#navbar', | |
data: { | |
heading: 'Welcome to my stopwatch bro.' | |
} | |
}; | |
let navbar = new Navbar(INITIAL_STATE); | |
navbar.render(); | |
}; | |
/** | |
* Setup the stopwatch on page load | |
*/ | |
const setup_stopwatch = () => { | |
let timer; | |
// Create the stopwatch | |
class Stopwatch extends Component { | |
constructor(props) { | |
super(props); | |
this.state = { ...props }; | |
this.formatTime = this.formatTime.bind(this); | |
this.start = this.start.bind(this); | |
this.stop = this.stop.bind(this); | |
this.reset = this.reset.bind(this); | |
this.clickHandler = this.clickHandler.bind(this); | |
let app = document.getElementById('app'); | |
app.addEventListener('click', this.clickHandler, false); | |
} | |
/** | |
* Format the time in seconds into hours, minutes, and seconds | |
* @param {Number} time The time in seconds | |
* @return {String} The time in hours, minutes, and seconds | |
*/ | |
formatTime(time) { | |
let minutes = parseInt(time / 60, 10); | |
let hours = parseInt(minutes / 60, 10); | |
if (minutes > 59) { | |
minutes = minutes % 60; | |
} | |
return (hours > 0 ? hours + 'h ' : '') + (minutes > 0 || hours > 0 ? minutes + 'm ' : '') + (time % 60) + 's'; | |
} | |
/** | |
* Start the stopwatch | |
*/ | |
start() { | |
let { data: { time } } = this.state; | |
// Start the timer | |
timer = setInterval(() => { update_timer() }, 1000); | |
// Update the timer | |
let update_timer = () => this.setState({ time: ++time }); // we need access to the current state of time | |
this.setState({ running: true }); | |
} | |
/** | |
* Stop the stopwatch | |
*/ | |
stop() { | |
this.setState({ running: false }); | |
clearInterval(timer); | |
} | |
/** | |
* Reset the stopwatch | |
*/ | |
reset() { | |
this.setState({ time: 0, running: false }); | |
clearInterval(timer); | |
} | |
clickHandler(event) { | |
event.preventDefault(); | |
// Check if a stopwatch action button was clicked | |
let action = event.target.getAttribute('data-stopwatch'); | |
console.warn(`click action: ${action}`); | |
switch(action) { | |
case 'start': // If it's the start button, start | |
this.start(); | |
break; | |
case 'stop': // If it's the stop button, stop | |
this.stop(); | |
break; | |
case 'reset': // If it's the stopwatch button, reset | |
this.reset(); | |
break; | |
default: | |
return; | |
} | |
} | |
template(props) { | |
const { time, running } = props; | |
console.warn(props); | |
let template = ` | |
<div class="timer"> | |
<div id="stopwatch"> | |
${this.formatTime(time)} | |
</div> | |
<p> | |
<button | |
data-stopwatch="${ running ? 'stop' : 'start' }" | |
> | |
${ running ? 'Stop' : 'Start' } | |
</button> | |
<button data-stopwatch="reset"> | |
Reset | |
</button> | |
</p> | |
</div> | |
`; | |
return template; | |
} | |
}; | |
const INITIAL_STATE = { | |
elem: '#app', | |
data: { | |
time: 0, | |
running: false | |
} | |
}; | |
let stopwatch = new Stopwatch(INITIAL_STATE); | |
stopwatch.render(); | |
}; | |
// Setup the app | |
setup_navbar(); | |
setup_stopwatch(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment