Skip to content

Commit

Permalink
feat(CountdownController): New configuration value padZeros to cont…
Browse files Browse the repository at this point in the history
…rol whether hours, minutes and seconds are output as zero-padded or not. Also fixed `removeUnused` behaviour. (#351)

The default is "true" so if you are expecting hours minutes and seconds to be unpadded, you'll need to add data-countdown-pad-zeros="false" to your controller elements.
  • Loading branch information
Sub-Xaero authored Apr 17, 2024
1 parent d1c4093 commit 9a4ad0b
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 40 deletions.
9 changes: 5 additions & 4 deletions docs/docs/controllers/visual/countdown_controller.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ A controller that displays a countdown clock to a specified date/time.

## [Values](https://stimulus.hotwire.dev/reference/values)

| Value | Type | Description | Default |
|----------------|---------|-------------------------------------------------------------------------------|---------|
| `deadline` | Date | The date/time to count down to | - |
| `removeUnused` | Boolean | Whether to remove unused targets. i.e. if there are no years in the countdown | `false` |
| Value | Type | Description | Default |
|----------------|---------|---------------------------------------------------------------------------------------------------------|---------|
| `deadline` | Date | The date/time to count down to | - |
| `removeUnused` | Boolean | Whether to remove unused targets. i.e. if there are no years in the countdown | `false` |
| `padZeros` | Boolean | Whether to pad the countdown values for hours, minutes, and seconds with zeros i.e. "06" instead of "6" | `true` |

## Events

Expand Down
51 changes: 37 additions & 14 deletions examples/controllers/countdown_controller.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="favicon.svg"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link href="favicon.svg" rel="icon" type="image/svg+xml"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Countdown Controller</title>
</head>
<body>
<div class="container p-4 text-center">
<p>Countdown til next Christmas:</p>
<div
data-controller="countdown update-date interval"
data-interval-seconds-value="10"
class="countdown-in-words d-flex justify-content-center"
data-action="countdown:ended->update-date#update"
data-update-date-property-value="countdownDeadlineValue"
data-controller="countdown update-date interval"
data-countdown-deadline-value="1970/01/01 00:00:00"
data-countdown-pad-zeros-value="false"
data-countdown-remove-unused-value="true"
data-interval-seconds-value="10"
data-update-date-property-value="countdownDeadlineValue"
>
<span data-countdown-target="years"></span>
<span data-countdown-target="months"></span>
Expand All @@ -25,42 +27,63 @@
<span data-countdown-target="seconds"></span>
</div>

<hr>
<p>
Sale ends in:
</p>

<div
class="d-flex justify-content-center"
data-action="countdown:ended->update-date#update"
data-controller="countdown update-date interval"
data-countdown-deadline-value="1970/01/01 00:00:00"
data-countdown-pad-zeros-value="true"
data-countdown-remove-unused-value="true"
data-interval-seconds-value="10"
data-update-date-property-value="countdownDeadlineValue"
>
<span data-countdown-target="hours"></span>
:
<span data-countdown-target="minutes"></span>
:
<span data-countdown-target="seconds"></span>
</div>
</div>

<style>
[data-countdown-target] + [data-countdown-target]::before {
.countdown-in-words [data-countdown-target] + [data-countdown-target]::before {
content: ", ";
}

[data-countdown-target] + [data-countdown-target]:nth-last-child(1)::before {
.countdown-in-words [data-countdown-target] + [data-countdown-target]:nth-last-child(1)::before {
content: ", and ";
}

[data-countdown-target="years"]::after {
.countdown-in-words [data-countdown-target="years"]::after {
content: " years";
}

[data-countdown-target="months"]::after {
.countdown-in-words [data-countdown-target="months"]::after {
content: " months";
}

[data-countdown-target="days"]::after {
.countdown-in-words [data-countdown-target="days"]::after {
content: " days";
}

[data-countdown-target="hours"]::after {
.countdown-in-words [data-countdown-target="hours"]::after {
content: " hours";
}

[data-countdown-target="minutes"]::after {
.countdown-in-words [data-countdown-target="minutes"]::after {
content: " minutes";
}

[data-countdown-target="seconds"]::after {
.countdown-in-words [data-countdown-target="seconds"]::after {
content: " seconds";
}

</style>
<script type="module" src="/main.js"></script>
<script src="/main.js" type="module"></script>
</body>
</html>
76 changes: 54 additions & 22 deletions packages/controllers/src/visual/countdown_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import { installClassMethods } from "@stimulus-library/mixins";

export class CountdownController extends BaseController {

static values = { deadline: String, removeUnused: Boolean };
static values = {
deadline: String,
removeUnused: Boolean,
padZeros: Boolean,
};
static targets = ["years", "months", "days", "hours", "minutes", "seconds"];
static classes = ["countingDown", "ended"];

// Values
declare readonly deadlineValue: string;
declare readonly removeUnusedValue: boolean;
declare readonly hasRemoveUnusedValue: boolean;
declare readonly padZerosValue: boolean;
declare readonly hasPadZerosValue: boolean;
// Targets
declare readonly hasYearsTarget: boolean;
declare readonly yearsTarget: HTMLElement;
Expand All @@ -40,6 +46,10 @@ export class CountdownController extends BaseController {
return this.hasRemoveUnusedValue ? this.removeUnusedValue : false;
}

get _padZeros(): boolean {
return this.hasPadZerosValue ? this.padZerosValue : true;
}

get _deadlineDate() {
return new Date(this.deadlineValue);
}
Expand All @@ -50,7 +60,6 @@ export class CountdownController extends BaseController {

connect() {
this._timeout = setTimeout(this._tick, 1000);
console.log(this._timeout);
installClassMethods(this);
this.addCountingDownClasses();
}
Expand Down Expand Up @@ -82,7 +91,7 @@ export class CountdownController extends BaseController {
this.dispatchEvent(this.el, this.eventName("ended"));
return;
} else {
distance = intervalToDuration({ start: this._deadlineDate, end: now });
distance = intervalToDuration({ start: now, end: this._deadlineDate });
this._timeout = setTimeout(this._tick, 1000);
}

Expand Down Expand Up @@ -117,47 +126,70 @@ export class CountdownController extends BaseController {
}
}

_updateTarget(target: HTMLElement, value: number) {
_updateTarget(target: HTMLElement, value: string) {
this._removeTargetIfUnused(target, value);
target.innerHTML = value.toString();
target.innerHTML = value;
}

_removeTargetIfUnused(target: HTMLElement, value: number) {
_removeTargetIfUnused(target: HTMLElement, value: string) {
let intValue = Number.parseInt(value);
if (this._removeUnused) {
if (value === 0 && target.dataset.unused) {
if (Number.parseInt(target.dataset.unused) > Date.now() + 1500) {
if (intValue === 0 && target.dataset.unused) {
let number = Number.parseInt(target.dataset.unused);
if (number < Date.now() - 1000) {
target.remove();
}
} else if (value == 0) {
} else if (intValue == 0) {
target.dataset.unused = Date.now().toString();
} else {
target.dataset.unused = undefined;
delete target.dataset.unused;
}
}
}

_years(duration: Duration): number {
return duration.years || 0;
_years(duration: Duration): string {
let unit = duration.years || 0;
return (unit > 0 ? unit : 0).toString();
}

_months(duration: Duration): number {
return duration.months || 0;
_months(duration: Duration): string {
let unit = duration.months || 0;
return (unit > 0 ? unit : 0).toString();
}

_days(duration: Duration): number {
return duration.days || 0;
_days(duration: Duration): string {
let unit = duration.days || 0;
return (unit > 0 ? unit : 0).toString();
}

_hours(duration: Duration): number {
return duration.hours || 0;
_hours(duration: Duration): string {
let unit = duration.hours || 0;
let str = (unit > 0 ? unit : 0).toString();
if (this._padZeros) {
return str.padStart(2, "0");
} else {
return str;
}
}

_minutes(duration: Duration): number {
return duration.minutes || 0;
_minutes(duration: Duration): string {
let unit = duration.minutes || 0;
let str = (unit > 0 ? unit : 0).toString();
if (this._padZeros) {
return str.padStart(2, "0");
} else {
return str;
}
}

_seconds(duration: Duration): number {
return duration.seconds || 0;
_seconds(duration: Duration): string {
let unit = duration.seconds || 0;
let str = (unit > 0 ? unit : 0).toString();
if (this._padZeros) {
return str.padStart(2, "0");
} else {
return str;
}
}

}

0 comments on commit 9a4ad0b

Please sign in to comment.