我正在开发一个Ember.js应用程序,我需要实现两个下拉列表,其中第二个下拉列表中的选项根据第一个下拉列表中的选定值进行过滤。我有以下要求:
第二个下拉列表应该从API端点获取其选项。每当在第一个下拉列表中选择一个值时,第二个下拉列表中的选项应相应地进行筛选。过滤应该是动态进行的,不需要页面刷新。我尝试过实现这个功能,但遇到了一些问题。在第一个下拉列表中选择新值时,第二个下拉列表不会更新其选项。如何实现这种动态过滤行为?
下面是我目前拥有的代码的简化版本:我有一个家长,然后2下拉组件内。我把第一个下拉列表的选择值从父列表发送到第二个下拉列表。但问题是,新数据不会根据第一个下拉列表的值进行过滤(即:selectedBU(从母体注射))。这个组件非常复杂,因此我只发布了index.js和index.hbs用于第二个dropsown。
second_dropdown.hbs
{{! @glint-nocheck - not typesafe yet }}
{{! https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/ }}
<h3>{{@this.selectedBU}}</h3>
<div data-test-product-select>
{{#if this.teams}}
{{#if @formatIsBadge}}
<Inputs::BadgeDropdownList
@items={{this.teams}}
@listIsOrdered={{true}}
@onItemClick={{this.onChange}}
@selected={{@selected}}
@placement={{@placement}}
@isSaving={{@isSaving}}
@renderOut={{@renderOut}}
@icon={{this.icon}}
class="w-80 product-select-dropdown-list"
...attributes
>
<:item as |dd|>
<dd.Action data-test-product-select-badge-dropdown-item>
<Inputs::TeamSelect::Item
@product={{dd.value}}
@selected={{dd.selected}}
/>
</dd.Action>
</:item>
</Inputs::BadgeDropdownList>
{{else}}
<X::DropdownList
@items={{this.teams}}
@listIsOrdered={{true}}
@onItemClick={{this.onChange}}
@selected={{@selected}}
@placement={{@placement}}
@isSaving={{@isSaving}}
@renderOut={{@renderOut}}
class="w-[300px] product-select-dropdown-list"
...attributes
>
<:anchor as |dd|>
<dd.ToggleAction
class="x-dropdown-list-toggle-select product-select-default-toggle hds-button hds-button--color-secondary hds-button--size-medium"
>
<FlightIcon @name={{or (get-product-id @selected) "folder"}} />
<span
class="product-select-selected-value
{{unless @selected 'text-color-foreground-faint'}}"
>
{{or @selected "Select your team/pod"}}
</span>
{{#if this.selectedProductAbbreviation}}
<span class="product-select-toggle-abbreviation">
{{this.selectedProductAbbreviation}}
</span>
{{/if}}
<FlightIcon @name="caret" class="product-select-toggle-caret" />
</dd.ToggleAction>
</:anchor>
<:item as |dd|>
<dd.Action class="pr-5">
<Inputs::TeamSelect::Item
@product={{dd.value}}
@selected={{dd.selected}}
@abbreviation={{dd.attrs.abbreviation}}
/>
</dd.Action>
</:item>
</X::DropdownList>
{{/if}}
{{else if this.fetchteams.isRunning}}
<FlightIcon data-test-product-select-spinner @name="loading" />
{{else}}
<div
class="absolute top-0 left-0"
{{did-insert (perform this.fetchteams)}}
></div>
{{/if}}
</div>
字符串
和/或
second_dropdown.ts
import { assert } from "@ember/debug";
import {action, computed} from "@ember/object";
import { inject as service } from "@ember/service";
import { Placement } from "@floating-ui/dom";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { task } from "ember-concurrency";
import FetchService from "hermes/services/fetch";
import getProductId from "hermes/utils/get-product-id";
interface InputsTeamSelectSignature {
Element: HTMLDivElement;
Args: {
selectedBU: string | null;
selected?: string;
onChange: (value: string, attributes?: TeamArea) => void;
formatIsBadge?: boolean;
placement?: Placement;
isSaving?: boolean;
renderOut?: boolean;
};
}
type TeamAreas = {
[key: string]: TeamArea;
};
export type TeamArea = {
abbreviation: string;
perDocDataType: unknown;
BU: string
};
export default class InputsTeamSelectComponent extends Component<InputsTeamSelectSignature> {
@service("fetch") declare fetchSvc: FetchService;
@tracked selected = this.args.selected;
@tracked teams: TeamAreas | undefined = undefined;
@tracked selectedBU: string | null = null;
@computed('args.selectedBU')
get ReFetchTeams() {
this.selectedBU = this.args.selectedBU;
return this.fetchteams.perform();
}
get icon(): string {
let icon = "folder";
if (this.selected && getProductId(this.selected)) {
icon = getProductId(this.selected) as string;
}
return icon;
}
get selectedProductAbbreviation(): string | null {
if (!this.selected) {
return null;
}
const selectedProduct = this.teams?.[this.selected];
assert("selected Team must exist", selectedProduct);
return selectedProduct.abbreviation;
}
@action onChange(newValue: any, attributes?: TeamArea) {
this.selected = newValue;
this.args.onChange(newValue, attributes);
}
// @action onBUChange(){
// this.selectedBU = this.args.selectedBU;
// this.fetchteams.perform();
// }
// protected fetchProducts = task(async () => {
// try {
// let products = await this.fetchSvc
// .fetch("/api/v1/products")
// .then((resp) => resp?.json());
// this.products = products;
// } catch (err) {
// console.error(err);
// throw err;
// }
// });
protected fetchteams = task(async () => {
try {
// Filter the teams based on the selected business unit
console.log("parent injected value is: ",this.args.selectedBU)
let teams = await this.fetchSvc
.fetch("/api/v1/teams")
.then((resp) => resp?.json());
// Filter the teams based on the selected business unit
const filteredTeams: TeamAreas = {};
for (const team in teams) {
if (Object.prototype.hasOwnProperty.call(teams, team)) {
const teamData: TeamArea | undefined = teams[team];
if (teamData && teamData.BU === this.args.selectedBU) {
filteredTeams[team] = teamData;
}
}
}
console.log("the filtered teams are: ",filteredTeams);
this.teams = filteredTeams;
console.log(this.teams);
} catch (err) {
console.error(err);
throw err;
}
});
}
declare module "@glint/environment-ember-loose/registry" {
export default interface Registry {
"Inputs::TeamSelect": typeof InputsTeamSelectComponent;
}
}
型
任何一种指导都将受到高度赞赏!谢谢你,谢谢
我试过使用@traked,@did-update等,但似乎没有什么工作,即使我删除“did-insert”下拉菜单完全消失。
1条答案
按热度按时间7uhlpewt1#
感谢您发布了一个如此详细的问题和示例代码!
我认为这里的答案是 * 派生数据 *(你已经在做了!但是有些东西有点不对劲)。(对于这个答案的观察者来说)“衍生数据”的意思是,我们需要定义“拥有一个值意味着什么”,然后每个选择都基于此 * 衍生 *。
我在您的代码片段中注意到了两个主要的问题
Select
可以响应 * 任务 *,而不是您管理的属性(此策略不易出错,但不是 * 问题 *(如下所示))。@selectedBU
更改时重新获取数据,因此派生数据将使这一点变得非常容易--我们不想考虑“效果,”因为它使跟踪行为变得困难,也使以后的维护变得困难(即使在React/Vue/Svelte/Solid/etc中,我们也不想使用效果,而希望使用派生数据)。由于您提供的代码是特定于您的应用程序的(我无法运行),因此我制作了一个(简化的)演示,显示了您所描述的情况(再次感谢您对预期行为的详细描述)。
我已经使用open StarWars API来模拟请求数据,这些选择将从这些数据中填充。
interactive version is here的
下面是相关代码:
字符串
一些注解: