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

**URL:** <https://meta.discourse.org/t/gjs-component-fails-with-undefined-helper-error-when-shown-via-modal-service-discourse-3-5-0/362272>\
**Category:** Development\
**Created:** [2025年四月17日 07:04 UTC](https://meta.discourse.org/t/gjs-component-fails-with-undefined-helper-error-when-shown-via-modal-service-discourse-3-5-0/362272 "2025-04-17T07:04:42Z")\
**Posts on this page:** 1\
**Showing post:** 1

<div class="post-metadata">

**Author:** ![SubStrider](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/substrider/32/512604_2.png) [@SubStrider](https://meta.discourse.org/u/SubStrider)\
**Post date:** [2025年四月17日 07:04 UTC](https://meta.discourse.org/t/gjs-component-fails-with-undefined-helper-error-when-shown-via-modal-service-discourse-3-5-0/362272/1 "2025-04-17T07:04:42Z")

</div>

在尝试使用 `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` 过程中发生的：

```plaintext
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>`）和标准的内置助手（`if`、`on` 等），也会发生这种情况。

以下是供参考的代码：

plugin.rb

```ruby
# 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

```javascript
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`)

```javascript
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` - _超简化版_)

```javascript
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 中从帖子菜单按钮显示模态框是否有其他推荐的模式？

任何提示都将不胜感激！

---

_[View the full topic](https://meta.discourse.org/t/gjs-component-fails-with-undefined-helper-error-when-shown-via-modal-service-discourse-3-5-0/362272)._
