encountering a persistent rendering error when trying to display a Glimmer component (.gjs
) as a modal using this.modal.show()
. The modal is triggered from another GJS component added to the post menu via the post-menu-buttons
value transformer. I’m running Discourse v3.5.0.beta3-dev
I am trying to Add a button to the post menu using api.registerValueTransformer("post-menu-buttons", ...)
. Clicking this button should open a modal defined by a separate GJS component (FeedbackFormModal
) using this.modal.show(FeedbackFormModal, ...)
.
When the button is clicked and this.modal.show()
is called, the application crashes with the following error, seemingly during the rendering process of the 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
This happens even when the FeedbackFormModal
template is stripped down to its absolute minimum, containing only imported core components (<DModal>
, <DButton>
) and standard built-in helpers (if
, on
, etc.).
Pasting the code below for reference:
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>
}
Given that the error Attempted to use a value as a helper... undefined
persists even when rendering an ultra-simplified GJS component template (containing only imported core components like <DModal>
/<DButton>
and built-in helpers like if
) via this.modal.show()
triggered from a component added via registerValueTransformer("post-menu-buttons", ...)
, what could be causing this?
Is there a known issue or limitation with helper/component scope resolution in modals triggered this way in recent Discourse versions (specifically 3.5.0.beta3-dev)? Are there alternative recommended patterns for showing modal forms from post menu buttons in GJS?
Any pointers would be greatly appreciated!