6

I am doing an inline editing feature on the show page with the 'Edit' button above the frame that loads with turbo. Trying to add a toggle on the edit button that it switches to the cancel button in edit mode.

Figured a simple stimulus controller would do the trick except when you add the data-action="click->edit-button#toggle" that overides the turbo action on the button. Thoughts? Ideas?

<div turbo-controller="edit-button">
  <a class="btn btn-sm btn-primary btn-outline" data-turbo-frame="project" data-edit-button-target="button" data-value="show" href="/foobar/234/edit">Edit</a>
  <turbo-frame id="project">
    content
  </turbo-frame>
</div>
Roland Studer
  • 4,315
  • 3
  • 27
  • 43
Dan Tappin
  • 2,692
  • 3
  • 37
  • 77

2 Answers2

0

The simplest solution would be to have a partial with the frame that renders either the edit link and content OR cancel link and edit form, depending on a local var setting. For example:

<turbo-frame id="project">
  <% if local_assigns[:editing] == true %>
    <a href="/foobar/123/cancel">Cancel</a>
    editing form....
  <% else %>
    <a href="/foobar/234/edit">Edit</a>
    content
  <% end %>
</turbo-frame>

This will by default render the content and edit link. In the edit request you just need to render the partial with editing true. Ex:

def edit
  foobar = Foobar.find(params[:id])
  render partial: 'foobar_editor', locals: { foobar: foobar, editing: true }
end

def cancel
  foobar = Foobar.find(params[:id])
  render partial: 'foobar_editor', locals: { foobar: foobar, editing: false }
end
riley
  • 2,387
  • 1
  • 25
  • 31
0

add the action to the target frame? – Dan Tappin

Let me just steal that idea:

<div data-controller="edit-button">
  <a
    href="/project/1/edit"
    data-turbo-frame="project_1"
    data-edit-button-target="toggle"
  >
    Edit
  </a>

  <turbo-frame
    id="project_1"
    data-action="turbo:frame-load->edit-button#toggle"
  >
    content
  </turbo-frame>
</div>
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["toggle"];

  toggle({ target: frame, params: { toggleUrl, toggleText } }) {
    frame.dataset.editButtonToggleTextParam = this.toggleTarget.textContent;
    this.toggleTarget.textContent = toggleText || "Cancel";

    frame.dataset.editButtonToggleUrlParam = this.toggleTarget.href;
    this.toggleTarget.href = toggleUrl || window.location.href;
  }
}

https://stimulus.hotwired.dev/reference/actions#action-parameters


Initially, the only issue that I had was when changing the link href attribute before the actual frame navigation. Just having the data-action didn't stop the frame from navigating. But if that's still an issue, here is another way to do it, it also simplifies the html part:

<a
  href="/project/1/edit"
  data-turbo-frame="project_1"
  data-controller="edit-button"
  data-action="edit-button#toggle:prevent"
>
  Edit
</a>

<turbo-frame id="project_1">
  content
</turbo-frame>
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  // `target` is the link so no need for explicit Target definition
  toggle({ target, params: { toggleUrl, toggleText } }) {
    // but the frame, we need to get manually
    const frame = document.getElementById(target.dataset.turboFrame);
    // navigate the frame before changing link's href
    frame.src = target.href;

    target.dataset.editButtonToggleTextParam = target.textContent;
    target.textContent = toggleText || "Cancel";

    target.dataset.editButtonToggleUrlParam = target.href;
    target.href = toggleUrl || window.location.href;
  }
}

It works, but I don't know if it's a supported use case, which I found here: https://github.com/hotwired/turbo/blob/v7.2.5/src/core/frames/frame_controller.ts#L374

and then in the docs for loading attribute:
https://turbo.hotwired.dev/reference/frames#html-attributes

changes to the src attribute will immediately navigate the element

I can only assume it's ok to change src.

Alex
  • 16,409
  • 6
  • 40
  • 56