Skip to content

Commit

Permalink
Merge pull request #8312 from stopfstedt/date-picker-modifier
Browse files Browse the repository at this point in the history
replaces ember-render-modifiers with custom modifier in DatePicker component
  • Loading branch information
jrjohnson authored Feb 7, 2025
2 parents 842643b + e2116c1 commit ac640c1
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 71 deletions.
2 changes: 0 additions & 2 deletions packages/ilios-common/.lint-todo
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
add|ember-template-lint|no-at-ember-render-modifiers|5|2|5|2|fb8a149d14413d4dfc84ffd31349ef3f2ac6d17b|1731542400000|1762646400000|1793750400000|addon/components/date-picker.hbs
add|ember-template-lint|no-at-ember-render-modifiers|6|2|6|2|993f1e23f796f19a221eae6e24872755e0436cb4|1731542400000|1762646400000|1793750400000|addon/components/date-picker.hbs
add|ember-template-lint|no-at-ember-render-modifiers|6|8|6|8|c628ee621a6e921e369bf6bcb158a5ef932e6741|1731542400000|1762646400000|1793750400000|addon/components/editable-field.hbs
add|ember-template-lint|no-at-ember-render-modifiers|2|2|2|2|ad17d66e0fe1720bc8ddedc12dff3a105709765c|1731542400000|1762646400000|1793750400000|addon/components/html-editor.hbs
add|ember-template-lint|no-at-ember-render-modifiers|3|2|3|2|d39abab22a3e75d93f69335da422e7ef73b36283|1731542400000|1762646400000|1793750400000|addon/components/html-editor.hbs
Expand Down
5 changes: 2 additions & 3 deletions packages/ilios-common/addon/components/date-picker.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
aria-label={{t "general.pickADate"}}
class="date-picker"
data-test-date-picker
{{did-insert (perform this.setupPicker)}}
{{did-update (perform this.updatePicker) @value @maxDate @minDate}}
{{date-picker @value minDate=@minDate maxDate=@maxDate locale=this.intl.primaryLocale onChangeHandler=@onChange}}
...attributes
/>
/>
66 changes: 0 additions & 66 deletions packages/ilios-common/addon/components/date-picker.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,6 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { dropTask, restartableTask, waitForProperty } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
import flatpickr from 'flatpickr';
import { later, next } from '@ember/runloop';
import { isTesting } from '@embroider/macros';

export default class DatePickerComponent extends Component {
@service intl;

@tracked _flatPickerInstance;
@tracked isOpen = false;

updatePicker = restartableTask(async (element, [value]) => {
await waitForProperty(this, '_flatPickerInstance');
if (this._flatPickerInstance.selectedDates[0] != value) {
this._flatPickerInstance.setDate(value);
}
});

setupPicker = dropTask(async (element) => {
const currentLocale = this.intl.primaryLocale;
let locale;
switch (currentLocale) {
case 'fr':
// eslint-disable-next-line no-case-declarations
const { French } = await import('flatpickr/dist/l10n/fr.js');
locale = French;
break;
case 'es':
// eslint-disable-next-line no-case-declarations
const { Spanish } = await import('flatpickr/dist/l10n/es.js');
locale = Spanish;
break;
default:
locale = 'en';
}
this._flatPickerInstance = flatpickr(element, {
locale,
defaultDate: this.args.value,
formatDate: (dateObj) =>
this.intl.formatDate(dateObj, { day: '2-digit', month: '2-digit', year: 'numeric' }),
onChange: (selectedDates) => this.onChange(selectedDates[0]),
onOpen: () => {
// eslint-disable-next-line ember/no-runloop
later(() => {
this.isOpen = true;
}, 250);
},
onClose: () => {
this.isOpen = false;
},
maxDate: this.args.maxDate ?? null,
minDate: this.args.minDate ?? null,
disableMobile: isTesting(),
});
});

willDestroy() {
super.willDestroy(...arguments);
if (this._flatPickerInstance) {
this._flatPickerInstance.destroy();
}
}

async onChange(date) {
await this.args.onChange(date);
// eslint-disable-next-line ember/no-runloop
await next(() => {});
}
}
83 changes: 83 additions & 0 deletions packages/ilios-common/addon/modifiers/date-picker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Modifier from 'ember-modifier';
import { registerDestructor } from '@ember/destroyable';
import flatpickr from 'flatpickr';
import { French } from 'flatpickr/dist/l10n/fr.js';
import { Spanish } from 'flatpickr/dist/l10n/es.js';
import { isTesting } from '@embroider/macros';
import { service } from '@ember/service';

