Как обновить привязки? - PullRequest
       27

Как обновить привязки?

0 голосов
/ 25 апреля 2018

Мне нужно знать, как обновить привязки в Aurelia . Я был "Googling" в течение некоторого времени, но не могу найти ответ. Причина, по которой мне нужно обновить привязки, заключается в том, что некоторые html (с привязками click.delegate) создаются после обращения к серверу для получения данных. Я обновляю сетку с помощью некоторых кнопок " Edit " и " Delete ". В любом случае, когда я использовал Durandal / KnockoutJS , я делал следующее:

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

Как мне сделать то же самое в Аурелии?

UPDATE:

Спасибо @ fred-kleuver за ваш ответ. Я не уверен, что это уместно в моем случае, и это кажется излишним для того, что я хочу сделать. Может быть, мне нужно сделать, как вы предложили, но прежде чем углубляться в выяснение всего этого, позвольте мне представить здесь более подробную информацию о том, что именно я делаю, поскольку у вас может быть более простое решение для меня:

Я использую Kendo UI (старая версия GPL с начала 2014 года), который, к сожалению, не работает с 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 голосов
/ 01 мая 2018

Я согласен с аргументом Фреда об использовании 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');
  }
}
0 голосов
/ 25 апреля 2018

Если вы генерируете 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>

EDIT:

Хорошо, на основании вашего обновленного ответа кажется, что в сгенерированном 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, которая, вероятно, может сделать для вас гораздо больше, чем полуинтегрированный мост кендо.

...