Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link
Owner Author

@jfbrennan 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

This comment has been minimized.

Copy link
Owner Author

@jfbrennan 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