我需要找到一个可靠的解决方案,让两个框架很好地发挥作用。
使用materialize-css,他们的select元素使用jquery来应用值更改。然而,这不会触发aurelia看到变化。使用...的技术
$("select")
.change((eventObject: JQueryEventObject) => {
fireEvent(eventObject.target, "change");
});
我可以触发aurelia看到的事件,然而,aurelia会导致事件在更新它的绑定时再次被触发,我最终会进入无限循环.... Stack Overflow:D
在这方面,让两人一起玩的最可靠的方法是什么?
答案 0 :(得分:4)
我已经使用了materialize-css + aurelia一段时间了,我可以确认来自物化的select元素是非常有问题的。
我只是想在这里分享我的一个解决方案,以防有人想要一些额外的例子。在这种情况下,阿什利可能更干净。我使用可绑定的选项而不是插槽。
除此之外,基本思想是相同的(使用保护变量和微任务)。
我在处理第三方插件和双向数据绑定时学到的一个教训是,它有助于在处理源自绑定目标(DOM上的select元素)的更改和更改之间进行更清晰,明确的分离源自绑定源(例如包含该元素的页面的ViewModel)。
我倾向于使用名称为onValueChangedByBindingSource
和onValueChangedByBindingTarget
的更改处理程序来处理将ViewModel与DOM同步的不同方式,从而减少代码混淆。
示例:https://gist.run?id=6ee17e333cd89dc17ac62355a4b31ea9
<强>的src /材料select.html 强>
<template>
<div class="input-field">
<select value.two-way="value" id="material-select">
<option repeat.for="option of options" model.bind="option">
${option.displayName}
</option>
</select>
</div>
</template>
<强>的src /材料select.ts 强>
import {
customElement,
bindable,
bindingMode,
TaskQueue,
Disposable,
BindingEngine,
inject,
DOM
} from "aurelia-framework";
@customElement("material-select")
@inject(DOM.Element, TaskQueue, BindingEngine)
export class MaterialSelect {
public element: HTMLElement;
public selectElement: HTMLSelectElement;
@bindable({ defaultBindingMode: bindingMode.twoWay })
public value: { name: string, value: number };
@bindable({ defaultBindingMode: bindingMode.oneWay })
public options: { displayName: string }[];
constructor(
element: Element,
private tq: TaskQueue,
private bindingEngine: BindingEngine
) {
this.element = element;
}
private subscription: Disposable;
public isAttached: boolean = false;
public attached(): void {
this.selectElement = <HTMLSelectElement>this.element.querySelector("select");
this.isAttached = true;
$(this.selectElement).material_select();
$(this.selectElement).on("change", this.handleChangeFromNativeSelect);
this.subscription = this.bindingEngine.collectionObserver(this.options).subscribe(() => {
$(this.selectElement).material_select();
});
}
public detached(): void {
this.isAttached = false;
$(this.selectElement).off("change", this.handleChangeFromNativeSelect);
$(this.selectElement).material_select("destroy");
this.subscription.dispose();
}
private valueChanged(newValue, oldValue): void {
this.tq.queueMicroTask(() => {
this.handleChangeFromViewModel(newValue);
});
}
private _suspendUpdate = false;
private handleChangeFromNativeSelect = () => {
if (!this._suspendUpdate) {
this._suspendUpdate = true;
let event = new CustomEvent("change", {
bubbles: true
});
this.selectElement.dispatchEvent(event)
this._suspendUpdate = false;
}
}
private handleChangeFromViewModel = (newValue) => {
if (!this._suspendUpdate) {
$(this.selectElement).material_select();
}
}
}
修改强>
自定义属性怎么样?
要点:https://gist.run?id=b895966489502cc4927570c0beed3123
<强>的src / app.html 强>
<template>
<div class="container">
<div class="row"></div>
<div class="row">
<div class="col s12">
<div class="input-element" style="position: relative;">
<select md-select value.two-way="currentOption">
<option repeat.for="option of options" model.bind="option">${option.displayName}</option>
</select>
<label>Selected: ${currentOption.displayName}</label>
</div>
</div>
</div>
</div>
</template>
<强>的src / app.ts 强>
export class App {
public value: string;
public options: {displayName: string}[];
constructor() {
this.options = new Array<any>();
this.options.push({ displayName: "Option 1" });
this.options.push({ displayName: "Option 2" });
this.options.push({ displayName: "Option 3" });
this.options.push({ displayName: "Option 4" });
}
public attached(): void {
this.value = this.options[1];
}
}
<强>的src / MD-select.ts 强>
import {
customAttribute,
bindable,
bindingMode,
TaskQueue,
Disposable,
BindingEngine,
DOM,
inject
} from "aurelia-framework";
@inject(DOM.Element, TaskQueue, BindingEngine)
@customAttribute("md-select")
export class MdSelect {
public selectElement: HTMLSelectElement;
@bindable({ defaultBindingMode: bindingMode.twoWay })
public value;
constructor(element: Element, private tq: TaskQueue) {
this.selectElement = element;
}
public attached(): void {
$(this.selectElement).material_select();
$(this.selectElement).on("change", this.handleChangeFromNativeSelect);
}
public detached(): void {
$(this.selectElement).off("change", this.handleChangeFromNativeSelect);
$(this.selectElement).material_select("destroy");
}
private valueChanged(newValue, oldValue): void {
this.tq.queueMicroTask(() => {
this.handleChangeFromViewModel(newValue);
});
}
private _suspendUpdate = false;
private handleChangeFromNativeSelect = () => {
if (!this._suspendUpdate) {
this._suspendUpdate = true;
const event = new CustomEvent("change", { bubbles: true });
this.selectElement.dispatchEvent(event)
this.tq.queueMicroTask(() => this._suspendUpdate = false);
}
}
private handleChangeFromViewModel = (newValue) => {
if (!this._suspendUpdate) {
$(this.selectElement).material_select();
}
}
}
答案 1 :(得分:3)
好吧,我花了完全太长时间让这个人按照我想要的方式回答,但稍后会更多。停止无限循环的实际答案非常简单,所以我们先来看看它。你需要有一个警卫财产,你需要使用Aurelia的TaskQueue
帮助取消警卫财产。
您的代码看起来会像这样:
$(this.selectElement).change(evt => {
if(!this.guard) {
this.guard = true;
const changeEvent = new Event('change');
this.selectElement.dispatchEvent(changeEvent);
this.taskQueue.queueMicroTask(() => this.guard = false);
}
});
请注意,我正在使用排队微型任务取消防守。这可以确保一切都按您想要的方式运作。
现在我们已经解决了这个问题,让我们看一下我创建的要点here。在这个要点中,我创建了一个自定义元素来包装Materialise选择功能。在创建此内容时,我了解到select
元素和通过slot
元素进行的内容投影不会合在一起。因此,您将在代码中看到我必须执行一些编码体操,以将选项元素从虚拟div
元素移动到select
元素。我要提交一个问题,以便我们可以查看这个问题,看看这是框架中的错误还是浏览器的限制。
通常,我强烈建议您创建一个自定义元素来包装此功能。鉴于我必须编写的代码随机播放节点,我不能说我高度建议创建自定义元素。我只是在这种情况下推荐它。
但无论如何,你去了!