Skip to content

Commit

Permalink
Merge pull request #3053 from AlchemyCMS/turbo-frames-create-element
Browse files Browse the repository at this point in the history
Use turbo frame and stream to create element
  • Loading branch information
tvdeyen authored Sep 26, 2024
2 parents 4c20717 + dd93836 commit da92ae1
Show file tree
Hide file tree
Showing 15 changed files with 158 additions and 78 deletions.
4 changes: 2 additions & 2 deletions app/controllers/alchemy/admin/elements_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ def create
end
end
if @element.save
render :create
render :create, status: :created
else
@element.page_version = @page_version
@elements = @page.available_element_definitions
load_clipboard_items
render :new
render :new, status: :unprocessable_entity
end
end

Expand Down
2 changes: 1 addition & 1 deletion app/helpers/alchemy/admin/form_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module FormHelper
def alchemy_form_for(object, *args, &block)
options = args.extract_options!
options[:builder] = Alchemy::Forms::Builder
options[:remote] = request.xhr?
options.key?(:remote) || options[:remote] = request.xhr?
options[:html] = {
id: options.delete(:id),
class: ["alchemy", options.delete(:class)].compact.join(" ")
Expand Down
3 changes: 2 additions & 1 deletion app/javascript/alchemy_admin/components/action.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { reloadPreview } from "alchemy_admin/components/preview_window"
import { closeCurrentDialog } from "alchemy_admin/dialog"
import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"

class Action extends HTMLElement {
Expand All @@ -10,7 +11,7 @@ class Action extends HTMLElement {
// add a intermediate closeCurrentDialog - action
// this will be gone, if all dialogs are working with a promise and
// we don't have to implicitly close the dialog
closeCurrentDialog: Alchemy.closeCurrentDialog,
closeCurrentDialog,
reloadPreview,
updateAnchorIcon: IngredientAnchorLink.updateIcon
}
Expand Down
9 changes: 9 additions & 0 deletions app/javascript/alchemy_admin/components/element_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ export class ElementEditor extends HTMLElement {
return
}

// When newly created, focus the element and refresh the preview
if (this.hasAttribute("created")) {
this.focusElement()
this.previewWindow?.refresh().then(() => {
this.focusElementPreview()
})
this.removeAttribute("created")
}

// Init GUI elements
ImageLoader.init(this)
fileEditors(
Expand Down
12 changes: 12 additions & 0 deletions app/javascript/alchemy_admin/dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,19 @@ export class Dialog {

// Initializes the Dialog body
init() {
const turbo_frame = this.dialog_body[0].querySelector("turbo-frame")

Hotkeys(this.dialog_body)

// Re-render dialog body if turbo frame render returned error
if (turbo_frame) {
turbo_frame.addEventListener("turbo:frame-render", (event) => {
if (!event.detail.fetchResponse.ok) {
this.watch_remote_forms()
}
})
}

this.watch_remote_forms()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
(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="add-nestable-element-button" is="alchemy-button" data-turbo="false">
<%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element.to_sym, scope: 'element_names')) %>
</button>
<%= turbo_frame_tag("new_nested_element_#{element.id}") do %>
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)], 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="add-nestable-element-button" is="alchemy-button">
<%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element.to_sym, scope: 'element_names')) %>
</button>
<% end %>
<% end %>
<% else %>
<%= link_to_dialog (nestable_element ? Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element.to_sym, scope: 'element_names')) : Alchemy.t("New Element")),
Expand Down
14 changes: 14 additions & 0 deletions app/views/alchemy/admin/elements/_clipboard_button.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<%= render Alchemy::Admin::ToolbarButton.new(
url: alchemy.admin_clipboard_path(remarkable_type: "elements"),
label: Alchemy.t("Show clipboard"),
icon: :clipboard,
icon_style: clipboard_empty?("elements") ? "line" : "fill",
dialog_options: {
title: Alchemy.t("Clipboard"),
size: "400x305"
},
link_options: {
id: "clipboard_button"
},
if_permitted_to: [:index, :alchemy_admin_clipboard]
) %>
2 changes: 2 additions & 0 deletions app/views/alchemy/admin/elements/_element.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
data-element-name="<%= element.name %>"
class="<%= element.css_classes.join(" ") %>"
<%= element.compact? ? "compact" : nil %>
<%= local_assigns[:created] ? "created" : nil %>
<%= element.fixed? ? "fixed" : nil %>
>
<% unless element.fixed? %>
Expand Down Expand Up @@ -68,6 +69,7 @@
<% if element.nestable_elements.any? %>
<div class="nestable-elements">
<%= content_tag :div,
id: "element_#{element.id}_nested_elements",
class: "nested-elements", data: {
'droppable-elements' => element.nestable_elements.join(' '),
'element-name' => element.name
Expand Down
28 changes: 15 additions & 13 deletions app/views/alchemy/admin/elements/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
<%= Alchemy.t(:no_more_elements_to_add) %>
<% end %>
<%- else -%>
<%= alchemy_form_for [:admin, @element] do |form| %>
<%= form.hidden_field :page_version_id %>
<%= form.input :name,
label: Alchemy.t(:element_of_type),
collection: elements_for_select(@elements),
prompt: Alchemy.t(:select_element),
selected: (@elements.first if @elements.count == 1),
input_html: {is: 'alchemy-select', autofocus: true} %>
<% if @elements.count == 1 %>
<%= form.hidden_field :name, value: @elements.first[:name] %>
<% end %>
<%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
<%= form.submit Alchemy.t(:add) %>
<%= turbo_frame_tag @element do %>
<%= alchemy_form_for [:admin, @element], remote: false do |form| %>
<%= form.hidden_field :page_version_id %>
<%= form.input :name,
label: Alchemy.t(:element_of_type),
collection: elements_for_select(@elements),
prompt: Alchemy.t(:select_element),
selected: (@elements.first if @elements.count == 1),
input_html: {is: 'alchemy-select', autofocus: true} %>
<% if @elements.count == 1 %>
<%= form.hidden_field :name, value: @elements.first[:name] %>
<% end %>
<%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
<%= form.submit Alchemy.t(:add) %>
<%- end -%>
<%- end -%>
<%- end -%>
35 changes: 0 additions & 35 deletions app/views/alchemy/admin/elements/create.js.erb

This file was deleted.

34 changes: 34 additions & 0 deletions app/views/alchemy/admin/elements/create.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<% opts = {
partial: "alchemy/admin/elements/element",
locals: {
element: Alchemy::ElementEditor.new(@element),
created: true
}
} %>
<% if @element.fixed? %>
<% target = "fixed_element_#{@element.id}" %>
<% elsif @element.parent_element %>
<% target = "element_#{@element.parent_element_id}_nested_elements" %>
<% else %>
<% target = "main-content-elements" %>
<% end %>
<%- if @cut_element_id -%>
<%= turbo_stream.remove "element_#{@cut_element_id}" %>
<% end %>
<% if @insert_at_top %>
<%= turbo_stream.prepend target, **opts %>
<% else %>
<%= turbo_stream.append target, **opts %>
<% end %>
<%= turbo_stream.replace "clipboard_button",
partial: "alchemy/admin/elements/clipboard_button" %>

<alchemy-growl>
<%= Alchemy.t(:successfully_added_element) %>
</alchemy-growl>

<alchemy-action name="closeCurrentDialog"></alchemy-action>
17 changes: 2 additions & 15 deletions app/views/alchemy/admin/elements/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,7 @@
},
if_permitted_to: [:create, Alchemy::Element]
) %>
<%= render Alchemy::Admin::ToolbarButton.new(
url: alchemy.admin_clipboard_path(remarkable_type: "elements"),
label: Alchemy.t("Show clipboard"),
icon: :clipboard,
icon_style: clipboard_empty?("elements") ? "line" : "fill",
dialog_options: {
title: Alchemy.t("Clipboard"),
size: "400x305"
},
link_options: {
id: "clipboard_button"
},
if_permitted_to: [:index, :alchemy_admin_clipboard]
) %>
<%= render "alchemy/admin/elements/clipboard_button" %>
<sl-tooltip content="<%= Alchemy.t("Collapse all elements") %>" placement="top-end" class="right">
<button id="collapse-all-elements-button" class="icon_button">
<alchemy-icon name="contract-up-down"></alchemy-icon>
Expand Down Expand Up @@ -53,7 +40,7 @@
<%= render @elements.map { |element| Alchemy::ElementEditor.new(element) } %>
</sl-tab-panel>
<% @fixed_elements.each do |element| %>
<sl-tab-panel name="fixed-element-<%= element.id %>" style="--padding: 0" class="scrollable-elements">
<sl-tab-panel id="fixed_element_<%= element.id %>" name="fixed-element-<%= element.id %>" style="--padding: 0" class="scrollable-elements">
<%= render Alchemy::ElementEditor.new(element) %>
</sl-tab-panel>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/alchemy/admin/elements_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ module Alchemy

