如何刷新绑定?

时间:2018-04-25 12:45:34

标签: javascript aurelia aurelia-binding

我需要知道如何刷新 Aurelia 中的绑定。我一直在"谷歌搜索"一段时间以来,但似乎无法找到答案。我需要刷新绑定的原因是因为在调用服务器以检索数据之后生成了一些html(带有click.delegate绑定)。我正在使用一些" 编辑"更新网格。和" 删除"纽扣。无论如何,当我使用 Durandal / KnockoutJS 时,我做了以下事情:

var body = this.element.find("tbody")[0];
if (body) {
    ko.cleanNode(body);
    ko.applyBindings(ko.dataFor(body), body);
}

我如何在Aurelia做同样的事情?

更新

感谢@ fred-kleuver的回复。我不确定这在我的情况下是否相关,对我想做的事情来说似乎有些过分。可能是我需要按照你的建议去做,但在我深入研究这一切之前,让我在这里详细说明我正在做的事情,因为你可能有一个更简单的解决方案: / p>

我正在使用Kendo UI(2014年初的旧GPL版本),遗憾的是它与Aurelia Kendo Bridge无法合作。因此,我必须自己初始化KendoGrid。我正在将代码复制到Aurelia的attached()生命周期方法:

$("#grid").kendoGrid({
    data: null,
    dataSource: {
        type: "odata",
        transport: {
            read: {
                url: this.apiUrl,
                dataType: "json"
            },
            parameterMap: function (options, operation) {
                var paramMap = kendo.data.transports.odata.parameterMap(options);
                if (paramMap.$inlinecount) {
                    if (paramMap.$inlinecount == "allpages") {
                        paramMap.$count = true;
                    }
                    delete paramMap.$inlinecount;
                }
                if (paramMap.$filter) {
                    paramMap.$filter = paramMap.$filter.replace(/substringof\((.+),(.*?)\)/, "contains($2,$1)");
                }
                return paramMap;
            }
        },
        schema: {
            data: function (data) {
                return data.value;
            },
            total: function (data) {
                return data["@odata.count"];
            },
            model: {
                fields: {
                    Name: { type: "string" }
                }
            }
        },
        pageSize: this.gridPageSize,
        serverPaging: true,
        serverFiltering: true,
        serverSorting: true,
        sort: { field: "Name", dir: "asc" }
    },
    dataBound: function (e) {
        var body = this.element.find("tbody")[0];
        if (body) {
            // TODO: Figure out how to do this in Aurelia
            //ko.cleanNode(body);
            //ko.applyBindings(ko.dataFor(body), body);
        }
    },
    filterable: true,
    sortable: {
        allowUnsort: false
    },
    pageable: {
        refresh: true
    },
    scrollable: false,
    columns: [{
        field: "Name",
        title: this.translations.columns.name,
        filterable: true
    }, {
        field: "Id",
        title: " ",
        template:
            '<div class="btn-group">' +
            '<button type="button" click.delegate="edit(#=Id#)" class="btn btn-default btn-xs">' + this.translations.edit + '</button>' +
            '<button type="button" click.delegate="remove(#=Id#)" class="btn btn-danger btn-xs">' + this.translations.delete + '</button>' +
            '</div>',
        attributes: { "class": "text-center" },
        filterable: false,
        width: 120
    }]
});

因此对于网格的dataBound函数,我希望Aurelia刷新它的绑定(以拾取每行按钮上的点击绑定)。

2 个答案:

答案 0 :(得分:3)

如果您正在生成html,则需要通过ViewCompiler传递它,以便处理所有绑定(以及自定义元素,属性等)并开始工作。

前段时间我写了一个自定义元素,我可以在视图中使用,然后通过可绑定属性将生成的html(以及绑定上下文)传递给它。这可能正是您所需要的,也可能是矫枉过正。它是生产代码,因此是所有try / catch的东西。

在后一种情况下,只关注我在render()方法中所做的事情,该方法包含编译,绑定和附加动态html的必要步骤。

TLDR:“肉”一直在底部,render()

import { bindingMode, createOverrideContext } from "aurelia-binding";
import { Container } from "aurelia-dependency-injection";
import { TaskQueue } from "aurelia-task-queue";
import { bindable, customElement, inlineView, ViewCompiler, ViewResources, ViewSlot } from "aurelia-templating";

@customElement("runtime-view")
@inlineView("<template><div></div></template>")
export class RuntimeView {
  @bindable({ defaultBindingMode: bindingMode.toView })
  public html: string;

  @bindable({ defaultBindingMode: bindingMode.toView })
  public context: any;

  public el: HTMLElement;
  public slot: ViewSlot;
  public bindingContext: any;
  public overrideContext: any;
  public isAttached: boolean;
  public isRendered: boolean;
  public needsRender: boolean;

  private tq: TaskQueue;
  private container: Container;
  private viewCompiler: ViewCompiler;

  constructor(el: Element, tq: TaskQueue, container: Container, viewCompiler: ViewCompiler) {
    this.el = el as HTMLElement;
    this.tq = tq;
    this.container = container;
    this.viewCompiler = viewCompiler;
    this.slot = this.bindingContext = this.overrideContext = null;
    this.isAttached = this.isRendered = this.needsRender = false;
  }

