@financial-times/o-date
Version:
JavaScript utilities for formatting and updating dates in the FT style. Also useful for formatting dates relative to the current time.
310 lines (268 loc) • 9.98 kB
JavaScript
import ftDateFormat from '@financial-times/ft-date-format';
const updateEventName = 'update';
let interval;
function ftDateFormatWarning(methodName) {
// eslint-disable-next-line no-console
console.warn(`The o-date method "${methodName}" is deprecated. Use the "ft-date-format" package instead or contact the Origami team for help: https://github.com/Financial-Times/ft-date-format`);
}
/**
* Initialise the o-date component.
*
* @param {HTMLElement | string} rootElement - The root element or CSS selector to initialise
*/
class ODate {
constructor(rootEl) {
if (!rootEl) {
// eslint-disable-next-line no-console
console.warn('To initialise all o-date elements on the page use ' +
'the `init` method. Passing no arguments to the constructor ' +
'is deprecated.');
}
if (rootEl && !(rootEl instanceof HTMLElement)) {
// eslint-disable-next-line no-console
console.warn('Using the constructor to initialise one or more ' +
'o-date elements with a query selector is deprecated. ' +
'Pass a single o-date HTMLElement to initialise or use the ' +
'`init` method.');
}
if (!rootEl) {
rootEl = document.body;
} else if (!(rootEl instanceof HTMLElement)) {
rootEl = document.querySelector(rootEl);
}
if (rootEl.getAttribute('data-o-component') === "o-date") {
this.el = rootEl;
} else {
this.el = rootEl.querySelector('[data-o-component~="o-date"]');
}
if (this.el) {
document.body.addEventListener(updateEventName, this);
this.update();
this.el.setAttribute('data-o-date-js', '');
}
if (this.el && !interval) {
interval = setInterval(function () {
document.body.dispatchEvent(new CustomEvent(updateEventName));
}, 60000);
}
}
// Use object-level event listener method so a new function doesn't need to be bound for each instance
handleEvent(e) {
if (e.type === updateEventName) {
this.update();
}
}
/**
* Re-render the formatted date within the `time` element.
*
* @returns {undefined}
*/
update() {
const el = this.el;
// Find the date to render.
// Use the current date if the `datetime` attribute is not set and
// the o-date `time` element has no text content.
const dateTime = el.getAttribute('datetime');
let date = dateTime ? ftDateFormat.toDate(dateTime) : null;
if (!date && this.el.textContent === '') {
date = new Date();
}
if (!date) {
return;
}
// Find elements to render formatted dates to.
// @deprecated - The class `.o-date__printer` is deprecated.
// `.o-date__printer` should be removed in the next major.
// Use `[data-o-date-printer]` instead of `.o-date__printer`.
let printers = el.querySelectorAll('.o-date__printer,[data-o-date-printer]');
printers = printers.length ? printers : [el];
// Render the found date in each printer element.
for (const printer of printers) {
this._renderDateFor(printer, date);
}
// If no printers result in output, e.g. because the,
// format chosen does not output the time after x hours,
// then hide the `time` element.
if (el.textContent) {
el.setAttribute('title', ftDateFormat.format(date, 'datetime'));
el.removeAttribute('aria-hidden');
} else {
el.setAttribute('aria-hidden', true);
}
}
/**
* Remove o-date from the `time` element i.e. remove event
* listeners and drop references to the element.
*
* @returns {undefined}
*/
destroy() {
document.body.removeEventListener(updateEventName, this);
this.el = null;
}
/**
* Initialise the o-date component.
*
* @param {HTMLElement | string} [el] - The root element or CSS selector to initialise
* @returns {Array<ODate> | ODate} - An o-date instance or array of o-date instances.
*/
static init (el) {
if (!el) {
el = document.body;
}
if (!(el instanceof HTMLElement)) {
el = document.querySelector(el);
}
/* If el's data-o-component has \bo-date\b in it, ie it is itself a date,
return a new o-date */
if (/\bo-date\b/.test(el.getAttribute('data-o-component'))) {
return new ODate(el);
}
// If el contains date components, return o-dates
const dateEls = el.querySelectorAll('[data-o-component~="o-date"]');
return [].map.call(dateEls, function (el) {
return new ODate(el);
});
}
/**
* Render the date to the "printer" element in the requested format.
*
* @param {HTMLElement} printer - The element to render the date in
* @param {Date} date - The date to format
* @returns {undefined}
*/
_renderDateFor(printer, date) {
// Use the printer `data-o-date-format` if found or fallback to the
// root element if not found.
const format = printer.getAttribute('data-o-date-format') ||
this.el.getAttribute('data-o-date-format');
const textCase = printer.getAttribute('data-o-date-text-case') ||
this.el.getAttribute('data-o-date-text-case');
let formattedDate;
if (format === 'today-or-yesterday-or-nothing') {
formattedDate = ftDateFormat.asTodayOrYesterdayOrNothing(date);
} else if (format === 'date-only') {
formattedDate = ftDateFormat.format(date, 'date');
} else if (format === 'time-ago-limit-4-hours') {
formattedDate = ftDateFormat.timeAgo(date, { limit: 4 * ftDateFormat.inSeconds.hour });
} else if (format === 'time-ago-limit-24-hours') {
formattedDate = ftDateFormat.timeAgo(date, { limit: 24 * ftDateFormat.inSeconds.hour });
} else if (format === 'time-ago-abbreviated') {
// eslint-disable-next-line no-console
console.warn('The o-date format "time-ago-abbreviated" is deprecated and the time is no longer abbreviated. Consider using "time-ago-limit-4-hours" instead.');
formattedDate = ftDateFormat.timeAgo(date);
} else if (format === 'time-ago-abbreviated-limit-4-hours') {
// eslint-disable-next-line no-console
console.warn('The o-date format "time-ago-abbreviated-limit-4-hours" is deprecated and the time is no longer abbreviated. Use "time-ago-limit-4-hours" instead.');
formattedDate = ftDateFormat.timeAgo(date, { limit: 4 * ftDateFormat.inSeconds.hour });
} else if (format === 'time-ago-no-seconds') {
formattedDate = ftDateFormat.timeAgoNoSeconds(date);
} else if (format !== null) {
formattedDate = ftDateFormat.format(date, format);
} else {
formattedDate = ftDateFormat.ftTime(date);
}
if (textCase === 'sentence') {
formattedDate = `${formattedDate.charAt(0).toUpperCase()}${formattedDate.substring(1)}`;
}
// To avoid triggering a parent live region unnecessarily
// <https://github.com/Financial-Times/o-date/pull/43>
const hasSingleTextNode = printer.childNodes.length === 1 &&
printer.firstChild &&
printer.firstChild.nodeType === 3;
if (hasSingleTextNode) {
printer.firstChild.nodeValue = formattedDate;
} else {
printer.innerHTML = formattedDate;
}
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static toDate() {
ftDateFormatWarning('toDate');
return ftDateFormat.toDate(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static format() {
ftDateFormatWarning('format');
return ftDateFormat.format(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static getSecondsBetween() {
ftDateFormatWarning('getSecondsBetween');
return ftDateFormat.getSecondsBetween(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static ftTime() {
ftDateFormatWarning('ftTime');
return ftDateFormat.ftTime(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static isNearFuture() {
ftDateFormatWarning('isNearFuture');
return ftDateFormat.isNearFuture(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static isFarFuture() {
ftDateFormatWarning('isFarFuture');
return ftDateFormat.isFarFuture(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static isToday() {
ftDateFormatWarning('isToday');
return ftDateFormat.isToday(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static isYesterday() {
ftDateFormatWarning('isYesterday');
return ftDateFormat.isYesterday(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static timeAgo() {
ftDateFormatWarning('timeAgo');
return ftDateFormat.timeAgo(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static asTodayOrYesterdayOrNothing() {
ftDateFormatWarning('asTodayOrYesterdayOrNothing');
return ftDateFormat.asTodayOrYesterdayOrNothing(...arguments);
}
/**
* @deprecated Use [ft-date-format]{@link https://github.com/Financial-Times/ft-date-format} instead.
* @returns {string} - A formatted date or empty string.
*/
static timeAgoNoSeconds() {
ftDateFormatWarning('timeAgoNoSeconds');
return ftDateFormat.timeAgoNoSeconds(...arguments);
}
}
export default ODate;