Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Creating components w/ es6 vanilla javascript - Reactive UI
// 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