UNPKG

@lemonadejs/timeline

Version:

LemonadeJS timeline component

312 lines (260 loc) 10.4 kB
if (!lemonade && typeof (require) === 'function') { var lemonade = require('lemonadejs'); } if (! utils && typeof (require) === 'function') { var utils = require('@jsuites/utils'); } ; (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.Timeline = factory(); }(this, (function () { let dateSignature = null; // Dispatcher const Dispatch = function (type, ...args) { if (typeof this[type] === 'function') { this[type](this, ...args); } } const extractFromHtml = function(element) { let data = []; // Content for (let i = 0; i < element.children.length; i++) { let e = element.children[i]; let item = { title: e.textContent || e.getAttribute('title') || '', date: e.getAttribute('data-date'), borderColor: e.getAttribute('data-color') || '', borderStyle: e.getAttribute('data-style') || '', } data.push(item); } return data; } const extract = function(children) { let data = []; if (this.tagName) { data = extractFromHtml(this); // Remove all elements this.textContent = ''; if (!Array.isArray(this.data)) { this.data = []; } if (data && data.length) { // Compatibility legacy jsuites data.forEach((v) => { this.data.push(data); }); } } } const Timeline = function (children, { onload, onchange, track }) { let self = this; extract.call(this, children); const toDate = function(d) { return d instanceof Date ? d : new Date(d); } let date = self.value ? toDate(self.value) : new Date(); self.result = []; if (!Array.isArray(self.data)) { self.data = []; } if (! self.format) { if (self.type === 'monthly') { self.format = 'dd mmm yyyy'; } else { self.format = 'dddd, dd'; } } self.year = date.getFullYear(); self.month = 1 + date.getMonth(); self.months = utils.Helpers.months; // Error message if (! self.message) { self.message = 'No records found'; } if (! self.order) { self.order = 'asc'; } // Make sure to align has a default if (!['top', 'right', 'bottom', 'left'].includes(self.align)) { self.align = 'left'; } if (typeof self.controls === 'undefined') { self.controls = self.type === 'monthly'; } self.next = function () { if (self.month === 12) { self.year++; self.month = 1; } else { self.month++; } } self.prev = function () { if (self.month === 1) { self.year--; self.month = 12; } else { self.month--; } } const isRemote = function() { return self.remote && self.url && self.type === 'monthly'; } const getDate = function(d) { let date = d && d === dateSignature ? '' : d; dateSignature = d; return date; } const updateResult = function () { let result = []; if (self.type === 'monthly') { for (let i = 0; i < self.data.length; i++) { let d = toDate(self.data[i].date); if ((d.getMonth() + 1) === self.month && d.getFullYear() === self.year) { result.push(self.data[i]); } } } else { result = self.data; } result = result.sort((a, b) => (self.order === 'desc' ? -1 : 1) * (toDate(a.date).getTime() - toDate(b.date).getTime())); for (let i = 0; i < result.length; i++) { result[i].day = utils.Mask.render(result[i].date, self.format); } self.result = result; // Reset the date signature to avoid interference in the next rendering dateSignature = null; // Event Dispatch.call(self, 'onupdate', result); } const fetchRemote = function () { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { let res = JSON.parse(xhr.responseText); let result = []; if (Array.isArray(res.result)) { result = res.result; } else if (Array.isArray(res)) { result = res } for (let i = 0; i < result.length; i++) { result[i].date = new Date(result[i].date); result[i].day = getDate(result[i].date.toLocaleDateString('en-GB', { year: 'numeric', month: 'short', day: '2-digit' })); } if (isRemote()) { self.result = result; } else { self.data = result; } } else { console.error('Failed to fetch data. Status code: ' + xhr.status); } } }; let url = self.url; if (isRemote()) { url += `?year=${self.year}&month=${self.month}`; url += `&asc=${self.order === 'asc'}` } xhr.open('GET', url, true); xhr.setRequestHeader('Content-Type', 'text/json') xhr.send(); } const updateBorders = function() { self.result.forEach(entry => { if (entry.borderColor) { entry.el.style.setProperty('--lm-border-color', entry.borderColor); } if (entry.borderStyle) { entry.el.style.setProperty('--lm-border-style', entry.borderStyle); } if (Array.isArray(entry.tags) && entry.tags.length) { const tagEls = entry.el.querySelectorAll('.lm-timeline-tag'); entry.tags.forEach((tag, i) => { if (tagEls[i] && tag.color) { tagEls[i].style.backgroundColor = tag.color; } }); } }); } const edition = function(e, s) { if (typeof self.onedition === 'function') { self.onedition(s); } } const tagClick = function(e, s) { if (s.onclick) { s.onclick(e, s); } } onchange((prop) => { if (prop === 'value') { date = toDate(self.value); self.year = date.getFullYear(); self.month = 1 + date.getMonth(); } else if (prop === 'data' || prop === 'month' || prop === 'order') { if (isRemote()) { fetchRemote(); } else { updateResult(); } } else if (prop === 'result') { updateBorders(); } }) onload(() => { if (self.url) { fetchRemote(); } else { updateResult(); } if (typeof(self.width) !== 'undefined') { self.el.style.width = parseInt(self.width) + 'px'; } if (typeof(self.height) !== 'undefined') { self.el.style.height = parseInt(self.height) + 'px'; } }); track('data'); track('order'); return render => render`<div class="lm-timeline"> <div class="lm-timeline-header" data-visible="{{self.controls}}" data-type="{{self.type}}"> <div class="lm-timeline-label"> <div class="lm-timeline-year">${self.year}</div> <div class="lm-timeline-month">${self.months[self.month - 1]}</div> </div> <div class="lm-timeline-navigation"> <button type="button" class="lm-timeline-icon lm-ripple" onclick="${self.prev}" tabindex="0">expand_less</button> <button type="button" class="lm-timeline-icon lm-ripple" onclick="${self.next}" tabindex="0">expand_more</button> </div> </div> <div :loop="self.result" data-message="{{self.message}}" data-mode="{{self.position}}" data-align="{{self.align}}" class="lm-timeline-data"> <div class="lm-timeline-item" data-bullet="{{self.day}}"> <div class="lm-timeline-edit" :render="self.editable"><button type="button" class="lm-timeline-icon lm-ripple lm-cursor" onclick="${edition}" tabindex="0">edit</button></div> <div class="lm-timeline-title">{{self.title}}</div> <div class="lm-timeline-subtitle">{{self.subtitle}}</div> <div class="lm-timeline-description">{{self.description}}</div> <div class="lm-timeline-tags" :loop="self.tags"> <span class="lm-timeline-tag" onclick="${tagClick}">{{self.title}}</span> </div> </div> </div> </div>`; } lemonade.setComponents({ Timeline: Timeline }); // Register the web component lemonade.createWebComponent('timeline', Timeline); return function (root, options) { if (typeof (root) === 'object') { lemonade.render(Timeline, root, options); return options; } else { return Timeline.call(this, root); } } })));