it "should create an element from clipboard" do
post :create, params: {paste_from_clipboard: element_in_clipboard.id, element: {page_version_id: page_version.id}}, xhr: true
expect(response.status).to eq(200)
expect(response.status).to eq(201)
expect(response.body).to match(/Successfully added new element/)
end

Expand Down
46 changes: 46 additions & 0 deletions spec/helpers/alchemy/admin/form_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require "rails_helper"

RSpec.describe Alchemy::Admin::FormHelper do
describe "#alchemy_form_for" do
subject { helper.alchemy_form_for(resource) {} }

let(:resource) do
[alchemy, :admin, Alchemy::Element.new(name: "article")]
end

it "returns a form with alchemy class" do
expect(subject).to have_css(".alchemy")
end

context "if options[:remote] is given" do
context "and set to true" do
subject { helper.alchemy_form_for(resource, remote: true) {} }

it "makes the form remote" do
expect(subject).to have_css("form[data-remote]")
end
end

context "and set to false" do
subject { helper.alchemy_form_for(resource, remote: false) {} }

it "makes the form non-remote" do
expect(subject).to have_css("form")
expect(subject).to_not have_css("form[data-remote]")
end
end
end

context "if options[:remote] is not given" do
context "and request is xhr" do
before do
allow(helper).to receive(:request).and_return(double(xhr?: true))
end

it "makes the form remote" do
expect(subject).to have_css("form[data-remote]")
end
end
end
end
end
11 changes: 9 additions & 2 deletions spec/javascript/alchemy_admin/components/action.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import "alchemy_admin/components/action"
import { renderComponent } from "./component.helper"
import { closeCurrentDialog } from "alchemy_admin/dialog"
import * as PreviewWindow from "alchemy_admin/components/preview_window"
import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"

jest.mock("alchemy_admin/dialog", () => {
return {
__esModule: true,
closeCurrentDialog: jest.fn()
}
})

jest.mock("alchemy_admin/components/preview_window", () => {
return {
__esModule: true,
Expand Down Expand Up @@ -39,11 +47,10 @@ describe("alchemy-action", () => {
})

it("call closeCurrentDialog function ", () => {
window.Alchemy.closeCurrentDialog = jest.fn()
renderComponent(
"alchemy-action",
`<alchemy-action name="closeCurrentDialog"></alchemy-action>`
)
expect(window.Alchemy.closeCurrentDialog).toBeCalled()
expect(closeCurrentDialog).toBeCalled()
})
})

0 comments on commit da92ae1

Please sign in to comment.