Skip to content

Commit

Permalink
Fix duplicated flatpickr calendar - DOM elements
Browse files Browse the repository at this point in the history
The datepicker is initialized multiple times, because the type attribute is reserved and had a side effect that triggered two times during the initialization. The datepicker now destroys the flatpickr instance, when the component will be removed from the DOM.
  • Loading branch information
sascha-karnatz committed Oct 16, 2023
1 parent d00cf37 commit 2385ca1
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 22 deletions.
2 changes: 1 addition & 1 deletion app/helpers/alchemy/admin/base_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ def alchemy_datepicker(object, method, html_options = {})
input_field = text_field object.class.name.demodulize.underscore.to_sym,
method.to_sym, {type: "text", class: type, value: value}.merge(html_options)

content_tag("alchemy-datepicker", input_field, type: type)
content_tag("alchemy-datepicker", input_field, "input-type" => type)
end

# Render a hint icon with tooltip for given object.
Expand Down
19 changes: 14 additions & 5 deletions app/javascript/alchemy_admin/components/datepicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,35 @@ import flatpickr from "flatpickr"

class Datepicker extends AlchemyHTMLElement {
static properties = {
type: { default: "date" }
inputType: { default: "date" }
}

constructor() {
super()
this.flatpickr = undefined
}

afterRender() {
const options = {
// alchemy_i18n supports `zh_CN` etc., but flatpickr only has two-letter codes (`zh`)
locale: currentLocale().slice(0, 2),
altInput: true,
altFormat: translate(`formats.${this.type}`),
altFormat: translate(`formats.${this.inputType}`),
altInputClass: "flatpickr-input",
dateFormat: "Z",
enableTime: /time/.test(this.type),
noCalendar: this.type === "time",
enableTime: /time/.test(this.inputType),
noCalendar: this.inputType === "time",
time_24hr: translate("formats.time_24hr"),
onValueUpdate(_selectedDates, _dateStr, instance) {
Alchemy.setElementDirty(instance.element.closest(".element-editor"))
}
}

flatpickr(this.getElementsByTagName("input")[0], options)
this.flatpickr = flatpickr(this.getElementsByTagName("input")[0], options)
}

disconnected() {
this.flatpickr.destroy()
}
}

Expand Down
6 changes: 3 additions & 3 deletions app/views/alchemy/admin/styleguide/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@

<div class="input">
<label class="control-label" for="datetime_picker">Date Time Picker</label>
<alchemy-datepicker type="datetime">
<alchemy-datepicker input-type="datetime">
<input type="text" id="datetime_picker">
</alchemy-datepicker>
</div>
Expand All @@ -83,12 +83,12 @@
<div class="control_group">
<div class="input-row">
<div class="input-column">
<alchemy-datepicker type="date">
<alchemy-datepicker input-type="date">
<input type="text" id="date_picker">
</alchemy-datepicker>
</div>
<div class="input-column">
<alchemy-datepicker type="time">
<alchemy-datepicker input-type="time">
<input type="text" id="time_picker">
</alchemy-datepicker>
</div>
Expand Down
6 changes: 3 additions & 3 deletions spec/helpers/alchemy/admin/base_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,22 +126,22 @@ module Alchemy
let(:type) { nil }

it "renders a text field with data attribute for 'date'" do
is_expected.to have_selector("alchemy-datepicker[type='date'] input[type='text']")
is_expected.to have_selector("alchemy-datepicker[input-type='date'] input[type='text']")
end

context "when datetime given as type" do
let(:type) { :datetime }

it "renders a text field with data attribute for 'datetime'" do
is_expected.to have_selector("alchemy-datepicker[type='datetime'] input[type='text']")
is_expected.to have_selector("alchemy-datepicker[input-type='datetime'] input[type='text']")
end
end

context "when time given as type" do
let(:type) { :time }

it "renders a text field with data attribute for 'time'" do
is_expected.to have_selector("alchemy-datepicker[type='time'] input[type='text']")
is_expected.to have_selector("alchemy-datepicker[input-type='time'] input[type='text']")
end
end

Expand Down
44 changes: 34 additions & 10 deletions spec/javascript/alchemy_admin/components/datepicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,43 @@ describe("alchemy-datepicker", () => {
component = renderComponent("alchemy-datepicker", html)
})

it("should render the input field", () => {
expect(component.getElementsByTagName("input")[0]).toBeInstanceOf(
HTMLElement
)
describe("picker without type", () => {
it("should render the input field", () => {
expect(component.getElementsByTagName("input")[0]).toBeInstanceOf(
HTMLElement
)
})

it("should enhance the input field with a flat picker config", () => {
expect(component.getElementsByTagName("input")[0].className).toEqual(
"flatpickr-input"
)
})

it("should have a type attribute", () => {
expect(component.inputType).toEqual("date")
})

it("creates a flatpickr-calendar on opening", () => {
expect(document.querySelector(".flatpickr-calendar")).toBeInstanceOf(
HTMLElement
)
})
})

it("should enhance the input field with a flat picker config", () => {
expect(component.getElementsByTagName("input")[0].className).toEqual(
"flatpickr-input"
)
describe("with type attribute", () => {
it("creates only one flatpickr-calendar on opening", () => {
component.setAttribute("inputType", "datetime")
expect(
document.getElementsByClassName("flatpickr-calendar").length
).toEqual(1)
})
})

it("should have a type attribute", () => {
expect(component.type).toEqual("date")
describe("remove datepicker", () => {
it("removes the flatpickr-calendar after removing", () => {
component.remove()
expect(document.querySelector(".flatpickr-calendar")).toBeNull()
})
})
})

0 comments on commit 2385ca1

Please sign in to comment.