export default class DatePickerModifier extends Modifier {
@service intl;
flatpickr = null;
locale = null;
onChangeHandler = null;

constructor(owner, args) {
super(owner, args);
registerDestructor(this, () => {
this.locale = null;
this.onChangeHandler = null;
if (this.flatpickr) {
this.flatpickr.destroy();
this.flatpickr = null;
}
});
}

modify(element, [value], { minDate, maxDate, locale, onChangeHandler }) {
// We only need to set this once.
if (!this.onChangeHandler) {
this.onChangeHandler = onChangeHandler;
}
if (!this.flatpickr) {
this.locale = locale ?? this.intl.primaryLocale;
this.flatpickr = this.initPicker(element, value, minDate, maxDate, this.locale);
}

if (this.flatpickr.selectedDates[0] !== value) {
this.flatpickr.setDate(value);
}
if (this.flatpickr.minDate !== minDate) {
this.flatpickr.set('minDate', minDate);
}
if (this.flatpickr.maxDate !== maxDate) {
this.flatpickr.set('maxDate', maxDate);
}

if (locale && this.locale !== locale) {
this.locale = locale;
this.flatpickr.set('locale', this.getFlatpickrLocale(locale));
}
}

// @see https://flatpickr.js.org/localization/
getFlatpickrLocale(localeIdentifier) {
switch (localeIdentifier) {
case 'fr':
return French;
case 'es':
return Spanish;
default:
return 'en';
}
}

initPicker(element, value, minDate, maxDate, locale) {
return flatpickr(element, {
locale: this.getFlatpickrLocale(locale),
defaultDate: value,
formatDate: (dateObj) =>
this.intl.formatDate(dateObj, { day: '2-digit', month: '2-digit', year: 'numeric' }),
onChange: (selectedDates) => this.onChange(selectedDates[0]),
maxDate: maxDate ?? null,
minDate: minDate ?? null,
disableMobile: isTesting(),
});
}

async onChange(date) {
if (this.onChangeHandler) {
await this.onChangeHandler(date);
}
}
}
1 change: 1 addition & 0 deletions packages/ilios-common/app/modifiers/date-picker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ilios-common/modifiers/date-picker';
111 changes: 111 additions & 0 deletions packages/test-app/tests/integration/modifiers/date-picker-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'test-app/tests/helpers';
import { render, find } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Modifier | date-picker', function (hooks) {
setupRenderingTest(hooks);

test('it works with minimal input', async function (assert) {
const value = new Date('2014-03-02');
this.set('value', value);
await render(hbs`<div data-test-picker-element {{date-picker this.value}}></div>`);
const flatpickr = find('[data-test-picker-element]')._flatpickr;
assert.strictEqual(value.getTime(), flatpickr.selectedDates[0].getTime());
assert.notOk(flatpickr.config.minDate);
assert.notOk(flatpickr.config.maxDate);
assert.dom('option:nth-of-type(1)', flatpickr.monthElements[0]).hasText('January');
});

test('it works with minDate, maxDate, and locale as input', async function (assert) {
const value = new Date('2014-03-02');
const minDate = new Date('2014-01-12');
const maxDate = new Date('2014-05-11');
const locale = 'es';
this.set('value', value);
this.set('minDate', minDate);
this.set('maxDate', maxDate);
this.set('locale', locale);
await render(
hbs`<div
data-test-picker-element
{{date-picker this.value minDate=this.minDate maxDate=this.maxDate locale=this.locale}}
></div>`,
);
const flatpickr = find('[data-test-picker-element]')._flatpickr;
assert.strictEqual(value.getTime(), flatpickr.selectedDates[0].getTime());
assert.strictEqual(minDate.getTime(), flatpickr.config.minDate.getTime());
assert.strictEqual(maxDate.getTime(), flatpickr.config.maxDate.getTime());
assert.dom('option:nth-of-type(1)', flatpickr.monthElements[0]).hasText('Enero');
});

test('changing value is responsive', async function (assert) {
const value = new Date('2014-03-02');
this.set('value', value);
await render(hbs`<div data-test-picker-element {{date-picker this.value}}></div>`);
const flatpickr = find('[data-test-picker-element]')._flatpickr;
assert.strictEqual(value.getTime(), flatpickr.selectedDates[0].getTime());
const newValue = new Date('2024-03-03');
this.set('value', newValue);
assert.strictEqual(newValue.getTime(), flatpickr.selectedDates[0].getTime());
});

test('changing minDate is responsive', async function (assert) {
const value = new Date('2014-03-02');
const minDate = new Date('2014-01-12');
this.set('value', value);
this.set('minDate', minDate);
await render(
hbs`<div data-test-picker-element {{date-picker this.value minDate=this.minDate}}></div>`,
);
const flatpickr = find('[data-test-picker-element]')._flatpickr;
assert.strictEqual(minDate.getTime(), flatpickr.config.minDate.getTime());
const newMinDate = new Date('2024-03-03');
this.set('minDate', newMinDate);
assert.strictEqual(newMinDate.getTime(), flatpickr.config.minDate.getTime());
});

test('changing maxDate is responsive', async function (assert) {
const value = new Date('2014-03-02');
const maxDate = new Date('2014-01-12');
this.set('value', value);
this.set('maxDate', maxDate);
await render(
hbs`<div data-test-picker-element {{date-picker this.value maxDate=this.maxDate}}></div>`,
);
const flatpickr = find('[data-test-picker-element]')._flatpickr;
assert.strictEqual(maxDate.getTime(), flatpickr.config.maxDate.getTime());
const newMaxDate = new Date('2024-03-03');
this.set('maxDate', newMaxDate);
assert.strictEqual(newMaxDate.getTime(), flatpickr.config.maxDate.getTime());
});

test('changing locale is responsive', async function (assert) {
const value = new Date('2014-03-02');
const locale = 'es';
this.set('value', value);
this.set('locale', locale);
await render(
hbs`<div data-test-picker-element {{date-picker this.value locale=this.locale}}></div>`,
);
const flatpickr = find('[data-test-picker-element]')._flatpickr;
assert.dom('option:nth-of-type(1)', flatpickr.monthElements[0]).hasText('Enero');
this.set('locale', 'fr');
assert.dom('option:nth-of-type(1)', flatpickr.monthElements[0]).hasText('janvier');
});

test('onChange callback fires', async function (assert) {
assert.expect(1);
const value = new Date('2014-03-02');
const newValue = new Date('2022-06-23');
this.set('value', value);
this.set('onChange', (date) => {
assert.strictEqual(newValue.getTime(), date.getTime());
});
await render(
hbs`<div data-test-picker-element {{date-picker this.value onChangeHandler=this.onChange}}></div>`,
);
const flatpickr = find('[data-test-picker-element]')._flatpickr;
flatpickr.setDate(newValue, true);
});
});

0 comments on commit ac640c1

Please sign in to comment.