Skip to content

Instantly share code, notes, and snippets.

@jfbrennan
Last active January 25, 2018 19:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jfbrennan/8fd5c0272660cad4e2d23e93863983ea to your computer and use it in GitHub Desktop.
Save jfbrennan/8fd5c0272660cad4e2d23e93863983ea to your computer and use it in GitHub Desktop.
Alert UI component rebuilt to compare Riot vs Slim vs Polymer vs Vue vs React and more
<dom-module id="poly-alert">
<template>
<div>
<i id="icon"></i>
<div>
<h6 dom-if="title" class="alert-title sub-header">{{title}}</h6>
<p>{{msg}}</p>
</div>
<button dom-if="!preventClose" id="dismissBtn" class="btn-close alert-remove"><span class="icon icon-close"></span></button>
</div>
</template>
<script>
class PolyAlert extends Polymer.Element {
static get is() { return 'poly-alert'; }
static get properties() {
return {
type: String,
title: String,
msg: String,
autodismiss: Object,
preventClose: {
type: Boolean,
value: false
}
};
}
constructor() {
super();
}
ready() {
super.ready();
// Create class lists
this.shadowRoot.firstElementChild.classList.add('alert');
this.shadowRoot.firstElementChild.classList.add('alert-' + this.type);
this.$.icon.classList.add('icon');
this.$.icon.classList.add('icon-' + (this.type === 'success' ? 'check' : this.type === 'error' ? 'info' : 'warn') + 'alt');
// on-click approach means the callback's context is the target not this element
this.$.dismissBtn.addEventListener('click', this.dismiss.bind(this));
if (this.autodismiss) {
var seconds = (typeof this.autodismiss === 'number' ? this.autodismiss : 4) * 1000;
setTimeout(this.close.bind(this), seconds);
}
}
// Removes Alert after CSS transition finishes. Publishes UITK event.
dismiss(e) {
var element = this.shadowRoot.firstElementChild; // firstElementChild is hacky, no find('.alert')???
this.addEventListener('transitionend', function(e) {
this.remove();
uitk.publish('alert.remove', this);
}.bind(this), {once: true});
element.classList.add('remove-animated'); // IE11 can't take multiple args :(
element.classList.add('animated-fade'); // IE11 can't take multiple args :(
}
}
customElements.define(PolyAlert.is, PolyAlert);
</script>
<style>
// Shadow DOM! Will have to redo all the existing Alert styles and any helpers like animation classes
// Would be ~100 more loc
</style>
</dom-module>
import React, {Component} from 'react'
import PropTypes from 'prop-types'
class ReactAlert extends Component {
static defaultProps = {
icon: null,
msg: '',
type: 'info',
autodismiss: 0,
onRemoveAlert: () => {}
};
static propTypes = {
icon: PropTypes.element,
msg: PropTypes.PropTypes.string,
type: PropTypes.oneOf(['info', 'success', 'error', 'warn']),
autodismiss: PropTypes.oneOfType([
PropTypes.number,
PropTypes.boolean
]),
onRemoveAlert: PropTypes.func
};
_removeItself = () => {
const {onRemoveAlert, id} = this.props;
onRemoveAlert(id)
};
componentDidMount () {
const {autodismiss} = this.props;
if (autodismiss) {
let seconds = (typeof autodismiss === 'number' ? autodismiss : 4) * 1000;
setTimeout(() => { this._removeItself() }, seconds)
}
}
render () {
const {msg, icon, type} = this.props;
return (
<div>
<div className={alert alert-{type}>
<i className={icon icon-{icon}></i>
<div>
<h6 className="alert-title sub-header">{title}</h6>
<p>{msg}</p>
</div>
<button onClick={this._removeItself} className="btn-close alert-remove"></button>
</div>
</div>
)
}
}
export default ReactAlert
<riot-alert class="alert alert-{opts.type} {remove-animated: removing, animated-fade: removing}">
<i class="icon icon-{icon}alt"></i>
<div>
<h6 if="{opts.title}" class="alert-title sub-header">{opts.title}</h6>
<p>{opts.msg}</p>
</div>
<button if="{!opts.preventclose}" onclick="{dismiss}" class="btn-close alert-remove"><span class="icon icon-close"></span></button>
<script>
// Determine Icon based on type
this.icon = this.opts.type === 'success' ? 'check' : this.opts.type === 'error' ? 'info' : 'warn';
// Removes Alert after CSS transition finishes. Publishes UITK event.
dismiss(e) {
this.root.addEventListener('transitionend', function(e) {
this.unmount();
uitk.publish('alert.remove');
}.bind(this), {once: true});
this.removing = true; // Transition event callback will do the unmount
}
this.on('mount', function() {
if (this.opts.autodismiss) {
var seconds = (typeof this.opts.autodismiss === 'number' ? this.opts.autodismiss : 4) * 1000;
setTimeout(this.remove.bind(this), seconds);
}
})
</script>
</riot-alert>
Slim.tag('slim-alert',
`
<div bind:class="alertClass">
<i bind:class="iconClass"></i>
<div>
<h6 s:if="title" class="alert-title sub-header" bind>{{title}}</h6>
<p bind>{{msg}}</p>
</div>
<button s:if="!preventClose" click="dismiss" class="btn-close alert-remove"><span class="icon icon-close"></span></button>
</div>
`,
class SlimAlert extends Slim {
onBeforeCreated() {
this.type = this.getAttribute('type');
this.msg = this.getAttribute('msg') || '';
this.preventClose = this.hasAttribute('preventclose');
this.autodismiss = this.getAttribute('dismiss');
// Create class lists
this.alertClass = 'alert alert-' + this.type;
this.iconClass = 'icon icon-' + (this.type === 'success' ? 'check' : this.type === 'error' ? 'info' : 'warn') + 'alt';
// Removes SlimAlert after CSS transition finishes. Publishes UITK event.
this.dismiss = function() {
this.addEventListener('transitionend', (e) => {
this.remove();
uitk.publish('alert.remove');
}, {once: true});
this.find('.alert').classList.add('remove-animated'); // IE11 can't take multiple args :(
this.find('.alert').classList.add('animated-fade'); // IE11 can't take multiple args :(
}
}
onCreated() {
if (this.autodismiss) {
var seconds = (typeof this.autodismiss === 'number' ? this.autodismiss : 4) * 1000; // The given secs or default to 4 secs
setTimeout(this.unmount.bind(this), seconds);
}
}
});
Vue.component('vue-alert', {template:
`<div v-bind:class="'alert alert-' + type">
<i v-bind:class="'icon icon-' + icon"></i>
<div>
<h6 v-if="title" class="alert-title sub-header">{{title}}</h6>
<p>{{msg}}</p>
</div>
<button v-if="preventClose" v-on:click="dismiss" class="btn-close alert-remove"><span class="icon icon-close"></span></button>
</div>
`,
props: ['type', 'title', 'msg', 'autodismiss', 'preventClose'],
data: function () {
return {
counter: 0
}
},
computed: {
icon: function () {
return this.type === 'success' ? 'check' : this.type === 'error' ? 'info' : 'warn'
}
},
methods: {
dismiss: function() {
this.root.addEventListener('transitionend', function(e) {
this.unmount();
uitk.publish('alert.remove');
}.bind(this), {once: true});
this.removing = true; // Transition event callback will do the unmount
}
}
});
@jfbrennan
Copy link
Author

jfbrennan commented Nov 17, 2017

Trying all the View libs. Who's the easiest?

So far Riot (29 loc of src, just 1 line to use). Slim and Vue are not far behind. React and Polymer are up to double the loc.

Interesting to note how Riot, Slim, Polymer usage is identical:

<body>
  <h1>Trying Riot, Slim, and Polymer. They respect web standards (i.e. Custom Elements)</h1>
  <riot-alert type="error" msg="Failed to fetch data. Please try again." preventclose></riot-alert>
  <slim-alert type="error" msg="Failed to fetch data. Please try again." preventclose></slim-alert>
  <poly-alert type="error" msg="Failed to fetch data. Please try again." preventclose></poly-alert>
</body>

Vue is similar, but requires ugly "root" elements:

<body>
  <h1>Trying Vue. Like Custom Elements, but needs an extra root element</h1>
  <div id="a-vue-root">
    <vue-alert type="error" msg="Failed to fetch data. Please try again." v-bind:preventclose="true"></vue-alert>
  </div>
</body>

React is not like Custom Elements and also requires container elements like Vue:

<body>
  <h1>Trying React. Not like custom elements; too much magic</h1>
  <div id="a-react-root"></div>

  <script type="text/babel">
    ReactDOM.render(
      <ReactAlert,  {type: 'error', msg: 'Failed to fetch trips. Please try again.', preventClose: true}/>, 
      document.getElementById('a-react-root')
    );
  </script>
</body>

Riot is also the smallest of the libs I've tried so far. It's 10kb min+gz.

@jfbrennan
Copy link
Author

jfbrennan commented Jan 9, 2018

The above components implement the same requirements. These are:

  • Block-level element with a colored border and white background (existing stylesheet was able to be reused except in shadow DOM cases)
  • Required type (one of info, success, warn, or error; determines colors)
  • Optional title
  • Required message
  • Optional autodismiss (pass true for the default duration, or pass Number of seconds for custom duration)
  • Dismiss button when clicked removes Alert and 'alert.dismissed' is published
  • Optional disabled (set it to hide the dismiss button)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment