GJS-Komponente schlägt bei Anzeige über den Modal-Service mit "undefined helper"-Fehler fehl (Discourse 3.5.0)

Es gibt einen hartnäckigen Rendering-Fehler, wenn versucht wird, eine Glimmer-Komponente (.gjs) als Modal mit this.modal.show() anzuzeigen. Das Modal wird von einer anderen GJS-Komponente ausgelöst, die über den post-menu-buttons-Werttransformator zum Beitragsmenü hinzugefügt wurde. Ich verwende Discourse v3.5.0.beta3-dev.

Ich versuche, eine Schaltfläche zum Beitragsmenü hinzuzufügen, indem ich api.registerValueTransformer("post-menu-buttons", ...) verwende. Das Klicken auf diese Schaltfläche sollte ein Modal öffnen, das von einer separaten GJS-Komponente (FeedbackFormModal) mithilfe von this.modal.show(FeedbackFormModal, ...) definiert wird.

Wenn auf die Schaltfläche geklickt und this.modal.show() aufgerufen wird, stürzt die Anwendung mit dem folgenden Fehler ab, scheinbar während des Rendering-Prozesses von 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

Dies geschieht selbst dann, wenn die Vorlage von FeedbackFormModal auf das absolute Minimum reduziert ist und nur importierte Kernkomponenten (<DModal>, <DButton>) und Standard-Hilfsprogramme (if, on usw.) enthält.

Zur Referenz füge ich den Code unten ein:

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 ...
});

Button Component (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>
}

Modal Component (assets/javascripts/discourse/components/feedback-form-modal.gjs - Ultra-Simplified)

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>
}

Angesichts der Tatsache, dass der Fehler Attempted to use a value as a helper... undefined weiterhin besteht, selbst wenn eine ultra-vereinfachte GJS-Komponentenvorlage (die nur importierte Kernkomponenten wie <DModal>/<DButton> und integrierte Hilfsprogramme wie if enthält) über this.modal.show() gerendert wird, das von einer Komponente ausgelöst wird, die über registerValueTransformer("post-menu-buttons", ...) hinzugefügt wurde, was könnte die Ursache dafür sein?

Gibt es ein bekanntes Problem oder eine Einschränkung bei der Auflösung von Hilfs-/Komponenten-Scopes in Modals, die auf diese Weise in neueren Discourse-Versionen (insbesondere 3.5.0.beta3-dev) ausgelöst werden? Gibt es alternative empfohlene Muster, um Modalformulare von Beitragsmenü-Schaltflächen in GJS anzuzeigen?

Jeder Hinweis wäre sehr willkommen!

1 „Gefällt mir“

Ich habe Ihren minimalen Testcode ausprobiert und er funktioniert bei mir:

Die einzigen Änderungen, die ich vorgenommen habe, waren das Hinzufügen eines fehlenden action-Imports, das Entfernen eines Kommentars außerhalb der Vorlage und das Erzwingen der Anzeige der Schaltfläche.

Können Sie einen schnellen TC auf GitHub erstellen, der diesen Fehler auslöst?

1 „Gefällt mir“

Vielen Dank für Ihre Aufmerksamkeit. Nach Ihrer Bestätigung habe ich versucht, einen GitHub-TC mit einem frischen Plugin einzurichten, und habe das Problem schließlich selbst isoliert.

Es tritt auf, wenn der if-Helfer innerhalb eines Komponentenarguments (@icon={{if...}}) in der Modalvorlage verwendet wird. Wenn diese spezifische Verwendung entfernt wird, wird das Modal korrekt gerendert.

Das habe ich versucht: @icon={{if this.isSubmitting \"spinner\"}}

Übrigens, ich liebe dieses neue Horizon-Theme.

1 „Gefällt mir“

Hallo,

Nachverfolgung dieses Problems: Ich habe, wie vorgeschlagen, das Plugin minimal-modal-test erstellt.

  • Erfolg: Wenn die Modal-Komponente (minimal-modal.gjs) des Test-Plugins nur grundlegende Elemente enthielt (wie <p>, <textarea>, die Kernkomponenten <DModal>, <DButton> und sogar integrierte Helfer wie #if für bedingte Blöcke oder if für das Symbol der Submit-Schaltfläche), wurde sie korrekt und ohne Fehler gerendert, wenn sie über modal.show() geöffnet wurde, was von der Schaltflächenkomponente ausgelöst wurde, die über den post-menu-buttons-Werttransformator hinzugefügt wurde. Dies bestätigt, dass der grundlegende Modal-Dienst, das Rendern von Komponenten, der Werttransformator und die integrierten Helfer in meiner Umgebung (Discourse v3.5.0.beta3-dev) isoliert zu funktionieren scheinen.

  • Fehler: Der Fehler (TypeError: userProvidedCallback is undefined / zuvor Attempted to use a value as a helper... undefined) tritt konsistent wieder auf, sobald ich die Kindkomponente <StarRatingInput> wieder in die Vorlage des Modals (minimal-modal.gjs oder feedback-form-modal.gjs) einfüge.

Ich habe sichergestellt, dass die StarRatingInput-Komponente selbst die zuvor identifizierten Workarounds verwendet (Inline-SVG anstelle des dIcon-Helfer, Pfeilfunktionen für Methoden wie starClass, um den this-Kontext zu beheben, Standard-JS Array.from anstelle des range-Helfers). Der interne Code von StarRatingInput scheint jetzt korrekt zu sein.

Dies deutet darauf hin, dass der Fehler durch das Rendern der verschachtelten <StarRatingInput>-Komponente (die eine #each-Schleife, dynamische Klassenbindungen und Ereignisbehandler enthält) innerhalb der übergeordneten Modal-Komponente in diesem spezifischen Rendering-Kontext ausgelöst wird.

Schritte zur Reproduktion

  1. Sie führen die neueste Discourse-Entwicklungsumgebung aus.

  2. Melden Sie sich als beliebiger Benutzer an.

  3. Navigieren Sie zu einem beliebigen Thema und zeigen Sie den ersten Beitrag an.

  4. Klicken Sie auf die Schaltfläche „Test Failing Modal“, die dem Beitragsmenü hinzugefügt wurde.

  5. Öffnen Sie die Entwicklerkonsole des Browsers.

Erwartetes Ergebnis

Ein einfaches modales Dialogfeld sollte erscheinen, das eine Sternbewertungseingabe enthält.

Tatsächliches Ergebnis

Das Modal wird nicht vollständig gerendert. Die Browserkonsole zeigt einen Fehler an.

1 „Gefällt mir“

Danke für das Repository, es hat geholfen!
Ich habe Ihr Problem gefunden. Es liegt nicht an der StartRatInput-Komponente, sondern daran, dass Sie eq aus dem falschen Pfad importieren.

Es schlägt hier fehl:

checked={{eq this.testRating ratingValue}}

Und Sie müssen den Import ändern:

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

import { eq } from "truth-helpers";

Ergebnis:

3 „Gefällt mir“

Vielen Dank dafür! Die Hälfte der Zeit habe ich mich mit Importen herumgeschlagen. :roll_eyes:

1 „Gefällt mir“

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