Il componente GJS fallisce con errore "helper indefinito" quando viene mostrato tramite il servizio modal (Discourse 3.5.0)

Si riscontra un errore di rendering persistente durante il tentativo di visualizzare un componente Glimmer (.gjs) come modale utilizzando this.modal.show(). Il modale viene attivato da un altro componente GJS aggiunto al menu del post tramite il trasformatore di valore post-menu-buttons. Sto eseguendo Discourse v3.5.0.beta3-dev.

Sto cercando di aggiungere un pulsante al menu del post utilizzando api.registerValueTransformer("post-menu-buttons", ...). Fare clic su questo pulsante dovrebbe aprire un modale definito da un componente GJS separato (FeedbackFormModal) utilizzando this.modal.show(FeedbackFormModal, ...).

Quando si fa clic sul pulsante e si chiama this.modal.show(), l’applicazione va in crash con il seguente errore, apparentemente durante il processo di rendering di FeedbackFormModal:

Errore verificato:

- Durante il rendering:
  -top-level
    application
      (component solo template sconosciuto)
        DiscourseRoot
          ModalContainer
            FeedbackFormModal
              DModal
                conditional-in-element:ConditionalInElement
                  (component solo template sconosciuto) index.js:3970:18

Errore verificato: index.js:3377:16

Uncaught (in promise) Error: Tentativo di utilizzare un valore come helper, ma non era un oggetto o una funzione. Le definizioni degli helper devono essere oggetti o funzioni con un gestore di helper associato. Il valore era: 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 // Il numero di riga potrebbe differire leggermente
    _triggerAction d-button.gjs:138
    Ember 10

Ciò accade anche quando il template di FeedbackFormModal è ridotto al minimo assoluto, contenendo solo componenti core importati (<DModal>, <DButton>) e helper integrati standard (if, on, ecc.).

Incollo il codice per riferimento:

plugin.rb

# frozen_string_literal: true
# name: my-plugin

module ::MyPlugin
  # ... costanti ...
end

require_relative "lib/my_plugin/engine"

after_initialize do
  # Carica Dipendenze (utilizzando require_dependency e 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__)
  # ... altre dipendenze ...

  # Aggiunge metodi a User (utilizzando class_eval poiché prepend ha fallito)
  ::User.class_eval do
    # Definisce metodi helper come my_custom_stat, ecc.
    def my_custom_stat; # ... implementazione ...; end
    public :my_custom_stat
    # ... altri metodi ...
  end

  # Prepend Guardian Extensions (se presenti)
  # ::Guardian.prepend(MyPlugin::GuardianExtensions)

  # Modifiche ai Serializer
  reloadable_patch do |plugin|
    # Aggiunge attributi ai serializer, ad es.:
    add_to_serializer(:post, :some_flag_for_button) do
        # Logica per determinare se il pulsante deve essere mostrato
        true # Esempio
    end
    # ... altre aggiunte ai serializer ...
  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"; // Componente pulsante
// ... importa altri componenti per altri outlet ...

