来自 GJS Composer 表单的自定义数据未包含在 `opts` 中的 `:topic_created` 事件

这是对此的延续 - Custom topic fields per category or custom topic entry form per category?

我决定创建这个插件。表单组件本身通过 api.renderInOutlet 正确渲染。但是数据没有在后端保存到自定义表中。

前端设置:

  1. 我使用 api.renderInOutlet("composer-fields", MyOutletConnectorComponent) 在指定类别中创建新主题时渲染 GJS 组件 (MyOutletConnectorComponent)。

  2. MyOutletConnectorComponent 然后渲染我的主 GJS 表单组件 (MyMarketFormComponenent)。

  3. MyMarketFormComponenent 中,用户输入会更新一个 JavaScript 对象,然后该对象被设置为 Outlet 参数提供的 composerModel 的顶层属性。例如:

    // 在 MyMarketFormComponenent 操作中
    const currentData = this.args.composerModel.get("market_listing_data") || {};
    const updatedData = { ...currentData, [fieldKey]: newValue };
    this.args.composerModel.set("market_listing_data", updatedData);
    
  4. 在我的插件初始化程序 (tecenc-market-composer.js) 中,我使用 api.serializeOnCreate("market_listing_data", "market_listing_data"); 来尝试将此数据传递到后端。我也使用 api.serializeToDraft("market_listing_data", "market_listing_data");

后端问题:

在我的 plugin.rb 中,在 DiscourseEvent.on(:topic_created) do |topic, opts, user| ... end 处理程序中:

  • 我检查主题是否在正确的类别中(这有效)。
  • 然后我尝试使用 market_data = opts[:market_listing_data](或 opts["market_listing_data"])访问序列化数据。
  • 问题是 opts[:market_listing_data] 始终为 nil
  • 记录 opts.keys 确认我的 market_listing_data 键不在 opts 哈希的顶层。opts 哈希本身包含标准键,如 :raw:title:category:archetype 等。

我的 plugin.rb 代码片段用于事件:

# plugin.rb
# ...
module ::TecencMarket
  SERIALIZED_DATA_KEY = "market_listing_data".freeze
end
# ...
after_initialize do
  # ...
  DiscourseEvent.on(:topic_created) do |topic, opts, user|
    # ... (类别检查逻辑) ...

    # 这是 market_data 为 nil 的地方
    market_data = opts[TecencMarket::SERIALIZED_DATA_KEY.to_sym]

    Rails.logger.info "[MyPlugin] Opts keys: #{opts.keys.inspect}"
    Rails.logger.info "[MyPlugin] market_data from opts: #{market_data.inspect}"

    if market_data.present? && market_data.is_a?(Hash)
      # ... 处理并将数据保存到自定义表 ...
    else
      Rails.logger.warn "[MyPlugin] Market data not found in opts as expected."
    end
  end
  # ...
end

问题:

  1. 确保通过 GJS 组件设置为 composerModel 的顶层属性(例如 composerModel.set("my_plugin_data_key", { ... });)被 api.serializeOnCreate 正确序列化并可在后端 :topic_created 事件的 opts 哈希中访问的正确且最可靠的方法是什么?
  2. api.serializeOnCreate("my_key", "my_key") 是否适用于从 GJS 上下文设置在 composerModel 上的任意顶层键,还是通过 custom_fields 进行序列化(例如,设置 composerModel.custom_fields.my_plugin_key = {...} 并使用 api.serializeOnCreate("custom_fields.my_plugin_key"))是这种类型的数据传输到新主题的唯一健壮/推荐方法?
  3. 在使用由 GJS 组件管理的数据时,使用 api.serializeOnCreate 是否有任何特定的注意事项或常见陷阱可能导致数据未包含在 opts 哈希中?

我已经确认前端表单正在正确收集数据并将其设置在 composerModel.market_listing_data 属性上。问题似乎纯粹在于如何将此数据序列化并传递到后端事件。

任何关于现代 Discourse 中推荐模式的指导或示例都将不胜感激!

谢谢!

您好,

关于我之前关于数据未到达 opts 哈希的问题的后续,我已将策略调整为使用推荐的 custom_fields 路径,从我的插件的 composer 表单中序列化数据。

当前方法:

  1. 前端初始化程序:
    • 我试图确保 composerModel.custom_fieldscomposerModel.custom_fields["tecenc_market_data"](我插件的数据键)被初始化为对象。我的最新尝试是使用 api.modifyClass("model:composer", ...) 添加初始化逻辑到 init()clearState():

      // 在我的插件初始化器(tecenc-market-composer.js)中
      api.modifyClass("model:composer", {
        pluginId: "tecencMarketInitCustomFieldsOnComposer",
        init() {
          this._super(...arguments); 
          if (!this.custom_fields || typeof this.custom_fields !== 'object') {
            this.custom_fields = {};
          }
          if (!this.custom_fields["tecenc_market_data"] || typeof this.custom_fields["tecenc_market_data"] !== 'object') {
            this.custom_fields["tecenc_market_data"] = {};
          }
        },
        clearState() {
          this._super(...arguments); 
          if (!this.custom_fields || typeof this.custom_fields !== 'object') {
            this.set("custom_fields", {});
          }
          let cf = this.get("custom_fields"); 
          if (!cf["tecenc_market_data"] || typeof cf["tecenc_market_data"] !== 'object') {
            const newCustomFields = { ...cf }; 
            newCustomFields["tecenc_market_data"] = {};
            this.set("custom_fields", newCustomFields);
          }
        }
      });
      
    • 然后我使用 api.serializeOnCreate('custom_fields.tecenc_market_data');api.serializeToDraft('custom_fields.tecenc_market_data');

    • 我的 GJS 表单组件通过设置 composerModel.custom_fields.tecenc_market_data.my_field = value;(通过设置一个新的 custom_fields 对象以实现响应性)来更新。

  2. 后端 plugin.rb:
    • 调用 Topic.register_custom_field_type("tecenc_market_data", :json)
    • topic_created 事件处理程序期望从 topic.custom_fields["tecenc_market_data"] 读取数据。

新的阻塞性错误:

尽管我尝试在 composerModel 上初始化 custom_fields,但我在尝试打开新话题的 composer 时(点击“创建话题”按钮),不断遇到以下 JavaScript 错误:


Uncaught (in promise) Error: Property set failed: object in path "custom_fields" could not be found.

Ember 3

open composer.js:1010

open composer.js:1009

_setModel composer.js:1451

open composer.js:1401

openNewTopic composer.js:1410

createTopic list.js:167

clickCreateTopicButton d-navigation.gjs:224

_triggerAction d-button.gjs:138

Ember 11

_triggerAction d-button.gjs:135

click d-button.gjs:93

source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:94366

Ember 2

source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:94040

Ember 26 property_set-BapAkp3X.js:79:10

```""

这个错误表明在 Discourse 核心代码在 `composer.js:1010`(在 `open` 方法中,由 `openNewTopic` 调用)试图在 `composerModel` 上设置嵌套属性时,`this.model.custom_fields`(在 `composerModel` 上)为 `undefined` 或 `null`。这种情况发生在我的插件表单甚至还没有渲染之前。

**问题:**

1. 考虑到核心的 `Composer` 模型应初始化 `custom_fields: {}`,为什么在此 Discourse 版本的 `openNewTopic` 序列中,`composerModel.custom_fields` 在 `composer.js:1010` 时仍为未定义?
2. 在初始化器中使用 `api.modifyClass("model:composer", ...)` 方法是不是确保 `custom_fields`(以及其中的嵌套插件键)提前初始化为对象的正确/最有效方式,以避免这个核心错误?如果不是,推荐的模式是什么?
3. 是否存在时间点上的问题,核心的 Composer 初始化步骤在 `custom_fields` 被期待存在之前就已经运行或期待它已经被初始化,而插件通过 `api.modifyClass` 进行的初始化还没有在 `Composer` 模型的原型或实例上完全生效?

任何关于核心中的 “Property set failed” 错误的见解,以及插件在初始化过程中安全地与 `composerModel.custom_fields` 交互的最佳实践都将非常有帮助。

感谢您的时间!

我认为你需要在Rails端的序列化器中添加你的数据。

你的数据有没有传到Rails?你把它存在哪里?

看看那些具有addToSerializer功能的插件。

我正在将数据保存在自定义数据库表中。

addToSerializer 是用于将数据从服务器移动到客户端的,对吧?

我的问题是将数据从客户端移动到服务器,以便我可以保存它。所以我正在使用 serializeOnCreate。但问题是数据没有流向 opts 哈希。所以,目前,答案是否,自定义数据并未按预期进入 opts 哈希。它没有进入 Rails。

我确实查看了这个教育插件,但无法为 6 个自定义字段使其正常工作。

1 个赞

我明白了。

我遗漏了这一步:api.serializeToTopic(MY_BLOB_KEY, \topic.${MY_BLOB_KEY}\);

2 个赞