GJS组件在通过模态服务显示时出现“未定义的帮助器”错误(Discourse 3.5.0)

在尝试使用 this.modal.show() 将 Glimmer 组件 (.gjs) 显示为模态框时,遇到了持续的渲染错误。该模态框是从通过 post-menu-buttons 值转换器添加到帖子菜单的另一个 GJS 组件触发的。我正在运行 Discourse v3.5.0.beta3-dev

我正在尝试使用 api.registerValueTransformer("post-menu-buttons", ...) 向帖子菜单添加一个按钮。点击此按钮应使用 this.modal.show(FeedbackFormModal, ...) 打开一个由单独的 GJS 组件 (FeedbackFormModal) 定义的模态框。

当点击按钮并调用 this.modal.show() 时,应用程序会崩溃,并出现以下错误,似乎是在渲染 FeedbackFormModal 过程中发生的:

Error occurred:

- While rendering:
  -top-level
    application
      (unknown template-only component)
        DiscourseRoot
          ModalContainer
            FeedbackFormModal
              DModal
                conditional-in-element:ConditionalInElement
                  (unknown template-only component) index.js:3970:18

Error occurred: index.js:3377:16

Uncaught (in promise) Error: Attempted to use a value as a helper, but it was not an object or function. Helper definitions must be objects or functions with an associated helper manager. The value was: undefined
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:89256
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:90066
    Ember 4
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:90164
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:89224
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:90163
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:90284
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:92117
    Ember 12
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:94577
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:96288
    Ember 34
    show modal.js:73
    openFeedbackModal leave-feedback-button.js:102 // Line number might differ slightly
    _triggerAction d-button.gjs:138
    Ember 10

即使 FeedbackFormModal 模板被精简到只包含导入的核心组件(<DModal><DButton>)和标准的内置助手(ifon 等),也会发生这种情况。

以下是供参考的代码:

plugin.rb

# frozen_string_literal: true
# name: my-plugin

module ::MyPlugin
  # ... constants ...
end

require_relative "lib/my_plugin/engine"

after_initialize do
  # Load Dependencies (using require_dependency and File.expand_path)
  require_dependency File.expand_path("app/controllers/my_plugin/my_controller.rb", __dir__)
  require_dependency File.expand_path("app/models/my_plugin/my_model.rb", __dir__)
  # ... other dependencies ...

  # Add methods to User (using class_eval as prepend failed)
  ::User.class_eval do
    # Define helper methods like my_custom_stat, etc.
    def my_custom_stat; # ... implementation ...; end
    public :my_custom_stat
    # ... other methods ...
  end

  # Prepend Guardian Extensions (if any)
  # ::Guardian.prepend(MyPlugin::GuardianExtensions)

  # Serializer modifications
  reloadable_patch do |plugin|
    # Add attributes to serializers, e.g.:
    add_to_serializer(:post, :some_flag_for_button) do
        # Logic to determine if button should show
        true # Example
    end
    # ... other serializer additions ...
  end
end

assets/javascripts/discourse/initializers/my-plugin-outlets.js

import { apiInitializer } from "discourse/lib/api";
import { hbs } from "ember-cli-htmlbars";
import LeaveFeedbackButton from "../components/leave-feedback-button"; // Button component
// ... import other components for other outlets ...

export default apiInitializer("1.13.0", (api) => {
  // Use Value Transformer for Post Menu Button
  api.registerValueTransformer("post-menu-buttons", ({ value: dag, context }) => {
    const { post } = context;
    // Logic to determine if button should render based on post.some_flag_for_button
    const shouldRenderButton = post?.some_flag_for_button; // Example flag

    if (shouldRenderButton) {
      dag.add("leaveMyPluginFeedback", LeaveFeedbackButton, {
        after: "like",
        args: { post: post },
      });
    }
    return dag;
  });

  // ... renderInOutlet for other UI elements ...
});

按钮组件 (assets/javascripts/discourse/components/leave-feedback-button.gjs)

import Component from "@glimmer/component";
import { action } from "@ember/object";
import { service } from "@ember/service";
import DButton from "discourse/components/d-button";
import FeedbackFormModal from "./feedback-form-modal"; // The modal component

export default class LeaveFeedbackButton extends Component {
  @service modal;
  @service appEvents; // Used for error display

  // Args: post

  get buttonLabel() { return "Action Button"; } // Hardcoded
  get buttonTitle() { return "Perform Action"; } // Hardcoded

  @action
  openFeedbackModal() {
    console.log("Opening Modal...");
    try {
       // Simplified model for testing
       const modelData = { post_id: this.args.post.id };
       this.modal.show(FeedbackFormModal, { model: modelData });
    } catch(e) {
        console.error("Error showing modal", e);
        this.appEvents.trigger("show:error", "Error opening modal.");
    }
  }

  <template>
    <DButton
      class="btn-default my-plugin-btn"
      @action={{this.openFeedbackModal}}
      @icon="star" {{!-- Example icon --}}
      @label={{this.buttonLabel}}
      title={{this.buttonTitle}}
    />
  </template>
}

模态框组件 (assets/javascripts/discourse/components/feedback-form-modal.gjs - 超简化版)

import Component from "@glimmer/component"; // Ensure this is imported
// Removed tracked, action, service etc. if not needed by simplified version
import DModal from "discourse/components/d-modal"; // Ensure this is imported
import DButton from "discourse/components/d-button"; // Ensure this is imported
import { on, preventDefault } from '@ember/modifier'; // Import built-ins if used

export default class FeedbackFormModal extends Component {
  // Minimal JS needed for simplified template

  // Example getter needed by template
  get modalTitle() { return "My Modal Title"; } // Hardcoded
  get cancelLabel() { return "Cancel"; }
  get submitLabel() { return "Submit"; }

  // Dummy action if needed by button
  @action submitFeedback() { console.log("Dummy submit"); }

  {{!-- ULTRA-SIMPLIFIED TEMPLATE THAT STILL CAUSES ERROR --}}
  <template>
    <DModal @title={{this.modalTitle}} @closeModal={{@closeModal}} class="feedback-form-modal">
      <:body>
          <p>--- MINIMAL MODAL TEST ---</p>
          {{#if this.errorMessage}} {{!-- Using built-in 'if' --}}
              <div class="alert alert-error" role="alert">{{this.errorMessage}}</div>
          {{/if}}
      </:body>
      <:footer>
          <DButton @action={{@closeModal}} class="btn-flat"> {{this.cancelLabel}} </DButton>
          <DButton @action={{this.submitFeedback}} class="btn-primary" @icon={{if this.isSubmitting "spinner"}} > {{!-- Using built-in 'if' --}}
              {{this.submitLabel}}
          </DButton>
      </:footer>
    </DModal>
  </template>
}

鉴于即使通过 registerValueTransformer("post-menu-buttons", ...) 触发的 this.modal.show() 渲染一个超简化的 GJS 组件模板(仅包含导入的核心组件如 <DModal>/<DButton> 和内置助手如 if)时,仍然出现 Attempted to use a value as a helper... undefined 错误,可能是什么原因造成的?

在最近的 Discourse 版本(特别是 3.5.0.beta3-dev)中,通过这种方式触发的模态框中是否存在助手/组件作用域解析的已知问题或限制?在 GJS 中从帖子菜单按钮显示模态框是否有其他推荐的模式?

任何提示都将不胜感激!

1 个赞

我尝试了你的最小测试代码,它对我来说可以工作:

我所做的唯一更改是添加了一个缺失的 action 导入,删除了模板外部的注释,并强制显示了按钮。

你能在 GitHub 上创建一个快速的 TC 来触发这个错误吗?

1 个赞

感谢您的关注。在您确认后,我尝试设置一个 GitHub TC,使用了一个全新的插件,并最终自己隔离了问题。

当在模态模板中使用组件参数(@icon={{if...}})内的 if 辅助函数时,就会发生此问题。删除此特定用法后,模态即可正确渲染。

这就是我尝试做的:@icon={{if this.isSubmitting \"spinner\"}}

顺便说一句,我喜欢这个新的 Horizon 主题。

1 个赞

你好,

关于此问题的后续:我已按照建议创建了 minimal-modal-test 插件。

  • 成功: 当测试插件的模态组件 (minimal-modal.gjs) 只包含基本元素时(例如 <p><textarea>、核心 <DModal><DButton>,甚至像 #if 用于条件块或 if 用于提交按钮的图标等内置助手),通过 post-menu-buttons 值转换器添加的按钮组件触发 modal.show() 时,它可以正确渲染而不会出错。这证实了基本模态服务、组件渲染、值转换器和内置助手在我的环境中(Discourse v3.5.0.beta3-dev)单独使用时似乎是有效的。

  • 失败: 一旦我将子组件 <StarRatingInput> 添加回模态的模板(minimal-modal.gjsfeedback-form-modal.gjs)中,错误(TypeError: userProvidedCallback is undefined / 之前是 Attempted to use a value as a helper... undefined)就会一致地再次出现

我已确保 StarRatingInput 组件本身使用了早期确定的解决方法(内联 SVG 而不是 dIcon 助手,箭头函数用于 starClass 等方法以修复 this 上下文,标准 JS Array.from 而不是 range 助手)。StarRatingInput 的内部代码现在似乎是正确的。

这表明错误是由在此特定渲染上下文中,在父模态组件内渲染嵌套的 <StarRatingInput> 组件(其中包含 #each 循环、动态类绑定和事件处理程序)触发的。

复现步骤

  1. 你正在运行最新的 Discourse 开发环境版本。

  2. 以任何用户身份登录。

  3. 导航到任何主题并查看第一个帖子

  4. 点击添加到帖子菜单的 “测试失败模态” 按钮。

  5. 打开浏览器的开发者控制台。

预期结果

应该出现一个简单的模态对话框,其中包含一个星级评分输入。

实际结果

模态完全无法渲染。浏览器控制台显示错误。

1 个赞

谢谢你的仓库,它很有帮助!
我找到了你的问题。问题不在于 StartRatInput 组件,而是你从错误的路径导入了 eq

它在这里失败:

checked={{eq this.testRating ratingValue}}

你需要更改导入:

import { eq } from \"@ember/helper\";

import { eq } from \"truth-helpers\";

结果:

3 个赞

非常感谢!我一半的时间都浪费在处理导入上。:roll_eyes:

1 个赞

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.