export default apiInitializer("1.13.0", (api) => {
  // Usa Value Transformer per il pulsante del menu post
  api.registerValueTransformer("post-menu-buttons", ({ value: dag, context }) => {
    const { post } = context;
    // Logica per determinare se il pulsante deve essere renderizzato in base a post.some_flag_for_button
    const shouldRenderButton = post?.some_flag_for_button; // Flag di esempio

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

  // ... renderInOutlet per altri elementi UI ...
});

Componente Pulsante (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"; // Il componente modale

export default class LeaveFeedbackButton extends Component {
  @service modal;
  @service appEvents; // Usato per la visualizzazione degli errori

  // Args: post

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

  @action
  openFeedbackModal() {
    console.log("Opening Modal...");
    try {
       // Modello semplificato per il test
       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" {{!-- Icona di esempio --}}
      @label={{this.buttonLabel}}
      title={{this.buttonTitle}}
    />
  </template>
}

Componente Modale (assets/javascripts/discourse/components/feedback-form-modal.gjs - Ultra-Semplificato)

import Component from "@glimmer/component"; // Assicurati che sia importato
// Rimosso tracked, action, service ecc. se non necessari per la versione semplificata
import DModal from "discourse/components/d-modal"; // Assicurati che sia importato
import DButton from "discourse/components/d-button"; // Assicurati che sia importato
import { on, preventDefault } from '@ember/modifier'; // Importa i built-in se usati

export default class FeedbackFormModal extends Component {
  // JS minimo necessario per il template semplificato

  // Getter di esempio necessario per il template
  get modalTitle() { return "My Modal Title"; } // Hardcoded
  get cancelLabel() { return "Cancel"; }
  get submitLabel() { return "Submit"; }

  // Azione fittizia se necessaria dal pulsante
  @action submitFeedback() { console.log("Dummy submit"); }

  {{!-- TEMPLATE ULTRA-SEMPLIFICATO CHE CAUSA ANCORA ERRORE --}}
  <template>
    <DModal @title={{this.modalTitle}} @closeModal={{@closeModal}} class="feedback-form-modal">
      <:body>
          <p>--- TEST MODALE MINIMO ---</p>
          {{#if this.errorMessage}} {{!-- Utilizzo dell'helper 'if' integrato --}}
              <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"}}> {{!-- Utilizzo dell'helper 'if' integrato --}}
              {{this.submitLabel}}
          </DButton>
      </:footer>
    </DModal>
  </template>
}

Dato che l’errore Attempted to use a value as a helper... undefined persiste anche quando si renderizza un template GJS ultra-semplificato (contenente solo componenti core importati come <DModal>/<DButton> e helper integrati come if) tramite this.modal.show() attivato da un componente aggiunto tramite registerValueTransformer("post-menu-buttons", ...), cosa potrebbe causarlo?

Esiste un problema noto o una limitazione con la risoluzione dello scope degli helper/componenti nei modali attivati in questo modo nelle versioni recenti di Discourse (in particolare 3.5.0.beta3-dev)? Esistono pattern alternativi consigliati per mostrare form modali dai pulsanti del menu post in GJS?

Qualsiasi suggerimento sarebbe molto apprezzato!

1 Mi Piace

Ho provato il tuo codice di test minimale e funziona per me:

Le uniche modifiche che ho apportato sono state l’aggiunta di un’importazione action mancante, la rimozione di un commento al di fuori del template e la forzatura della visualizzazione del pulsante.

Puoi creare un rapido TC su GitHub che attivi questo errore?

1 Mi Piace

Grazie per aver dato un’occhiata. Dopo la tua conferma, ho cercato di configurare un TC su GitHub con un plugin nuovo di zecca e ho finito per isolare il problema da solo.

Si verifica quando si utilizza l’helper if all’interno di un argomento del componente (@icon={{if...}}) nel template della modale. La rimozione di questo uso specifico fa sì che la modale venga renderizzata correttamente.

Questo è ciò che stavo cercando di fare @icon={{if this.isSubmitting \"spinner\"}}

A proposito, adoro questo nuovo tema Horizon.

1 Mi Piace

Ciao,

In seguito a questo problema: ho creato il plugin minimal-modal-test come suggerito.

  • Successo: Quando il componente modale del plugin di test (minimal-modal.gjs) conteneva solo elementi di base (come <p>, <textarea>, il core <DModal>, <DButton>, e persino helper integrati come #if per blocchi condizionali o if usato per l’icona del pulsante di invio), veniva renderizzato correttamente senza errori quando aperto tramite modal.show() attivato dal componente pulsante aggiunto tramite il trasformatore di valore post-menu-buttons. Questo conferma che il servizio modale di base, il rendering dei componenti, il trasformatore di valore e gli helper integrati sembrano funzionare in isolamento nel mio ambiente (Discourse v3.5.0.beta3-dev).

  • Fallimento: L’errore (TypeError: userProvidedCallback is undefined / precedentemente Attempted to use a value as a helper... undefined) ricompare in modo coerente non appena aggiungo il componente figlio <StarRatingInput> nuovamente nel template della modale (minimal-modal.gjs o feedback-form-modal.gjs).

Ho assicurato che il componente StarRatingInput stesso utilizzi i workaround identificati in precedenza (SVG inline invece dell’helper dIcon, funzioni freccia per metodi come starClass per correggere il contesto this, Array.from standard di JS invece dell’helper range). Il codice interno di StarRatingInput sembra corretto ora.

Ciò indica che l’errore viene attivato dal rendering del componente annidato <StarRatingInput> (che contiene un ciclo #each, binding di classi dinamici e gestori di eventi) all’interno del componente modale padre in questo specifico contesto di rendering.

Passaggi per Riprodurre

  1. Stai eseguendo l’ultima versione dell’ambiente di sviluppo di Discourse.

  2. Accedi come qualsiasi utente.

  3. Naviga in qualsiasi argomento e visualizza il primo post.

  4. Fai clic sul pulsante “Test Failing Modal” aggiunto al menu del post.

  5. Apri la console per sviluppatori del browser.

Risultato Atteso

Dovrebbe apparire una semplice finestra di dialogo modale contenente un input per la valutazione a stelle.

Risultato Effettivo

La modale non riesce a renderizzarsi completamente. La console del browser mostra un errore.

1 Mi Piace

Grazie per il repository, è stato utile!
Ho trovato il tuo problema. Non riguarda il componente StartRatInput, ma in realtà è perché importi eq dal percorso sbagliato.

Fallisce qui:

checked={{eq this.testRating ratingValue}}

E devi cambiare l’importazione:

import { eq } from "@ember/helper";
→
import { eq } from "truth-helpers";

Risultato:

3 Mi Piace

Grazie mille per questo! Metà del tempo l’ho sprecato a lottare con le importazioni. :roll_eyes:

1 Mi Piace

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