themesberg / flowbite

Open-source UI component library and front-end development framework based on Tailwind CSS
https://flowbite.com
MIT License
7.89k stars 742 forks source link

Rails Turbo Frame Modal #989

Closed blastedcode closed 5 days ago

blastedcode commented 6 days ago

Describe the bug I am using Flowbite with Ruby on Rails. I want to have a form in a modal. Using the standard button I can open a modal and close it as expected.

When I load the content into the modal, which is a form with a single field and button. The Modal pops up with the darker background and as the form is loaded in the background is removed. Additionally if click on the close button the first click brings back the background and keeps the modal open. Clicking a second time closes the modal

To Reproduce Steps to reproduce the behavior:

  1. Click on link to open up modal with content from Turbo Frames
  2. Watch Modal open with Content
  3. Confirm background is removed
  4. Click on close button
  5. Confirm Modal does not close but background is now set
  6. Click on close button again
  7. Confirm modal closes

Expected behavior I expect the modal to open with the background greyed out and stay grey. Also when the close button is clicked the modal closes on the first click

Screenshots Video Example

Desktop (please complete the following information):

Code being Used

Modal

<div id="updateProductModal" tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-modal md:h-full">
  <div class="relative p-4 w-full max-w-2xl h-full md:h-auto">
    <!-- Modal content -->
    <div class="relative p-4 bg-white rounded-lg shadow dark:bg-gray-800 sm:p-5">
      <!-- Modal header -->
      <div class="flex justify-between items-center pb-4 mb-4 rounded-t border-b sm:mb-5 dark:border-gray-600">
        <h3 class="text-lg font-semibold text-gray-900 dark:text-white">
          Update Product
        </h3>
        <button type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-toggle="updateProductModal">
          <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
            <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 1 011.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
          </svg>
          <span class="sr-only">Close modal</span>
        </button>
      </div>
      <!-- Modal body -->
      <div id="modal-content">
        <%= turbo_frame_tag "modal" do %>
          <div id="modal-form-content">
            <!-- The form content will be dynamically loaded here by Turbo -->
          </div>
        <% end %>
      </div>
    </div>
  </div>
</div>

Button that is calling the modal with Turbo Frames

<%= link_to "Add Education", new_team_member_qualification_path(team_member),
                    data: {
                      turbo_frame: "modal",
                      turbo_prefetch: "false",
                      modal_target: "updateProductModal",
                      modal_toggle: "updateProductModal",
                    },
                    class: "block text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800" %>

The controller that sits behind new_team_member_qualification_path

def new
    @qualification = @team_member.qualifications.build
    # render partial: "form", locals: { qualification: @qualification }
    render turbo_stream: turbo_stream.update("modal-form-content", partial: "qualifications/form", locals: { qualification: @qualification, profile: @profile })
  end

Button that opens the modal correctly that does nto load content in from Turbo

<a id="updateProductButton" data-modal-target="updateProductModal" data-modal-toggle="updateProductModal" class="block text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800" type="button">
          Add Experience
        </a>
blastedcode commented 5 days ago

I have managed to get it to work by using the following code. Interestingly without the 1 millisecond delay and calling modal.show() straight away the background is never greyed out. I have left the console logging in the javascript but its not needed.

I am going to close as its only a 1ms - 5ms delay.

Here is a video of it working:

https://streamable.com/jhui74

modal_trigger_controller.js

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
    static values = {
        modalId: String
    }

    connect() {
        console.log("Modal Trigger Controller connected");
        this.setupFrameListener();
    }

    setupFrameListener() {
        // Attach an event listener for the turbo frame load event
        document.addEventListener("turbo:frame-load", (event) => {
            // Check if the loaded frame is the one we are interested in
            if (event.target.id === "modal-content-frame") {
                console.log("Modal content frame loaded");
                this.showModal();
            }
        });
    }

    // Handle the click event to show the modal
    showModal() {
        const modalId = this.modalIdValue;
        if (modalId) {
            setTimeout(function() {
                console.log("Showing modal with ID: " + modalId);
                const modal = FlowbiteInstances.getInstance('Modal', modalId);
                modal.show();
            }, 1);
        }
    }
}

Controller

def new
    @qualification = @team_member.qualifications.build

    respond_to do |format|
      format.turbo_stream do
        render turbo_stream: turbo_stream.replace("modal-content-frame", partial: "qualifications/form", locals: { qualification: @qualification, team_member: @team_member })
      end
      format.html do
        render partial: "qualifications/form", locals: { qualification: @qualification, team_member: @team_member }
      end
    end
  end

Turbo Form Tempalte

<%= turbo_frame_tag "modal-content-frame" do %>
  <%= form_with(model: [@team_member, qualification], local: false, data: { turbo_frame: "modal-content-frame" }) do |form| %>
    <div>
      <%= form.label :title %>
      <%= form.text_field :title %>
    </div>

    <div>
      <%= form.submit %>
    </div>
  <% end %>
<% end %>

Template with modal


<%= link_to "Add Education", new_team_member_qualification_path(team_member),
                    data: {
                      turbo_frame: "modal-content-frame",
                      turbo_prefetch: "false",
                      controller: "modal-trigger",
                      modal_trigger_modal_id_value: "updateProductModal",
                    },
                    class: "block text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800" %>

<div id="updateProductModal" tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-modal md:h-full">
  <div class="relative p-4 w-full max-w-2xl h-full md:h-auto">
    <!-- Modal content -->
    <div class="relative p-4 bg-white rounded-lg shadow dark:bg-gray-800 sm:p-5">
      <!-- Modal header -->
      <div class="flex justify-between items-center pb-4 mb-4 rounded-t border-b sm:mb-5 dark:border-gray-600">
        <h3 class="text-lg font-semibold text-gray-900 dark:text-white">
          Update Product
        </h3>
        <button type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-toggle="updateProductModal">
          <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
            <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 1 011.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
          </svg>
          <span class="sr-only">Close modal</span>
        </button>
      </div>
      <!-- Modal body -->
      <div id="modal-content">
        <!-- Turbo Frame to dynamically load form content -->
        <%= turbo_frame_tag "modal-content-frame" do %>
          <!-- Placeholder or initial content -->
          Loading form...
        <% end %>
      </div>
    </div>
  </div>
</div>