  public bind(bindingContext: any, overrideContext: any): void {
    this.bindingContext = this.context || bindingContext.context || bindingContext;
    this.overrideContext = createOverrideContext(this.bindingContext, overrideContext);

    this.htmlChanged();
  }

  public unbind(): void {
    this.bindingContext = null;
    this.overrideContext = null;
  }

  public attached(): void {
    this.slot = new ViewSlot(this.el.firstElementChild || this.el, true);
    this.isAttached = true;

    this.tq.queueMicroTask(() => {
      this.tryRender();
    });
  }

  public detached(): void {
    this.isAttached = false;

    if (this.isRendered) {
      this.cleanUp();
    }
    this.slot = null;
  }

  private htmlChanged(): void {
    this.tq.queueMicroTask(() => {
      this.tryRender();
    });
  }

  private contextChanged(): void {
    this.tq.queueMicroTask(() => {
      this.tryRender();
    });
  }

  private tryRender(): void {
    if (this.isAttached) {
      if (this.isRendered) {
        this.cleanUp();
      }
      try {
        this.tq.queueMicroTask(() => {
          this.render();
        });
      } catch (e) {
        this.tq.queueMicroTask(() => {
          this.render(`<template>${e.message}</template>`);
        });
      }
    }
  }

  private cleanUp(): void {
    try {
      this.slot.detached();
    } catch (e) {}
    try {
      this.slot.unbind();
    } catch (e) {}
    try {
      this.slot.removeAll();
    } catch (e) {}

    this.isRendered = false;
  }

  private render(message?: string): void {
    if (this.isRendered) {
      this.cleanUp();
    }

    const template = `<template>${message || this.html}</template>`;
    const viewResources = this.container.get(ViewResources) as ViewResources;
    const childContainer = this.container.createChild();
    const factory = this.viewCompiler.compile(template, viewResources);
    const view = factory.create(childContainer);

    this.slot.add(view);
    this.slot.bind(this.bindingContext, this.overrideContext);
    this.slot.attached();

    this.isRendered = true;
  }
}

用法(当然你使用变量而不是内联):

<runtime-view
    html.bind="'<some-element some-property.bind="value"></some-element>'"
    context.bind="{ value: 'text' }">
</runtime-view>

编辑:

好的,根据您更新的答案,您似乎在生成的html中没有任何html行为,因此您不需要调用生命周期。

如果不花费相当多的时间来获得与您相同的设置,我无法测试这一点,所以我会给你一些尝试:

(至于this.somethings,只需将第一个字母大写 - 它为您提供需要注入的Aurelia组件)

选项1

使用TemplatingEngine.enhance

dataBound: e => {
    const body = document.querySelector("#grid tbody");
    if (body) {
        this.templatingEngine.enhance({ element: body, bindingContext: this });
    }
}

选项2

手动增强tbody就地

dataBound: e => {
    const body = document.querySelector("#grid tbody");
    if (body) {
        const factory = this.viewCompiler.compile(body);
        factory.create(this.container, { enhance: true });
    }
}

选项3

完全取代身体的innerHTML

dataBound: e => {
    const body = document.querySelector("#grid tbody")
    if (body) {
        const html = body.innerHTML;
        body.innerHTML = "";
        const factory = this.viewCompiler.compile(html);
        const view = factory.create(this.container);
        const slot = new ViewSlot(body, true);
        slot.add(view);
    }
}

document.addEventListener

你已经基本上以你使用剑道的方式绕过了Aurelia,你甚至都没有数据绑定到任何东西。现在你正在建立自己的脆弱桥梁。

如果您使用的只是click.delegate,为什么不在按钮上使用.addEventListener("click", someFunction)

查找工作桥或不使用kendo

我确信在您的应用程序环境中有更清晰的方法可以实现这一点,但如果您不提供plunkr repro或类似的东西,则不可能提出任何“即时”建议。

但是如果你不能花太多时间学习Aurelia,我建议尝试找到开箱即用的组件。 aurelia-v-grid是本土Aurelia网格的一个很好的例子,它可能比半集成的剑道桥可以为你做更多的事情。

答案 1 :(得分:3)

我同意Fred关于使用addEventListener的论点。你只是尝试使用Aurelia来连接事件处理程序,我认为你的方法本身对这个问题来说太过分了。

由于您已经在使用jQuery,因此只需使用jQuery live事件来连接不断变化的网格DOM的事件句柄。

edit button为例,

在你的kendoGrid模板中

'<button data-id="#=Id#" class="edit-btn ..." type="button" >' + ...

在你的Aurelia组件中

@inject(Element)
export class YourComp {
  constructor(element) {
    this.element = element;
  }

  edit(id) { /* ... */ }

  attached() {
    // this can work across grid rebuild
    $(this.element).on('click', '.edit-btn', event => {
      this.edit(event.target.getAttribute('data-id');
    });
  }

  detached() {
    $(this.element).off('click', '.edit-btn');
  }
}