来自外部控制器的火灾事件

bar*_*one 3 stimulusjs esbuild rails7

我正在迁移到 Rails 7,我觉得有很多变化,但我有信心理解它们并能够升级我自己创建的用于保存个人记录和约会的个人应用程序

更具体地说,我需要在 flatpickr 控制器和 fullcalendar 控制器之间的控制器(@hotwire/stimulus)之间进行通信。这个想法是在从 flatpicr 中选择时跳转到日期

我已经尝试了很多不同的选择,但我真的陷入困境..欢迎任何帮助:)

导轨 7.0.3.1

索引.html.erb

<div data-controller="flatpickr" name="" data-action=""></div>
<div data-controller="calendar">
 <div data-calendar-target="window"></div>
 <turbo-frame id="popup" data-calendar-target="popup"></turbo-frame>
</div>
Run Code Online (Sandbox Code Playgroud)

flatpickr_controller.js

import Flatpickr from 'stimulus-flatpickr'

export default class extends Flatpickr {

  connect() {

    this.config = {

      inline: true,
      enableTime: false,
      time_24hr: false,

      onChange: function(selectedDates, dateStr, instance) {          

        const calendarController = this.application.getControllerForElementAndIdentifier(this.calendarTarget, "calendar")
        calendarController.gotoDate('18-01-2025') //random date

      },
    };
    super.connect();
  }
}
Run Code Online (Sandbox Code Playgroud)

日历控制器.js

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

import { Calendar } from '@fullcalendar/core';
import resourceTimeGridPlugin from '@fullcalendar/resource-timegrid';
import interactionPlugin from '@fullcalendar/interaction';


export default class extends Controller {
  static targets = [ "popup", "window" ];

  connect() {
    let overlay = this.popupTarget;

    this.calendar = new Calendar(this.windowTarget, {

        plugins: [ resourceTimeGridPlugin, interactionPlugin ],

        themeSystem: 'bootstrap5',
        initialView: 'resourceTimeGridDay',

        aspectRatio:  1.8,
        nowIndicator: true,
        
        selectable: true,
        editable: true,
      
        allDaySlot: false,

   });

    window.addEventListener('load', () => {
      this.calendar.render();
    });
  }

