在 Stimulus 控制器中记忆 Bootstrap5 模态

Memoize a Bootstrap5 modal inside a Stimulus controller

我在部分定义的模态中有一个表单:

<!-- app/views/events/new_event_modal.html.erb -->
<div data-controller="events--form">  
  <div class="modal fade" id="new-event-modal" tabindex="-1" aria-labelledby="new-event-label" aria-hidden="true" data-events--form-target="modal">
    <div class="modal-dialog modal-lg">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title text-light" id="new-event-label">Ajouter un évennement</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
        </div>
        <div class="modal-body">
          <%= render partial: 'events/form',
                     locals: { event:     event,
                               calendars: calendars,
                               users:     users } %>
        </div>
      </div>
    </div>
  </div>
</div>


<!-- app/views/events/_form.html.erb -->
<%= turbo_frame_tag dom_id(event) do %>
  <div class="container text-light">
    <%= form_with(model: event || Event.new) do |form| %>
      
      <!-- form's content omitted -->

      <div class="actions modal-footer">
        <%= form.submit class: 'btn btn-primary' %>
      </div>
    <% end %>
  </div>
<% end %>

因为它是一个日历应用程序,所以我用用户选择的日期预先填写表格,然后显示模态。这是通过一些“+”图标和 Stimulus 控制器完成的:

# app/views/simple_calendar/_mounth_calendar.html.erb
# each day has this icon
<%= image_tag 'add.svg',
  size: '20x20',
  alt:  '+',
  data: {
    action: 'click->events--form#configure_and_show_modal',
    date:   day.strftime('%d/%m/%Y')
  }
%>
// app/javascript/packs/controllers/events/form_controller.js
import { Controller } from "@hotwired/stimulus"

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

  connect() {
    this.element.addEventListener('turbo:submit-end', (event) => {
      if (event.detail.success) {
        this.hide_modal();
      }
    });
  }

  configure_and_show_modal(event) {
    const date = event.target.dataset.date;

    this.configure_flatpickr(date);
    this.show_modal();
  }

  configure_flatpickr(date) {
    flatpickr("[data-behavior='flatpickr']", {
      altInput:      true,
      altFormat:     'd F Y',
      altInputClass: 'form-control input text-dark',
      dateFormat:    'd/m/Y H:i',
      defaultDate:   date,
      mode:          'range',
    })
  }

  show_modal() {
    this.modal.show();
  }

  hide_modal() {
    this.modal.hide();
  }

  get modal() {
    return new bootstrap.Modal(this.modalTarget);
  }
}

问题是get modal()每次都实例化一个新对象,因此调用hide_modal()会在新创建的对象上调用.hide()并且不会隐藏显示的模态。

我知道我需要某种记忆,但我不知道如何在 Stimulus 控制器上实现它。

使用 ruby 我们会做类似的事情:

@modal ||= get_modal

谁能指导我这个方向?

实现此目标的一种简单方法是将模态实例存储在 class 实例上。

这样您仍然可以从您的 getter 访问它,但不必每次都重新创建它。

_modalInstance只是一个约定,下划线表示应该是private ish。但是,如果控制器以某种方式断开连接并且必须重新连接已显示的模态,这可能会导致问题。


import { Controller } from "@hotwired/stimulus"

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

  get modal() {
    if (this._modalInstance) return this._modalInstance;
    const modal = new bootstrap.Modal(this.modalTarget);
    this._modalInstance = modal;
    return this._modalInstance;
  }
}

实现此目的的更好方法是使用 Bootstrap JavaScript API.

bootstrap.Modal.getOrCreateInstance(myModalEl)

https://getbootstrap.com/docs/5.1/components/modal/#getorcreateinstance


import { Controller } from "@hotwired/stimulus"

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

  get modal() {
    return bootstrap.Modal.getOrCreateInstance(this.modalTarget);
  }
}

以下是我实现记忆的方式:

get modal() {
  if (this._modal == undefined) {
    this._modal = new bootstrap.Modal(this.modalTarget);
  }
  return this._modal
}