Le composant GJS échoue avec une erreur "helper indéfini" lorsqu'il est affiché via le service modal (Discourse 3.5.0)

Je rencontre une erreur de rendu persistante lorsque j’essaie d’afficher un composant Glimmer (.gjs) en tant que modal en utilisant this.modal.show(). Le modal est déclenché à partir d’un autre composant GJS ajouté au menu de publication via le transformateur de valeur post-menu-buttons. J’utilise Discourse v3.5.0.beta3-dev.

J’essaie d’ajouter un bouton au menu de publication en utilisant api.registerValueTransformer("post-menu-buttons", ...). Cliquer sur ce bouton devrait ouvrir un modal défini par un composant GJS séparé (FeedbackFormModal) en utilisant this.modal.show(FeedbackFormModal, ...).

Lorsque le bouton est cliqué et que this.modal.show() est appelé, l’application plante avec l’erreur suivante, apparemment lors du processus de rendu de 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

Cela se produit même lorsque le template FeedbackFormModal est réduit à son minimum absolu, ne contenant que des composants de base importés (<DModal>, <DButton>) et des helpers intégrés standard (if, on, etc.).

Je colle le code ci-dessous pour référence :

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

Composant Bouton (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>
}

Composant Modal (assets/javascripts/discourse/components/feedback-form-modal.gjs - Ultra-Simplifié)

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

Étant donné que l’erreur Attempted to use a value as a helper... undefined persiste même lors du rendu d’un template de composant GJS ultra-simplifié (contenant uniquement des composants de base importés comme <DModal>/<DButton> et des helpers intégrés comme if) via this.modal.show() déclenché à partir d’un composant ajouté via registerValueTransformer("post-menu-buttons", ...), qu’est-ce qui pourrait causer cela ?

Y a-t-il un problème connu ou une limitation avec la résolution de la portée des helpers/composants dans les modaux déclenchés de cette manière dans les versions récentes de Discourse (en particulier 3.5.0.beta3-dev) ? Existe-t-il des modèles alternatifs recommandés pour afficher des formulaires modaux à partir de boutons de menu de publication dans GJS ?

Toute indication serait grandement appréciée !

1 « J'aime »

J’ai essayé votre code de test minimal et il fonctionne pour moi :

Les seules modifications que j’ai apportées sont d’ajouter une importation action manquante, de supprimer un commentaire en dehors du modèle et de forcer l’affichage du bouton.

Pouvez-vous créer une TC rapide sur GitHub qui déclenche cette erreur ?

1 « J'aime »

Merci d’avoir jeté un œil. Après votre confirmation, j’essayais de configurer un TC GitHub avec un plugin tout neuf et j’ai fini par isoler le problème moi-même.

Il se produit lors de l’utilisation de l’aide if dans un argument de composant (@icon={{if...}}) à l’intérieur du modèle modal. La suppression de cette utilisation spécifique permet au modal de s’afficher correctement.

Voici ce que j’essayais de faire : @icon={{if this.isSubmitting "spinner"}}

Au fait, j’adore ce nouveau thème Horizon.

1 « J'aime »

Bonjour,

Pour faire suite à ce problème : J’ai créé le plugin minimal-modal-test comme suggéré.

  • Succès : Lorsque le composant modal du plugin de test (minimal-modal.gjs) ne contenait que des éléments de base (comme <p>, <textarea>, le composant DModal et DButton de base, et même des helpers intégrés comme #if pour les blocs conditionnels ou if utilisé pour l’icône du bouton de soumission), il s’affichait correctement sans erreur lorsqu’il était ouvert via modal.show() déclenché par le composant bouton ajouté via le transformateur de valeur post-menu-buttons. Cela confirme que le service modal de base, le rendu des composants, le transformateur de valeur et les helpers intégrés semblent fonctionnels isolément dans mon environnement (Discourse v3.5.0.beta3-dev).

  • Échec : L’erreur (TypeError: userProvidedCallback is undefined / précédemment Attempted to use a value as a helper... undefined) se reproduit de manière constante dès que j’ajoute le composant enfant <StarRatingInput> dans le template du modal (minimal-modal.gjs ou feedback-form-modal.gjs).

J’ai veillé à ce que le composant StarRatingInput lui-même utilise les solutions de contournement identifiées précédemment (SVG en ligne au lieu du helper dIcon, fonctions fléchées pour les méthodes comme starClass afin de corriger le contexte this, Array.from standard de JS au lieu du helper range). Le code interne de StarRatingInput semble correct maintenant.

Cela suggère que l’erreur est déclenchée par le rendu du composant imbriqué <StarRatingInput> (qui contient une boucle #each, des liaisons de classes dynamiques et des gestionnaires d’événements) à l’intérieur du composant modal parent dans ce contexte de rendu spécifique.

Étapes pour reproduire

  1. Vous exécutez la dernière version de l’environnement de développement Discourse.

  2. Connectez-vous en tant que n’importe quel utilisateur.

  3. Naviguez vers n’importe quel sujet et visualisez le premier message.

  4. Cliquez sur le bouton “Test Failing Modal” ajouté au menu du message.

  5. Ouvrez la console du développeur du navigateur.

Résultat attendu

Une simple boîte de dialogue modale devrait apparaître, contenant un champ de saisie d’évaluation par étoiles.

Résultat actuel

Le modal ne parvient pas à se rendre complètement. La console du navigateur affiche une erreur.

1 « J'aime »

Merci pour le dépôt, il a aidé !
J’ai trouvé votre problème. Il ne s’agit pas du composant StartRatInput, mais en fait parce que vous importez eq depuis le mauvais chemin.

Cela échoue ici :

checked={{eq this.testRating ratingValue}}

Et vous devez changer l’importation :

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

import { eq } from "truth-helpers";

Résultat :

3 « J'aime »

Merci beaucoup pour ça ! La moitié du temps, j’ai perdu à lutter avec les importations. :roll_eyes:

1 « J'aime »

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