我创建了一个更丰富的用户卡片内容主题,并添加了一些自定义用户字段。我希望让卡片保持固定,并添加一个关闭按钮,类似于“创建新主题”组件的实现方式。
我这样理解是否正确:我需要覆盖 user-card-contents.js 文件,以阻止关闭元素的调用?我能否将这部分内容打包到主题中?
谢谢!
我创建了一个更丰富的用户卡片内容主题,并添加了一些自定义用户字段。我希望让卡片保持固定,并添加一个关闭按钮,类似于“创建新主题”组件的实现方式。
我这样理解是否正确:我需要覆盖 user-card-contents.js 文件,以阻止关闭元素的调用?我能否将这部分内容打包到主题中?
谢谢!
嘿,Pete。欢迎来到 Meta ![]()
简短的回答是:可以。如果你的更改仅影响前端,那么可以在主题或组件中完成。
能否请你详细说明一下你说的“sticky”是什么意思?如果你是指希望它随内容滚动但保持在同一位置,那么这需要通过 CSS 来实现。另外,这项更改是否打算同时应用于桌面端和移动端?
能否请你具体描述一下你指的是哪个调用?(例如点击时、滚动时等)
关闭按钮的 HTML 结构需要添加到用户卡片的 Handlebars 模板中。处理按钮点击事件的逻辑则需要添加到组件的 .js 文件中。
可能不需要完全覆盖,有一些钩子(hooks)可以用来覆盖类中的特定方法。如果你能更详细地描述一下你想实现的功能,我可以分享更多关于这方面的信息。
谢谢你的欢迎,Joe!
我的目标是在桌面端阻止 card-contents-base.js 混入中的事件处理器 clickOutsideEventName 关闭卡片。相反,我希望强制用户点击按钮来关闭它。对于移动端,我可能需要采用不同的处理方式。
我已经让这个 Handlebars 模板运行起来了,接下来要解决的是 .js 部分的问题 ![]()
TL;DR 我想这正是你所需要的
主题 JS
api.modifyClass("component:user-card-contents", {
didInsertElement() {
this._super(...arguments);
$("html").off(this.clickOutsideEventName);
},
actions: {
closeCard() {
this._close();
}
}
});
然后在你的模板中的某个位置添加以下内容
{{d-button
class="btn-flat"
action=(action "closeCard")
icon="times"
}}
详细版本
既然你已经开发过自己的主题,可能已经知道大部分内容,但我会尽量为更广泛的受众提供更详细的说明。
我首先会做的就是在本地或 GitHub 上搜索。在 GitHub 上,你会得到类似 这样的结果。在大多数情况下,一个搜索词会有多个结果,你要么需要更具体,要么需要手动扫描结果以找到接近你需要的内容。
所以,现在我们来到了这个文件
这个文件是一个 Mixin。我为什么要提到这一点?因为你需要意识到 Mixin 可以在许多不同的地方共享。在这个例子中,它既用于用户卡片,也用于群组卡片。因此,你在这里做的修改将同时影响两者。
如果你在文件中搜索,你会发现 clickOutsideEventName 首先在这里定义
然后作为属性在这里传递
最后在这里被使用
很酷,但这都意味着什么呢?好吧,如果你看看所有这些代码被添加的位置,你会注意到它位于 didInsertElement 内部
Ember 指南 指出
Ember 保证,在调用
didInsertElement()时:
- 组件的元素已被创建并插入到 DOM 中。
- 组件的元素可以通过组件的
this.element属性访问。
我们为什么需要这个?因为我们需要为用户卡片和群组卡片设置不同的 mousedown 事件处理程序。如果我们回顾一下,你现在会注意到元素的 ID 被用于 clickOutsideEventName
正如我们上面讨论的,它随后被作为属性传递。
现在,让我们看看这一切如何与你正在做的事情相关联。
你试图防止用户点击卡片外部时卡片被关闭。所以,让我们尝试找出一种方法来实现这一点。如果你还记得,我们讨论过 clickOutsideEventName 最终在这里被使用
简而言之,当卡片插入时(在首次页面查看时),这会在 HTML 元素上添加一个 mousedown 事件处理程序。然后我们检查 mousedown 事件的目标。如果目标在卡片内,我们就退出。如果它在卡片外,我们就通过调用 this._close() 来关闭它。
_close() 在这里定义
这就是你添加关闭按钮时需要调用的方法——但我们稍后再回到这个话题。
现在,目标是如果你希望点击卡片外部不关闭卡片,就需要移除这个 mousedown 事件处理程序。我们该怎么做呢?好吧,我们需要修改 didInsertElement(),方法如下。
Discourse 主题可以访问 插件 API,其中包含许多你可以使用的方法。关于最常用的方法,这里有更多细节 在此。
如果你还记得,我们正在处理的文件是一个 Mixin。Mixin 是一个 Ember 类。因此,我们将使用的方法是 modifyClass
当你使用 modifyClass 时,你可以添加、修改或完全覆盖一个类方法。我们将专注于修改方法,因为这是你想要做的。
我们要修改 didInsertElement(),所以我们可以这样做
api.modifyClass('mixin:card-contents-base', {
didInsertElement() {
console.log("foo");
}
});
然后试试看……
嗯,这行不通。
为什么会这样?原来 modifyClass 方法目前不支持修改 Mixin(就我所测试而言)。我会记下这一点,找出原因并检查是否可以修复。不过现在,让我们回到你想要做的事情上。
好吧,我们不能修改 Mixin,所以我想我们被困住了,对吧?不。让我们再深入挖掘一下。
正如我之前提到的,该 Mixin 被用户 user-card-contents 和 group-card-contents Ember 组件 使用(再次强调,因为 Mixin 旨在使代码可重用)
所以,让我们看看 user-card-contents 组件在这里
如果你仔细阅读,你会发现我们首先在这里导入了上面讨论的 Mixin 此处
然后创建一个新的 Ember 组件并将 Mixin 传递给它。
这意味着什么?这意味着 user-card-contents 的 didInsertElement() 实际上是从 Mixin 继承的。group-card-contents 也是如此。
这把我们带到了哪里?好吧,有好消息也有坏消息。好消息是,如果你想修改 user-card-contents 而不影响 group-card-contents,你可以做到!坏消息是,如果你想让你的修改同时适用于两者,那么你就得复制一些代码。
让我们回到 modifyClass 并再次尝试 user-card-contents。所以像这样:
api.modifyClass('component:user-card-contents', {
didInsertElement() {
console.log("foo");
}
});
看看会发生什么……
voalá ![]()
修改已注册,我们可以在控制台中看到它,但我们还没有完全达到目标。
所以,既然我们现在可以修改 didInsertElement(),让我们尝试回到你想要做的事情。如果你还记得,mousedown 事件处理程序在这里的 didInsertElement 中定义
那我们能做些什么呢?好吧,这很简单
$("html").off(clickOutsideEventName)
但这能行吗?不行。如果你这样做
api.modifyClass('component:user-card-contents', {
didInsertElement() {
$("html").off(clickOutsideEventName)
}
});
你会得到一些损坏的东西。为什么会这样?因为这不是对方法的修改。这是对核心 didInsertElement() 方法的完全覆盖。因此,如果你这样做,核心中的任何代码实际上都不会被执行。
我们如何修复这个问题?原来 Ember 有一个叫做 this._super(...arguments) 的东西。
它是做什么的?它允许你在类方法已有的代码基础上追加或前置代码。例如,如果你这样做
api.modifyClass('component:user-card-contents', {
didInsertElement() {
// 你想添加的代码
$("html").off(clickOutsideEventName);
// 核心代码
this._super(...arguments);
}
});
那么你想添加的代码将在这里的任何其他代码之前运行
但是,如果你这样做……
api.modifyClass('component:user-card-contents', {
didInsertElement() {
// 核心代码
this._super(...arguments);
// 你想添加的代码
$("html").off(clickOutsideEventName);
}
});
那么你的代码将在以下所有内容运行之后运行
这是一个很好的方法,可以让你的主题对核心中的更改具有弹性,因为核心中的所有代码首先运行,然后你的代码随后运行。所以,让我们再试一次,看看会发生什么。
api.modifyClass('component:user-card-contents', {
didInsertElement() {
// 核心代码
this._super(...arguments);
// 你想添加的代码
$("html").off(clickOutsideEventName);
}
});
以及……
为什么会发生这种情况?是因为不同的代码上下文。在 Ember 组件文件中,clickOutsideEventName 在被使用时已经被定义。你的主题在不同的文件中,所以 clickOutsideEventName 在那里没有定义。
我们如何修复它?还记得这个吗?
clickOutsideEventName 是组件的一个属性,所以如果你使用 this.clickOutsideEventName,它应该可以工作。让我们试试。
api.modifyClass('component:user-card-contents', {
didInsertElement() {
// 核心代码
this._super(...arguments);
// 你想添加的代码
$("html").off(this.clickOutsideEventName);
},
});
确实有效 ![]()
用户卡片现在可以打开且没有任何错误,点击外部没有任何反应。
你可以对群组卡片做完全相同的事情,如下所示
api.modifyClass('component:group-card-contents', {
didInsertElement() {
// 核心代码
this._super(...arguments);
// 你想添加的代码
$("html").off(this.clickOutsideEventName);
},
});
剩下的唯一事情是将关闭按钮连接到之前讨论的 _close() 方法,这需要三个步骤。
closeCard 动作(你可以随意命名)为了简单起见,我将坚持使用用户卡片。所以,我们添加这个(以及我们之前讨论过的内容)
api.modifyClass("component:user-card-contents", {
didInsertElement() {
this._super(...arguments);
$("html").off(this.clickOutsideEventName);
},
// 新内容
actions: {
closeCard() {
this._close();
}
}
});
这一切所做的就是每当触发 closeCard 自定义动作时,调用核心的 _close() 方法。
接下来,我们需要在用户卡片模板中添加一个按钮,或者类似这样的内容
{{d-button
class="btn-flat"
action=(action "closeCard")
icon="times"
}}
我的粗略结果如下所示
当然,正如我上面提到的,你可以对群组卡片做类似的事情。
我将把群组卡片和移动端的实现留作你的练习,因为如果你希望对它们进行修改,本质上会使用我们上面讨论的完全相同的概念,但如果你遇到任何问题,请告诉我。
非常感谢,这真是一个出色的教程!我真的很感激。我之前并没有意识到插件 API 的存在。
上面有一点并不那么显而易见:需要将主题 JS 的更改包裹在 <script> 标签中,并将其放入插件的 common/head_tag.html 文件中:
<script type="text/discourse-plugin" version="0.2">
</script>
只是出于好奇,标签中的版本号在这里重要吗?另外,是否总是最好将这些内容放在 head_tag.html 而不是 header.html 中,还是说这其实并不重要?
谢谢!
@Johani 如果您愿意,我可以为此另开一个线程,但我正在尝试追踪客户端的 user-card 控制器,该控制器:
我的目标是移除第二次点击的功能。谢谢!