ruby-on-rails 是否触发来自刺激控制器的Flash消息?

ecr0jaav  于 2023-04-22  发布在  Ruby
关注(0)|答案(1)|浏览(126)

我正在尝试使用rails 7、stimulus和navigator.share实现共享功能,但并非所有浏览器都支持该功能。对于这些情况,我需要将链接复制到剪贴板,但显示flash消息。我的flash消息有问题。解决此问题的正确方法是什么?是否有可能这样做?

分享按钮

<div data-controller="share" 
     data-share-target="trigger" 
     data-share-url-value="<%= post_url(@post) %>"  
     data-share-name-value="<%= @post.name %>">
  <%= button_to "", data: { action: "click->share#share" } do %>
    <%= inline_svg_tag "svg/share.svg", class: 'h-6 w-6' %>
  <% end %>
</div>

刺激控制器

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

export default class extends Controller {
  static targets = ["trigger"];
  static values = ["name"];

  connect() {}

  async share(e) {
    e.preventDefault();

    const shareData = {
      name: this.nameValue,
      url: this.data.get("urlValue"),
      text: "some cool text",
      title: "title here",
    };

    try {
      if (navigator.share) {
        await navigator.share(shareData);
      } else {
        navigator.clipboard.writeText(this.data.get("urlValue"));

        // Trigger Flash message here
      }
    } catch (err) {
      console.log(err);
    }
  }
}

我的闪光灯
app/views/layouts/application.html.erb

<body class="min-h-full flex flex-col h-full">
    <main class="flex-1">
      <div id="flash" class="w-full">
        <% flash.each do |key, value| %>
          <div>
            <p><%= value %></p>
          </div>
          <% end %>
      </div>
    
      <%= yield %>
    </main>
  </body>

这是可能的吗?如果不可能,你会如何实施?我也希望有任何关于实施的反馈。

6ie5vjzr

6ie5vjzr1#

有几种方法可以解决这个问题,最简单的方法是在同一个Share控制器中添加动态注入消息的功能。
下面是一个完整的代码示例。

1. HTML
  • 这建立在您的基本模板和特定于控制器的HTML的基础上,并进行了一些更改。
  • 首先,我们在#flash元素中添加一个template元素,以容纳要为每条消息复制的客户端HTML内容。
  • 我们给予这个模板一个数据属性(可以是任何东西)沿着包含消息文本的实际内容。
  • 对于受控元素,不需要data-share-target="trigger",因为您可以始终使用this.element访问受控元素,但看起来您无论如何都没有使用它。
  • 对于您的button,请记住添加type="button"避免了使用event.preventDefault的需要,并且它可以更好地实现可访问性。
  • 如果你有一个按钮只是一个图标,请记住总是为你的按钮包括一个可访问的标签。我已经添加了aria-label,但你也可以添加一个跨度sr-only (looks like you are using Tailwind)
<body>
  <div id="flash" class="w-full" aria-live="polite">
    <!-- ...flash.each do etc  -->
    <template data-template>
      <div><p data-value></p></div>
    </template>
  </div>
  <section>
    <h4>Share stuff</h4>
    <div
      data-controller="share"
      data-share-name-value="some-name"
      data-share-url-value="path/to/post/"
    >
      <button type="button" data-action="share#share" aria-label="share">
        Share
      </button>
    </div>
  </section>
</body>
2.控制器(JS)
  • 看起来你声明static values的方式是不正确的,这应该是一个对象语法,这样你就可以声明每个值的类型。
  • 在控制器代码的基础上,我们可以添加一个新的方法addMessage,它将找到id为flash的元素,然后在里面找到模板元素。它将克隆该元素,然后找到放置消息内容的位置。然后它将把这个节点追加到flash容器中。
  • 可选:在消息上包含超时,并在您知道用户有时间查看它时将其删除。
  • 有了这个addMessage方法后,如果需要向用户显示“剪贴板已更新”消息和任何错误,就可以使用这个方法来显示它们。
  • 最后,async/await感觉像是新的,应该使用,但我认为在这种情况下,它使代码的可读性降低。在promise上使用一个简单的then回调就足够了。
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static values = {
    messageTimeout: { default: 2.5 * 1000, type: Number },
    name: String,
    url: String,
  };

  addMessage(content, { type = "error" } = {}) {
    const flashContainer = document.getElementById("flash");

    if (!flashContainer) return;

    const template = flashContainer.querySelector("[data-template]");
    const node = template.content.firstElementChild.cloneNode(true);
    node.querySelector("[data-value]").innerText = content.message;

    flashContainer.append(node);

    // optional - add timeout to remove after 2.5 seconds
    window.setTimeout(() => {
      node.remove();
    }, this.messageTimeoutValue);
  }

  share() {
    const name = this.nameValue;
    const url = this.urlValue;

    const shareData = {
      name,
      text: "some cool text",
      title: "title here",
      url,
    };

    try {
      if (navigator.share) {
        navigator.share(shareData).catch((error) => this.handleError(error));
      } else {
        navigator.clipboard.writeText(url);
        this.addMessage({ message: "copied to clipboard" }, { type: 'info' });
      }
    } catch (error) {
      this.handleError(error);
    }
  }
}
替代方式

另一种方法是将flash消息行为拆分到一个新的控制器中。如果您希望其他代码需要动态创建消息,这将非常有用。
这里有一个参考实现,您可以使用它。
https://github.com/wagtail/wagtail/blob/main/client/src/controllers/MessagesController.ts(Wagtail,一个基于Django的CMS,有一个类似的全局消息用例)。
你也可以使用一个刺激的方法来处理'remove after a delay'部分,这里有一个'吐司'样式的删除的参考实现。
https://github.com/stimulus-components/stimulus-notification/blob/master/src/index.ts
最好从简单的开始,只有在需要的时候才添加这个单独的控制器行为。

相关问题