Как обновить привязки?
Мне нужно знать, как освежить привязки в Аурелии. Я был "Googling" в течение некоторого времени, но не могу найти ответ. Причина, по которой мне нужно обновить привязки, заключается в том, что некоторые из click.delegate
bindings) генерируется после обращения к серверу для получения данных. Я обновляю сетку с помощью кнопок "Редактировать" и "Удалить". Во всяком случае, когда я использовал Durandal / KnockoutJS, я сделал следующее:
var body = this.element.find("tbody")[0];
if (body) {
ko.cleanNode(body);
ko.applyBindings(ko.dataFor(body), body);
}
Как мне сделать то же самое в Аурелии?
ОБНОВИТЬ:
Спасибо @fred-kleuver за ваш ответ. Я не уверен, что это уместно в моем случае, и это кажется излишним для того, что я хочу сделать. Может быть, мне нужно сделать, как вы предложили, но прежде чем углубляться в выяснение всего этого, позвольте мне представить здесь более подробную информацию именно о том, что я делаю, поскольку у вас может быть более простое решение для меня:
Я использую Kendo UI (старая версия GPL с начала 2014 года), которая, к сожалению, не работает с Aurelia Kendo Bridge. Поэтому я должен сам инициализировать KendoGrid. Я копирую код следующим образом в Аурелию 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 ответа
Если вы генерируете html, вам нужно будет пропустить его через ViewCompiler, чтобы все привязки (и пользовательские элементы, атрибуты и т. Д.) Были обработаны и начали работать.
Некоторое время назад я написал пользовательский элемент, который мог бы использовать в представлении, а затем передать ему сгенерированный html (а также контекст привязки) через связываемое свойство. Это может быть только то, что вам нужно, или это может быть излишним. Это производственный код, отсюда и все, что нужно попробовать.
В последнем случае просто сосредоточиться на том, что я делаю в 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
Полностью заменить внутреннюю часть HTML
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
Вы уже в значительной степени обходите Аурелию тем, как используете Кендо, и вы даже не привязываете данные ни к чему. Теперь вы делаете свой собственный хрупкий мост.
Если все, что вы используете, это click.delegate
тогда почему бы просто не использовать .addEventListener("click", someFunction)
на кнопках?
Найти рабочий мост или не использовать кендо
Я уверен, что есть намного более чистые способы сделать это в контексте вашего приложения, но невозможно сделать какие-либо "точечные" предложения, если вы не предоставите репродукцию plunkr или что-то подобное.
Но я бы порекомендовал попробовать и найти компоненты, которые работают "из коробки", если вы не можете тратить слишком много времени на изучение Aurelia. aurelia-v-grid - отличный пример нативной сетки Aurelia, которая, вероятно, может сделать для вас гораздо больше, чем полуинтегрированный мост кендо.
Я согласен с аргументом Фреда об использовании addEventListener. Вы только пытаетесь использовать Aurelia для подключения обработчика событий, я думаю, что ваш подход сам по себе излишний для этой проблемы.
Поскольку вы уже используете jQuery, просто используйте живое событие jQuery, чтобы подключить дескриптор события для постоянно меняющейся 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');
}
}