Как ввести / заменить часть вида и посмотреть модель в Аурелии - PullRequest
0 голосов
/ 05 мая 2018

Я перехожу с KnockoutJS на Aurelia и с трудом пытаюсь понять, как я могу обменять некоторые HTML / JS из представления. У меня есть какой-нибудь существующий код нокаута:

$.ajax({
    url: "/admin/configuration/settings/get-editor-ui/" + replaceAll(self.type(), ".", "-"),
    type: "GET",
    dataType: "json",
    async: false
})
.done(function (json) {

    // Clean up from previously injected html/scripts
    if (typeof cleanUp == 'function') {
        cleanUp(self);
    }

    // Remove Old Scripts
    var oldScripts = $('script[data-settings-script="true"]');

    if (oldScripts.length > 0) {
        $.each(oldScripts, function () {
            $(this).remove();
        });
    }

    var elementToBind = $("#form-section")[0];
    ko.cleanNode(elementToBind);

    var result = $(json.content);

    // Add new HTML
    var content = $(result.filter('#settings-content')[0]);
    var details = $('<div>').append(content.clone()).html();
    $("#settings-details").html(details);

    // Add new Scripts
    var scripts = result.filter('script');

    $.each(scripts, function () {
        var script = $(this);
        script.attr("data-settings-script", "true");//for some reason, .data("block-script", "true") doesn't work here
        script.appendTo('body');
    });

    // Update Bindings
    // Ensure the function exists before calling it...
    if (typeof updateModel == 'function') {
        var data = ko.toJS(ko.mapping.fromJSON(self.value()));
        updateModel(self, data);
        ko.applyBindings(self, elementToBind);
    }

    //self.validator.resetForm();
    switchSection($("#form-section"));
})
.fail(function (jqXHR, textStatus, errorThrown) {
    $.notify(self.translations.getRecordError, "error");
    console.log(textStatus + ': ' + errorThrown);
});

В приведенном выше коде self.type(), передаваемое в URL для запроса AJAX, является именем некоторых настроек. Вот пример некоторых настроек:

public class DateTimeSettings : ISettings
{
    public string DefaultTimeZoneId { get; set; }

    public bool AllowUsersToSetTimeZone { get; set; }

    #region ISettings Members

    public string Name => "Date/Time Settings";

    public string EditorTemplatePath => "Framework.Web.Views.Shared.EditorTemplates.DateTimeSettings.cshtml";

    #endregion ISettings Members
}

Я использую это свойство EditorTemplatePath, чтобы отобразить это представление и вернуть его в запросе AJAX. Пример просмотра настроек выглядит следующим образом:

@using Framework.Web
@using Framework.Web.Configuration
@inject Microsoft.Extensions.Localization.IStringLocalizer T

@model DateTimeSettings

<div id="settings-content">
    <div class="form-group">
        @Html.LabelFor(m => m.DefaultTimeZoneId)
        @Html.TextBoxFor(m => m.DefaultTimeZoneId, new { @class = "form-control", data_bind = "value: defaultTimeZoneId" })
        @Html.ValidationMessageFor(m => m.DefaultTimeZoneId)
    </div>
    <div class="checkbox">
        <label>
            @Html.CheckBoxFor(m => m.AllowUsersToSetTimeZone, new { data_bind = "checked: allowUsersToSetTimeZone" }) @T[FrameworkWebLocalizableStrings.Settings.DateTime.AllowUsersToSetTimeZone]
        </label>
    </div>
</div>

<script type="text/javascript">
    function updateModel(viewModel, data) {
        viewModel.defaultTimeZoneId = ko.observable("");
        viewModel.allowUsersToSetTimeZone = ko.observable(false);

        if (data) {
            if (data.DefaultTimeZoneId) {
                viewModel.defaultTimeZoneId(data.DefaultTimeZoneId);
            }
            if (data.AllowUsersToSetTimeZone) {
                viewModel.allowUsersToSetTimeZone(data.AllowUsersToSetTimeZone);
            }
        }
    };

    function cleanUp(viewModel) {
        delete viewModel.defaultTimeZoneId;
        delete viewModel.allowUsersToSetTimeZone;
    }

    function onBeforeSave(viewModel) {
        var data = {
            DefaultTimeZoneId: viewModel.defaultTimeZoneId(),
            AllowUsersToSetTimeZone: viewModel.allowUsersToSetTimeZone()
        };

        viewModel.value(ko.mapping.toJSON(data));
    };
</script>

Теперь, если вы вернетесь к запросу AJAX и увидите, что я там делаю, это должно иметь больше смысла. Существует <div>, где я внедряю этот HTML-код следующим образом:

<div id="settings-details"></div>

Я пытаюсь понять, как это сделать в Аурелии. Я вижу, что могу использовать Aurelia templatingEngine.enhance({ element: elementToBind, bindingContext: this }); вместо Knockout ko.applyBindings(self, elementToBind);, поэтому я думаю, что это должно связать новые свойства с моделью представления. Однако я не знаю, что делать со скриптами из шаблонов редактора настроек. Я полагаю, что могу попытаться сохранить ту же логику, что у меня уже есть (используя jQuery для добавления / удаления скриптов и т. Д.) ... но я надеюсь, что с Aurelia есть более чистое / более элегантное решение для этого. Я посмотрел на slots, но я не думаю, что это применимо здесь, хотя я могу ошибаться.

1 Ответ

0 голосов
/ 05 мая 2018

Как обсуждалось в комментариях, мой ответ на ваш другой вопрос должен решить эту проблему здесь. Учитывая этот runtime-view элемент:

машинопись

    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;
      }
    }

ES6

    import { bindingMode, createOverrideContext } from "aurelia-binding";
    import { Container } from "aurelia-dependency-injection";
    import { TaskQueue } from "aurelia-task-queue";
    import { DOM } from "aurelia-pal";
    import { bindable, customElement, inlineView, ViewCompiler, ViewResources, ViewSlot } from "aurelia-templating";
    
    @customElement("runtime-view")
    @inlineView("<template><div></div></template>")
    @inject(DOM.Element, TaskQueue, Container, ViewCompiler)
    export class RuntimeView {
      @bindable({ defaultBindingMode: bindingMode.toView }) html;
      @bindable({ defaultBindingMode: bindingMode.toView }) context;
    
      constructor(el, tq, container, viewCompiler) {
        this.el = el;
        this.tq = tq;
        this.container = container;
        this.viewCompiler = viewCompiler;
        this.slot = this.bindingContext = this.overrideContext = null;
        this.isAttached = this.isRendered = this.needsRender = false;
      }
    
      bind(bindingContext, overrideContext) {
        this.bindingContext = this.context || bindingContext.context || bindingContext;
        this.overrideContext = createOverrideContext(this.bindingContext, overrideContext);
    
        this.htmlChanged();
      }
    
      unbind() {
        this.bindingContext = null;
        this.overrideContext = null;
      }
    
      attached() {
        this.slot = new ViewSlot(this.el.firstElementChild || this.el, true);
        this.isAttached = true;
    
        this.tq.queueMicroTask(() => {
          this.tryRender();
        });
      }
    
      detached() {
        this.isAttached = false;
    
        if (this.isRendered) {
          this.cleanUp();
        }
        this.slot = null;
      }
    
      htmlChanged() {
        this.tq.queueMicroTask(() => {
          this.tryRender();
        });
      }
    
      contextChanged() {
        this.tq.queueMicroTask(() => {
          this.tryRender();
        });
      }
    
      tryRender() {
        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>`);
            });
          }
        }
      }
    
      cleanUp() {
        try {
          this.slot.detached();
        } catch (e) {}
        try {
          this.slot.unbind();
        } catch (e) {}
        try {
          this.slot.removeAll();
        } catch (e) {}
    
        this.isRendered = false;
      }
    
      render(message) {
        if (this.isRendered) {
          this.cleanUp();
        }
    
        const template = `<template>${message || this.html}</template>`;
        const viewResources = this.container.get(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;
      }
    }

Вот несколько способов, которыми вы можете использовать его:

dynamicHtml - это свойство в ViewModel, содержащее произвольный сгенерированный html с любым видом привязок, пользовательскими элементами и другим поведением aurelia.

Он скомпилирует этот html-файл и свяжет его с привязкойContext, который он получит в bind() - который будет моделью представления представления, в котором вы его объявили.

<runtime-view html.bind="dynamicHtml">
</runtime-view>

С учетом someObject в виде модели:

this.someObject.foo = "bar";

А dynamicHtml вот так:

this.dynamicHtml = "<div>${foo}</div>";

Это будет отображаться так, как вы ожидаете в обычном представлении Aurelia:

<runtime-view html.bind="dynamicHtml" context.bind="someObject">
</runtime-view>

Повторное назначение html или context приведет к его повторной компиляции. Просто чтобы дать вам представление о возможных вариантах использования, я использую это в проекте с редактором monaco для динамического создания компонентов Aurelia из самого приложения Aurelia, и этот элемент даст предварительный просмотр в реальном времени (рядом с редактором) и также компилировать + визуализировать сохраненный html / js / json, когда я использую его в другом месте приложения.

...