As always, the manual is longer than the codes. What's wrong with my modules.
-
Author: Seongnoh Sean Yi eouia0819@gmail.com
-
Repository : https://github.com/MMRIZE/MMM-CustomElementTime
-
Version: 1.0.0 (2021-09-24)
You can get <mm-time
> custom element (a.k.a. web components), which enables to display time with these features;
- self-redraw
- alarm
- locale/language-aware
- relative-humanized
- customizable
- programmable/controllable in other modules
- shoot-and-forget about time manipulating.
- without 3rd party library/dependency
- placable anywhere HTML is allowed
- I. Motivation & Concept
- II. Installation & Setup
- III.
<mm-time>
spec - IV. Example
Tons of MM modules have been handling time-related stuff for many years. A module developer needs to implement his logic to display "in 5 minutes" or "Friday, 25. December". Yup. It looks pretty straightforward at first glance until considering customizability or localization features. There was a relatively easy solution like momentJS
. But now we are facing of deprecation of momentJS
. Maybe luxon
would be the best alternative, but...
JavaScript environment is evolving every day so fast. Now we can obtain brand new weapons - Custom Element
and Intl
(Frankly, they have existed for several years already). I think these new features will change (and is changing) the whole things of Javascript applications, including MagicMirror.
This MM module gives the custom element <mm-time>
, which can display time
in the MM screen anywhere.
This module is not just for average users, and even more, it is a THING for developers. However, even ordinary users can use this tag wherever they want, and HTML is allowed. For example, you can put this <mm-time>
tag in the helloworld
module to display your custom world-clocks or event countdown.
For the developer; you can use this tag to reduce your code and not worry about handling displaying time. With just inserting <mm-time>
into your module's screen output, you don't need to make logic for taking time-related stuff by yourself. Additionally, this tag can give more than your expectation.
This module and custom tag <mm-time>
is made with only pure Javascript. Without momentJS
or something 3rd Party dependency, probably you can handle the time how to show, I hope. (But the calculation of time is a different perspective.)
cd ~/MagicMirror/modules
git clone https://github.com/MMRIZE/MMM-CustomElementTime
It's better to upgrade your Electron dependency to use new features fully.
cd ~/MagicMirror
npm install electron@latest --save-optional
You might need to upgrade/rebuild other 3rd party MM modules to obtain compatibility.
In the case of serveronly
mode, you may need a newer updated browser. (e.g. Chrome/Chromium > v92 - 2021 Jun.)
This module doesn't have its own real estate in the MM screen. Don't set
position
.
{
module: 'MMM-CustomElementTime'
},
{
module: 'MMM-CustomElementTime',
config: {
locale: 'de-DE', // By default, MM's global locale will be used unless this value exists.
customFormatter: {
"myFormatter": (parts) => { ... },
}
}
},
locale
: Set default locale of<mm-time>
tag.customFormatter
: Set custom formatters to customize output.
Then, put <mm-time>
anywhere you want.
- This tag is extended from normal
HTML
element, so all the general behaviours (attribute, event, method, attribute-property reflection, etc.) of normal HTML tags have also. - Format:
<mm-time attributes="..." ...>Fallback Message</mm-time>
- Fallback Message would be shown when
mm-time
is not working properly. - so
<mm-time time="2021-12-25">2021-12-25</mm-time>
would be the safe fallback format. - Self-closing
<mm-time/>
format is not allowed by HTML custom element spec definition.
- Fallback Message would be shown when
- By default,
<mm-time>
hasinline-block
as CSS display property. You can override in your CSS definitions. - This tag's will be refreshed when;
- tag is connected to DOM,
- some core attribute('time', 'range', etc.) is changed,
- refreshing by
refresh
interval attribute. - Forcely refreshed by
.update()
method.
- JS DOM Manipulating also be possible.
var eventTime = document.createElement('mm-time')
eventTime.time = new Date('2021-12-25 12:34:56')
document.getElementById('somethinhg').appendChild(eventTime)
eventTime.update()
- as DOM property:
.time
(ms Number | text | Date Object)
This attribute is for setting target time to display. If omitted, the current time(when connected or refreshed) will be used.
<mm-time></mm-time> // Current time
<mm-time time="2021-12-25 12:34"></mm-time> // Target time as date-like text
<mm-time time="1632431481000"></mm-time> // Target time as epoch timestamp
time
attribute could have the value UNIX epoch timestamp (ms)
or date-like text
(RFC 2822 or ISO 8601). date-like text
would make unexpected result due to browser compatibility, so timestamp(ms)
would be recommended. (Anyway, most modern browsers can handle it properly.)
As a property of DOM manipulating, .time
property setter can get Date Object
. In this case, time
attribute will get the timestamp value from the Date object as the reflection of the property .time
.
mmtimeTag.time = new Date('2021-01-01') // or "2021-01-01" or "1609459200000" or 1609459200000
console.log(mmtimeTag.getAttribute('time')) // => "1609459200000"
- as DOM property:
.refresh
(number)
You can redraw the tag periodically with this attribute.
<mm-time data-time-style="long" refresh="1000"></mm-time> // It will refresh the output per 1 sec.
- as DOM property:
.relative
(boolean)
This attribute decide absolute DateTimeFormat ("Friday, 24, September 11:30 AM") or relative format ("3 hours ago").
If the range
attribute is set, relative
attribute will be ignored. (will be displayed as 'absolute')
<mm-time time="2021-12-25 12:34:56"></mm-time>
// By default, absolute format will be the output like "25/12/2021"
<mm-time time="2021-12-25 12:34:56" relative></mm-time>
// relative format : "in 2 months"
<mm-time time="2021-12-25 12:34:56" range="2021-12-31 11:59:59" relative></mm-time>
// 'relative' attribute will be ignored and the output will be the range format
- as DOM property:
.relativeUnit
(text)
This will be effective only with type="relative"
.
<mm-time time="2021-12-25 12:34:56" relative-unit="minute" relative></mm-time> /* "in 1,234,567 minutes" */
<mm-time time="2021-12-25 12:34:56" relative-unit="auto" relative></mm-time> /* "in 2 months" */
<mm-time time="2021-12-25 12:34:56" relative></mm-time> /* "in 2 months" */
Available values are year
, quarter
, month
, week
, day
, hour
, minute
, second
and their plural forms.(e.g. years
). The default value is omittable auto
. auto
will seek and show the best-humanized matching value to avoid too ridiculousy big number.
- as DOM property:
.relativeReverse
(boolean)
By default, relative time is calculated how far the target time be away from now
. You can change the direction reversively.
<mm-time time="2021-12-25" relative relative-unit="day"></mm-time>
// "in 92 days"
<mm-time time="2021-12-25" relative relative-unit="day" relative-reverse></mm-time>
// "92 days ago"
- as DOM property:
.relativeQuarter
(boolean)
Under some culture, quarter
is unfamiliar unit to count periods. So quarter
will be ignored by default in auto
unit mode. When you want to use quarter
in auto
mode, use this attribute.
<mm-time time="2020-12-25" relative></mm-time>
// "9 months ago"
<mm-time time="2020-12-25" relative relative-quarter></mm-time>
// "3 quarters ago"
- as DOM property:
.range
(ms Number | text | Date Object)
When this attribute set, the output will be the Range Format (Shared parts, and Start/End parts). For example; 2021-12-25 12:34 - 2021-12-25 23:59
is sharing 2021-12-15, PM
, so it could be displayed like December 25, 2021, 12:34 - 23:59 PM
.
<mm-time time="2021-12-25 12:34:56" range="2021-12-25 23:59:59" data-date-style="long" data-time-style="short"></mm-time>
- as DOM property:
.locale
(text)
This attribute enables locale-aware output. Locale format is BCP 47. ("en"
, "en-US"
, "en-US-u-ca-buddhist"
, etc. But "en_US"
is not valid.)
<mm-time></mm-time>
// By default, MMM-CustomElementTime's config.locale or MM Global config.locale or system locale will be used.
// If your default locale is `en-US`, this will show "9/24/2021"
<mm-time locale="en-CA"></mm-time>
// It will show AngloCanadian format; "2021-09-24"
- as DOM property:
.dataSet
(object)
Each absolute/relative formats could have plenty options to format the output.
<mm-time></mm-time> // "9/24/2021"
<mm-time data-day="2-digit"></mm-time> // "24"
<mm-tiem data-date-style="full"></mm-time> // "Friday, September 24, 2021"
<mm-time data-calendar="chinese"></mm-time> // "8/18/2021" <-- Chinese Lunar calendar
<mm-time time="2021-12-25" data-style="short" relative></mm-time> // "in 3 mo."
...
- For absolute format options: See
options
part - For relative format options: See
options
part - If you want to apply
month="numeric"
option for your output, appen prefix'data-'
and describe like this;data-month="numeric"
. - HTML allows only lower-cased characters, then if you want to apply camelCased options like
timeStyle="long"
, dash-conversion is needed like this;data-time-style="long"
- Some options could not be used together. Read the above links.
- At this moment, some options might be misbehavioured in some environment due to Browser compatibility or implementation difference. Read above links. (Updating your browser/electron to the latest version will solve this issue.)
- as DOM property:
.decouple
(boolean)
With this attribute, you can disassemble the output text to contextual HTML parts.
<p>
<mm-time data-time-style="medium"></mm-time><br/>
<mm-time data-time-style="medium" decouple></mm-time>
</p>
will seems having same screen output;
9:20:31 PM
9:20:31 PM
but will have different HTML
<p>
<mm-time data-time-style="medium">
9:20:31 PM
</mm-time>
<br/>
<mm-time data-time-style="medium" decouple="">
<span class="mm-time-parts hour">9</span>
<span class="mm-time-parts literal">:</span>
<span class="mm-time-parts minute">20</span>
<span class="mm-time-parts literal">:</span>
<span class="mm-time-parts second">31</span>
<span class="mm-time-parts literal"> </span>
<span class="mm-time-parts dayPeriod">PM</span>
</mm-time>
</p>
You can decorate the parts with CSS or can manipulate DOM.
- as DOM property:
.formatter
(Array of string or list-like text)
You can use several formatters sequentially by describing them with comma or space separtor.
<mm-time formatter="myFormat_1, myFormat_2 myFormat_3"></mm-time>
You can handle the parts themselves before displaying output on the MM screen with custom formatters. custom formatters are definable in config.customFormatter: {}
or registrable from other modules via notification
or direct-access of .registerCustomFormatter()
Or your module can register its own custom formatter by itself.
// by public method of module
var CustomElementTimeModule = MM.getModules().find(module => (module.name === 'MMM-CustomElementTime'))
CustomElementTimeModule.registerCustomFormatter('myFormat', (parts) => {
...
})
// or by notification
var payload = {
name: 'myFormat',
callback: (parts) => {
...
}
}
this.sendNotification('CETIME_REGISTER_CUSTOM_FORMATTER', payload)
See example how to use in Examples section.
Usually, all general Events (e.g. 'click', 'loaded', ... etc.) will work. Additionally two custom events are added. Both custom event will have detail
like this;
{
displayed, // screen output of this tag when the event is fired.
time, // Date object of target time when the event is fired.
}
This event will be fired when the tag is refreshed.
This event will be fired when the target time is passed. Time-passing will be checked on refresh time. So this event might not occur on exact target time.
For the two custom events, onUpdated
and onPassed
event handler is enabled.
var eventTime = document.getElementById("eventTime")
eventTime.onUpdated = (event) => {
console.log(event.target, event.detail)
}
Of course, you can use .addEventListener(eventId, func)
.
function alertOnce (event) {
console.log(event.target, event.detail)
event.target.removeEventListener('passed', this)
}
eventTime.addEventListener('passed', alertOnce)
Careless handling event could become a common pitfall of
gc
fail of javascript. You need to be careful.
You can update the tag forcely.
var eventTime = document.getElementById("eventTime")
eventTime.update()
For who uses traditional Taiwanese living in Berlin with the Chinese calendar.
<mm-time locale="zh-Hant-tw" data-numbering="hanidec" data-calendar="chinese" data-timezone="Europe/Berlin" data-date-style="long" data-time-style="medium"/></mm-time>
will show;
2021辛丑年八月十九 下午7:38:01
...
<div class="article_pulished">
Published <mm-time time="2021-09-24 12:34" data-numeric="auto" relative refresh="60000">at 24/09/21 12:34 PM</mm-time>
</div>
...
will show like these by time passing without redrawing the moudle's output.
Published 35 minutes ago
Published yesterday
Published 2 weeks ago
...
Even if your module is using momentJS
to display this kind of relative time, you need to refresh or redraw output to reflect of the time passing by yourself. But <mm-time>
will redraw itself automatically.
{
module: "helloworld",
position: "top_left",
header: 'mm-time custom tag in HelloWorld',
config: {
text: `
<style>
mm-time .mm-time-parts.second {
font-size: 50%;
line-height: 50%;
color: var(--color-text-dimmed);
vertical-align: super;
}
mm-time .mm-time-parts.minute + .mm-time-parts.literal {
display:none;
}
</style>
<mm-time class="date normal medium" data-date-style="full" refresh="1000"></mm-time><br/>
<mm-time class="time bright large light" data-hour12="false" data-time-style="medium" refresh="1000" decouple></mm-time>
`
}
},
will make the helloworld
module to display clock having exactly same look with the default clock module.
{
module: "MMM-CustomElementTime",
config: {
customFormatter: {
"backwardSecond": (parts) => {
for (i = 0; i < parts.length; i++) {
let { type, value } = parts[i]
if (type === 'second') {
let backwardSecond = 59 - value
parts[i].value = `<span style="color:red;">${backwardSecond}</span>`
}
}
return parts
},
// ...
}
}
},
<mm-time data-time-style="medium" refresh="1000" formatter="backwardSecond"></mm-time>
This will display red seconds that will run backward from 59 to 0. How weird.
<p>
Seoul : <mm-time data-day="numeric" data-weekday="long" data-hour="2-digit" data-minute="2-digit" data-time-zone="Asia/Seoul">Seoul time</mm-time><br/>
New York : <mm-time data-day="numeric" data-weekday="long" data-hour="2-digit" data-minute="2-digit" data-time-zone="EST">New York time</mm-time><br/>
</p>
will show;
Seoul : 26 Sunday, 03:01 AM
New York : 25 Saturday, 01:01 PM
<mm-time id="alarm1" time="2021-09-24 17:00" relative relative-unit="second" refresh="1000"></mm-time>
var alarm = document.getElementById('alarm1')
alarm.onPassed = () => {
this.sendNotification('SHOW_ALERT', {
message:'It is 17:00!!',
timer: 5000
})
alarm.onPassed = null
}
This will show an alert on 17:00 (when the element would be updated by refresh = "1000")
The next event will take place on <mm-time time="2021-10-04" range="2021-10-08" data-month="short" data-day="numeric">2021-10-04 ~ 2021-10-08</mm-time>.
will show;
The next event will take place on Oct 4 - 8.