Add alert class

This commit is contained in:
Bartek Fabiszewski 2020-05-12 17:07:03 +02:00
parent e57977f94b
commit a0ef7f3854
4 changed files with 312 additions and 16 deletions

View File

@ -526,6 +526,43 @@ button > * {
margin: 0 5px; margin: 0 5px;
} }
.alert {
position: fixed;
top: 0;
left: 50%;
width: 300px;
background: #666;
color: white;
font-family: "Open Sans", Verdana, sans-serif;
font-size: 0.8em;
line-height: 20px;
text-align: center;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
margin: 1em 0 1em -150px;
padding: 6px 20px;
border-radius: 5px;
border-top: 1px solid #555;
box-shadow: 10px 10px 10px -8px rgba(0, 0, 0, 0.3);
}
.alert.error {
background: #d95b5b;
border-top: 1px solid #d05858;
}
.alert button {
position: absolute;
top: -1px;
right: 0;
border: none;
margin: 0;
height: 100%;
background: none;
font-weight: normal;
font-size: 15px;
}
/* chart */ /* chart */
.ct-point { .ct-point {
transition: 0.3s; transition: 0.3s;

132
js/src/alert.js Normal file
View File

@ -0,0 +1,132 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import uUtils from './utils.js';
export default class uAlert {
/**
* @typedef {Object} AlertOptions
* @property {number} [autoClose=0] Optional autoclose delay time in ms, default 0 no autoclose
* @property {string} [id] Optional box id
* @property {string} [class] Optional box class
*/
/**
* Builds alert box
* @param {string} message
* @param {AlertOptions} [options] Optional options
*/
constructor(message, options = {}) {
this.autoClose = options.autoClose || 0;
const html = `<div class="alert"><span>${message}</span></div>`;
this.box = uUtils.nodeFromHtml(html);
if (options.id) {
this.box.id = options.id;
}
if (options.class) {
this.box.classList.add(options.class);
}
if (this.autoClose === 0) {
const button = document.createElement('button');
button.setAttribute('type', 'button');
button.textContent = '×';
button.onclick = () => this.destroy();
this.box.appendChild(button);
}
this.closeHandle = null;
}
/**
* Calculate new box top offset
* @return {number} Top offset
*/
static getPosition() {
const boxes = document.querySelectorAll('.alert');
const lastBox = boxes[boxes.length - 1];
let position = 0;
if (lastBox) {
const maxPosition = document.body.clientHeight - 100;
position = lastBox.getBoundingClientRect().bottom;
if (position > maxPosition) {
position = maxPosition;
}
}
return position;
}
render() {
const top = uAlert.getPosition();
if (top) {
this.box.style.top = `${top}px`;
}
document.body.appendChild(this.box);
}
destroy() {
if (this.closeHandle) {
clearTimeout(this.closeHandle);
this.closeHandle = null;
}
if (this.box) {
if (document.body.contains(this.box)) {
document.body.removeChild(this.box);
}
this.box = null;
}
}
/**
* Show alert box
* @param {string} message
* @param {AlertOptions} [options] Optional options
* @return uAlert
*/
static show(message, options) {
const box = new uAlert(message, options);
box.render();
if (box.autoClose) {
box.closeHandle = setTimeout(() => box.destroy(), box.autoClose);
}
return box;
}
/**
* Show alert error box
* @param {string} message
* @param {Error=} e Optional error to be logged to console
* @return uAlert
*/
static error(message, e) {
if (e instanceof Error) {
console.error(`${e.name}: ${e.message} (${e.stack})`);
}
return this.show(message, { class: 'error' });
}
/**
* Show alert toast box
* @param {string} message
* @return uAlert
*/
static toast(message) {
return this.show(message, { class: 'toast', autoClose: 10000 });
}
}

View File

@ -280,22 +280,6 @@ export default class uUtils {
window.location.assign(url); window.location.assign(url);
} }
/**
* @param {(Error|string)} e
* @param {string=} message
*/
static error(e, message) {
let details;
if (e instanceof Error) {
details = `${e.name}: ${e.message} (${e.stack})`;
} else {
details = e;
message = e;
}
console.error(details);
alert(message);
}
/** /**
* Degrees to radians * Degrees to radians
* @param {number} degrees * @param {number} degrees

143
js/test/alert.test.js Normal file
View File

@ -0,0 +1,143 @@
/*
* μlogger
*
* Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import uAlert from '../src/alert.js';
describe('Alert tests', () => {
const message = 'test message';
let alert;
afterEach(() => {
if (alert) {
alert.destroy();
}
document.body.innerText = '';
});
it('should create alert box with message', () => {
// when
alert = new uAlert(message);
const textEl = alert.box.firstChild;
// then
expect(textEl.innerText).toBe(message);
});
it('should create alert box with autoClose option', () => {
// given
const autoClose = 1;
const options = { autoClose }
// when
alert = new uAlert(message, options);
const textEl = alert.box.firstChild;
// then
expect(textEl.innerText).toBe(message);
expect(alert.autoClose).toBe(autoClose);
});
it('should create alert box with id option', () => {
// given
const id = 'testId';
const options = { id }
// when
alert = new uAlert(message, options);
const boxEl = alert.box;
const textEl = alert.box.firstChild;
// then
expect(textEl.innerText).toBe(message);
expect(boxEl.id).toBe(id);
});
it('should create alert box with class option', () => {
// given
const className = 'test_class';
const options = { class: className }
// when
alert = new uAlert(message, options);
const boxEl = alert.box;
const textEl = alert.box.firstChild;
// then
expect(textEl.innerText).toBe(message);
expect(boxEl.classList).toContain(className);
});
it('should render and destroy alert box', () => {
// given
const id = 'testId';
const options = { id }
alert = new uAlert(message, options);
// when
alert.render();
// then
expect(document.querySelector(`#${id}`)).not.toBeNull();
// when
alert.destroy();
// then
expect(document.querySelector(`#${id}`)).toBeNull();
});
it('should show and autoclose alert box', (done) => {
// given
const id = 'testId';
const options = { id: id, autoClose: 50 }
// when
alert = uAlert.show(message, options);
// then
expect(document.querySelector(`#${id}`)).not.toBeNull();
setTimeout(() => {
expect(document.querySelector(`#${id}`)).toBeNull();
done();
}, 100);
});
it('should close alert box on close button click', (done) => {
// given
const id = 'testId';
const options = { id }
alert = uAlert.show(message, options);
const closeButton = alert.box.querySelector('button');
// when
closeButton.click();
// then
setTimeout(() => {
expect(document.querySelector(`#${id}`)).toBeNull();
done();
}, 100);
});
it('should show error alert box', () => {
// when
alert = uAlert.error(message);
// then
expect(document.querySelector('.alert.error')).not.toBeNull();
});
it('should show toast alert box', () => {
// when
alert = uAlert.toast(message);
// then
expect(document.querySelector('.alert.toast')).not.toBeNull();
expect(alert.autoClose).toBeGreaterThan(0);
});
});