  refresh(e) {
    if (e.detail.success) {
      this.calendar.refetchEvents();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

输出

application-7082a89999639e6d01ae0ef0aaaf6707b39fab96541f1dcd1c79da24753cb0ed.js:28271 Uncaught TypeError: Cannot read properties of undefined (reading 'getControllerForElementAndIdentifier')
    at Object.onChange (ap ...
Run Code Online (Sandbox Code Playgroud)

我想我会对此生气...谢谢!

LB *_*ton 6

尝试理解所有这些做得很好,但学习新东西可能很困难,特别是当您有“工作”代码并且您被迫进行更改时。

可以提供帮助的一件事是重新访问 Stimulus 文档,它确实提供了这些问题所需的几乎所有答案,但可能需要重新阅读一下。

另一件令人超级沮丧的事情是 JavaScript 的使用this及其工作原理。

希望下面的细分有所帮助。

问题

1.理解this(JavaScript)

上面代码的第一个问题是您引用this时假设它引用您的控制器实例,但它引用的是事件的上下文。

onChange: function(selectedDates, dateStr, instance) {          
  const calendarController = this.application.getControllerForElementAndIdentifier(this.calendarTarget, "calendar")
  calendarController.gotoDate('18-01-2025') //random date
},
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,this.application并且this.calendarTarget永远不会工作,因为this这里是由处理程序调用上下文创建的上下文onChange

解决这个问题的快速方法this是使用箭头函数。在下面修改后的代码片段中(由于下面的问题 2 和 3,它仍然不起作用),使用箭头函数方法而不是声明function,它从父上下文中拉入this,这将是控制器的实例。

onChange: (selectedDates, dateStr, instance) => {          
  const calendarController = this.application.getControllerForElementAndIdentifier(this.calendarTarget, "calendar")
  calendarController.gotoDate('18-01-2025') //random date
},
Run Code Online (Sandbox Code Playgroud)

然而,最好的方法是完整阅读 Mozilla 文档https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this ,再读一遍,然后可能是第三个时间。之后,找到一些 YouTube 视频并观看。如果你真正理解这个概念,你会发现 JavaScript 开发会容易得多,但它很难理解。

2. 了解刺激目标

下一个问题是您在 flatpackr 控制器中的使用this.calendarTarget,由于设置不正确,该控制器将没有任何可用目标。

在 Stimulus 文档 - https://stimulus.hotwired.dev/reference/targets中,您可以读到目标必须在控制器的范围内。但在下面的 HTML 中,data-controller="flatpickr"div 没有子级,并且在 HTML 中任何可以由该控制器访问的地方也没有目标。

<div data-controller="flatpickr" name="" data-action="">No Children here?</div>
<div data-controller="calendar">
 <div data-calendar-target="window"></div>
 <turbo-frame id="popup" data-calendar-target="popup"></turbo-frame>
</div>
Run Code Online (Sandbox Code Playgroud)

有几种方法可以访问控制器范围之外的内容,但最简单的方法是完全绕过此问题并使用 Stimulus 的首选方式与其他控制器进行通信。

但是,如果您想使用目标,您需要做两件事。

  • A. 确保在控制器上声明了目标静态属性。
export default class extends Flatpickr {
  static targets = [ "calendar" ]; // this is required
Run Code Online (Sandbox Code Playgroud)
  • B. 确保目标元素具有正确的属性并且是所需控制器的子元素。
<div data-controller="flatpickr" name="" data-action="">
  <div data-controller="calendar" data-flatpickr-target="calendar">
    <div data-calendar-target="window"></div>
    <turbo-frame id="popup" data-calendar-target="popup"></turbo-frame>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

3. 刺激跨控制器与事件的协调

getControllerForElementAndIdentifier最后,如果没有其他方式与另一个控制器进行通信,您的使用将被记录为解决方法。

首选方法是使用事件,它非常强大、灵活,可能会解决 99.9% 的用例。请阅读https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEventhttps://developer.mozilla.org/en-US/docs/Web/API/CustomEvent如果您不确定首先发生的浏览器事件是什么。

然后,您可以从flatpackr控制器分派一个事件以供calendar控制器接收。

最简单的方法是在您的第一个控制器上调度一个事件,并在 DOM 中“冒泡”,然后您的日历控制器全局监听该事件。

解决方案 - 示例代码

  • 首先,从 HTML 开始,下面唯一真正的更改是data-action日历上的属性。
  • 这将侦听全局事件flatpackr:changed,当它看到该事件时,它将调用您的日历控制器的方法goToDate
<div data-controller="flatpickr"></div>
<div data-controller="calendar" data-action="flatpackr:changed@window->calendar#goToDate">
 <div data-calendar-target="window"></div>
 <turbo-frame id="popup" data-calendar-target="popup"></turbo-frame>
</div>
Run Code Online (Sandbox Code Playgroud)
  • 在您的 flatpackr 控制器中,使用上述箭头函数方法,您可以调用this.dispatch()它将触发CustomEvent带有提供的选项的 a 的调度。
  • Stimulus 将以控制器名称作为名称的前缀。
  • 注意:您可以更具体地说明此事件被分派到的位置,但如果只有一个日历实例,那么现在无需担心。
import Flatpickr from 'stimulus-flatpickr'

export default class extends Flatpickr {

  connect() {
    this.config = {
      inline: true,
      enableTime: false,
      time_24hr: false,
      onChange: (selectedDates, dateStr, instance) => {   
        // note: Stimulus sets `bubbles` to true by default but good to be explicit 
        const someDate = '18-01-2025'; // random date
        // passing data to the other controller can be via the `detail` object in the CustomEvent & Stimulus will automatically add 'flatpackr:' to the start of the event name for you (Thanks Stimulus!)
        this.dispatch('changed', { detail: { date: someDate } , bubbles: true } );
      },
    };
    // super.connect(); - not sure that you need this in most cases so commented out
  }
}
Run Code Online (Sandbox Code Playgroud)
  • 在日历控制器中,所需要的只是要声明的方法goToDate
  • event.detail您可以从参数中读取提供的详细信息。
import { Controller } from "@hotwired/stimulus";

import { Calendar } from '@fullcalendar/core';
import resourceTimeGridPlugin from '@fullcalendar/resource-timegrid';
import interactionPlugin from '@fullcalendar/interaction';


export default class extends Controller {
  static targets = [ "popup", "window" ];

  connect() {
    // ...
    // note: you may not need the window on load listener as `connect` will only be called when there is a DOM ready to attach to.
  }

  refresh(e) {
    // ...
  }

  goToDate(event) {
    // note: you can use destructuring above and change the signature to `  goToDate({ detail: { date } }) {` instead
    const date = event.detail.date;
    console.log('do something with the date now', date);
  }
}
Run Code Online (Sandbox Code Playgroud)

注意:我没有在本地测试过,但应该足够接近