【问题标题】:Technique for jquery change events and aureliajquery 更改事件和 aurelia 的技术
【发布时间】:2017-02-23 20:33:30
【问题描述】:

我需要找到一个可靠的解决方案来让这两个框架很好地发挥作用。

使用 materialize-css,他们的 select 元素使用 jquery 来应用值更改。然而,这并不会触发 aurelia 看到变化。使用...的技术 $("select") .change((eventObject: JQueryEventObject) => { fireEvent(eventObject.target, "change"); }); 我可以触发 aurelia 看到的事件,但是,aurelia 然后在更新它的绑定时导致事件再次被触发,我最终陷入无限循环......堆栈溢出:D

在这方面让两者一起玩最可靠的方法是什么?

【问题讨论】:

标签: aurelia


【解决方案1】:

我使用 materialize-css + aurelia 已经有一段时间了,我可以确认来自 materialize 的 select 元素是相当有问题的。

我只是想在这里分享我的一个解决方案,以防有人需要更多示例。在这种情况下,Ashley's 可能更清洁。我的选项使用可绑定而不是插槽。

除了基本思想是相同的(使用保护变量和微任务)。

我在处理 3rd 方插件和双向数据绑定方面学到的一个教训是,它有助于在处理源自绑定目标(DOM 上的选择元素)的更改和更改之间进行更清晰、更明显的分离源自绑定源(例如包含该元素的页面的 ViewModel)。

我倾向于使用名称为 onValueChangedByBindingSourceonValueChangedByBindingTarget 的更改处理程序来处理将 ViewModel 与 DOM 同步的不同方式,从而减少代码混乱。

示例:https://gist.run?id=6ee17e333cd89dc17ac62355a4b31ea9

src/material-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/material-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();
        }
    }
}

【讨论】:

  • 老实说,考虑到在select 元素内处理slot 的愚蠢性,这或多或少是我可能实现的方式。话虽如此,我认为这两个答案都同样有效。你的展示了使用数据绑定来实现这一点,而我的展示了如何通过内容投影完成一些“不应该”实现的事情。
  • 你救了我的命,元素模式对我不起作用,但属性完美!非常感谢!
  • 只是一个小错误,我的数据来自 xhr 请求,如果来自另一个路由,则选择显示选择了错误的选项,但如果我按 F5 重新加载页面,它带有正确选择的选项。 ://
【解决方案2】:

好吧,我花了 完全 太长的时间来让这个问题按照我想要的方式回答,但稍后会详细介绍。停止无限循环的实际答案相当简单,所以我们先来看一下。您需要有一个保护属性,并且您需要使用 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。在这个要点中,我创建了一个自定义元素来包装 Materialize 选择功能。在创建这个时,我了解到select 元素和通过slot 元素进行的内容投影不会一起使用。因此,您将在代码中看到我必须做一些编码操作才能将选项元素从虚拟div 元素移动到select 元素中。我将提出一个问题,以便我们可以对此进行调查,看看这是框架中的错误还是仅仅是浏览器的限制。

通常,我强烈建议创建一个自定义元素来包装此功能。考虑到我必须编写的用于随机播放节点的代码,我不能说我强烈建议创建自定义元素。在这种情况下,我只是真的推荐它。

但无论如何,你去吧!

【讨论】:

  • 评论 slot 元素内的 select 元素。我提出了这个问题,这是否是 Aurelia 中的一个错误,或者只是浏览器处理事情的一部分。 Patrick Walters 提出了这个要点:gist.run/?id=799d9122a3e3727c343ecf9f5c4f9b1f 这表明这是浏览器,而不是 Aurelia。所以我想我们被这种愚蠢所困:-(
  • 在这种情况下您不需要as-element,就像您对表格一样吗?由于as-element 和插槽不能很好地结合在一起(afaik),这可能是compose 可以解决问题的情况之一——即使我知道你真的不喜欢那样;)
  • slot 元素在内部未作为“普通”Aurelia 自定义元素实现。它很特别,所以as-element 在这里不会真正起作用。关于使用compose,你介意分叉我的演示来说明你的意思吗?
  • 它仍然搞砸了绑定,但这里是要点,所以你可以看到我(有点)在尝试什么:gist.run/?id=d4bd1c4962a9f3670733e498e587ba9a我已经尝试了多种方法,但它似乎只有在整个 html (select + options) 会立即渲染,并且在渲染完成后 初始化下拉菜单时。 Compose 没有给出任何信号,所以我需要自己的“compose”并稍作调整。标记是如此简单,在这种情况下,自定义属性不是更容易和更清晰吗?
猜你喜欢
  • 2011-05-09
  • 1970-01-01
  • 1970-01-01
  • 2015-02-14
  • 1970-01-01
  • 2011-07-09
  • 1970-01-01
  • 1970-01-01
  • 2011-12-10
相关资源
最近更新 更多