Skip to content

Commit

Permalink
Merge pull request #2621 from AlchemyCMS/alchemy-button-component
Browse files Browse the repository at this point in the history
Add alchemy-button web component
  • Loading branch information
tvdeyen authored Nov 28, 2023
2 parents 8d23973 + d974b3e commit a43e3d7
Show file tree
Hide file tree
Showing 17 changed files with 131 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ class window.Alchemy.ConfirmDialog extends Alchemy.Dialog
@cancel_button.focus()
@cancel_button.click =>
@close()
Alchemy.Buttons.enable()
false
@ok_button.click =>
@close()
Expand Down
3 changes: 0 additions & 3 deletions app/assets/javascripts/alchemy/alchemy.dialog.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,6 @@ class window.Alchemy.Dialog
# Watches ajax requests inside of dialog body and replaces the content accordingly
watch_remote_forms: ->
form = $('[data-remote="true"]', @dialog_body)
form.bind "ajax:complete", () =>
Alchemy.Buttons.enable(@dialog_body)
return
form.bind "ajax:success", (event) =>
xhr = event.detail[2]
content_type = xhr.getResponseHeader('Content-Type')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ Alchemy.ElementEditors =
# Prevent this event from beeing called twice on the same element
if event.currentTarget == event.target
Alchemy.setElementClean($element)
Alchemy.Buttons.enable($element)
true

# Toggle visibility of the ingredient fields in the group
Expand Down
3 changes: 1 addition & 2 deletions app/javascript/alchemy_admin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import "@hotwired/turbo-rails"
import Rails from "@rails/ujs"

import Buttons from "alchemy_admin/buttons"
import GUI from "alchemy_admin/gui"
import { translate } from "alchemy_admin/i18n"
import Dirty from "alchemy_admin/dirty"
Expand All @@ -21,6 +20,7 @@ import PagePublicationFields from "alchemy_admin/page_publication_fields"
$.fx.speeds._default = 400

// Web Components
import "alchemy_admin/components/button"
import "alchemy_admin/components/char_counter"
import "alchemy_admin/components/datepicker"
import "alchemy_admin/components/node_select"
Expand All @@ -41,7 +41,6 @@ if (typeof window.Alchemy === "undefined") {

// Enhance the global Alchemy object with imported features
Object.assign(Alchemy, {
Buttons,
...Dirty,
GUI,
t: translate, // Global utility method for translating a given string
Expand Down
62 changes: 0 additions & 62 deletions app/javascript/alchemy_admin/buttons.js

This file was deleted.

52 changes: 52 additions & 0 deletions app/javascript/alchemy_admin/components/button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Spinner from "../spinner"

class Button extends HTMLButtonElement {
connectedCallback() {
if (this.form) {
this.form.addEventListener("submit", (event) => {
const isDisabled = this.getAttribute("disabled") === "disabled"

if (isDisabled) {
event.preventDefault()
event.stopPropagation()
} else {
this.disable()
}
})

if (this.form.dataset.remote == "true") {
this.form.addEventListener("ajax:complete", () => {
this.enable()
})
}
} else {
console.warn("No form for button found!", this)
}
}

disable() {
const spinner = new Spinner("small")
const rect = this.getBoundingClientRect()

this.dataset.initialButtonText = this.innerHTML
this.setAttribute("disabled", "disabled")
this.setAttribute("tabindex", "-1")
this.classList.add("disabled")
this.style.width = `${rect.width}px`
this.style.height = `${rect.height}px`
this.innerHTML = " "

spinner.spin(this)
}

enable() {
this.classList.remove("disabled")
this.removeAttribute("disabled")
this.removeAttribute("tabindex")
this.style.width = null
this.style.height = null
this.innerHTML = this.dataset.initialButtonText
}
}

customElements.define("alchemy-button", Button, { extends: "button" })
1 change: 0 additions & 1 deletion app/javascript/alchemy_admin/gui.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import TagsAutocomplete from "alchemy_admin/tags_autocomplete"

function init(scope) {
Alchemy.Buttons.observe(scope)
if (!scope) {
Alchemy.watchForDialogs()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<%= content_tag :div, class: 'add-nested-element', data: { element_id: element.id } do %>
<% if element.expanded? || element.fixed? %>
<% if element.nestable_elements.length == 1 &&
(nestable_element = element.nestable_elements.first) &&
<% if element.nestable_elements.length == 1 &&
(nestable_element = element.nestable_elements.first) &&
Alchemy::Element.all_from_clipboard_for_parent_element(get_clipboard("elements"), element).none?
%>
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
<%= f.hidden_field :name %>
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
<%= f.hidden_field :parent_element_id, value: element.id %>
<button class="button add-nestable-element-button" data-alchemy-button>
<button class="add-nestable-element-button" is="alchemy-button">
<%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element, scope: 'element_names')) %>
</button>
<% end %>
Expand All @@ -24,4 +24,4 @@
}, class: "button add-nestable-element-button" %>
<% end %>
<% end %>
<% end %>
<% end %>
2 changes: 1 addition & 1 deletion app/views/alchemy/admin/elements/_footer.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</p>
<% end %>

<button type="submit" form="element_<%= element.id %>_form" class="button" data-alchemy-button>
<button type="submit" form="element_<%= element.id %>_form" is="alchemy-button">
<%= Alchemy.t(:save) %>
</button>
</div>
1 change: 0 additions & 1 deletion app/views/alchemy/admin/elements/create.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
$element_area = $('[name="fixed-element-<%= @element.id %>"]');
<% elsif @element.parent_element %>
$element_area = $('#element_<%= @element.parent_element_id %> > .nestable-elements > .nested-elements');
Alchemy.Buttons.enable('.nestable-elements');
<% else %>
$element_area = $('#main-content-elements');
<% end %>
Expand Down
1 change: 0 additions & 1 deletion app/views/alchemy/admin/elements/update.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
$errors.html('<%= j @error_message %><ul><li><%== j @error_messages.join("</li><li>") %></li></ul>');
$errors.show();
$('<%== @element.ingredients_with_errors.map { |ingredient| "[data-ingredient-id=\"#{ingredient.id}\"]" }.join(", ") %>').addClass('validation_failed');
Alchemy.Buttons.enable($el);

<%- end -%>
})();
2 changes: 1 addition & 1 deletion app/views/alchemy/admin/styleguide/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@

<div class="submit">
<button class="secondary">Secondary</button>
<button data-alchemy-button>Primary Submit</button>
<button type="submit" is="alchemy-button">Primary Submit</button>
</div>
</form>
</div>
Expand Down
1 change: 0 additions & 1 deletion app/views/alchemy/base/error_notice.js.erb
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
Alchemy.growl('<%= j @notice %>', 'error');
Alchemy.Buttons.enable();
5 changes: 3 additions & 2 deletions lib/alchemy/forms/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ def datepicker(attribute_name, options = {})
#
def submit(label, options = {})
options = {
wrapper_html: {class: "submit"}
wrapper_html: {class: "submit"},
input_html: {is: "alchemy-button"}
}.update(options)
template.content_tag("div", options.delete(:wrapper_html)) do
template.content_tag("button", label, options.delete(:input_html))
template.button_tag(label, options.delete(:input_html))
end
end
end
Expand Down
64 changes: 64 additions & 0 deletions spec/javascript/alchemy_admin/components/button.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import "alchemy_admin/components/button"
import { renderComponent } from "./component.helper"

describe("alchemy-button", () => {
it("disables button on form submit", () => {
const html = `
<form>
<button type="submit" is="alchemy-button">Save</button>
</form>
`
const button = renderComponent("alchemy-button", html)
const submit = new Event("submit", { bubbles: true })

button.form.dispatchEvent(submit)

expect(button.getAttribute("disabled")).toEqual("disabled")
expect(button.getAttribute("tabindex")).toEqual("-1")
expect(button.classList.contains("disabled")).toBeTruthy()
expect(button.innerHTML).toEqual(
'&nbsp;<alchemy-spinner size="small" color="currentColor"></alchemy-spinner>'
)
})

it("logs warning if no form found", () => {
global.console = {
...console,
warn: jest.fn()
}

const html = `
<button is="alchemy-button">Save</button>
`
const button = renderComponent("alchemy-button", html)

expect(console.warn).toHaveBeenCalledWith(
"No form for button found!",
button
)
})

describe("on remote forms", () => {
it("re-enables button on ajax complete", () => {
const html = `
<form data-remote="true">
<button type="submit" is="alchemy-button">Save</button>
</form>
`
const button = renderComponent("alchemy-button", html)

const submit = new Event("submit", { bubbles: true })
button.form.dispatchEvent(submit)

expect(button.getAttribute("disabled")).toEqual("disabled")

const ajaxComplete = new CustomEvent("ajax:complete", { bubbles: true })
button.form.dispatchEvent(ajaxComplete)

expect(button.getAttribute("disabled")).toBeNull()
expect(button.getAttribute("tabindex")).toBeNull()
expect(button.classList.contains("disabled")).toBeFalsy()
expect(button.innerHTML).toEqual("Save")
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
export const renderComponent = (name, html) => {
document.body.innerHTML = html
return document.querySelector(name)
return document.querySelector(`${name}, [is="${name}"]`)
}

export const setupLanguage = () => {
Expand Down
4 changes: 4 additions & 0 deletions spec/javascript/alchemy_admin/utils/ajax.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ describe("get", () => {
})

it("network errors get rejected", async () => {
global.console = {
...console,
error: jest.fn()
}
xhrMock.get("http://localhost/users", () => {
return Promise.reject(new Error())
})
Expand Down

0 comments on commit a43e3d7

Please sign in to comment.