2

<compose>我了解 Aurelia 的自定义元素与;的优缺点。Jeremy Danyow 的博客文章有所帮助。但是,我也想吃我的蛋糕

我想创建我也可以动态组合的自定义元素。由于<compose>需要不同的实例化,使用它意味着我需要为每个元素创建两个并行版本——一个<compose>用于静态调用,一个用于静态调用。例如,考虑以下用例:

<template>
  <h1>Welcome to the Data Entry Screen</h1>

  <!-- Static controls -->
  <my-textbox label="Your name:" value.bind="entry_name"></my-textbox>
  <my-datepicker label="Current date:" value.bind="entry_date"></my-datepicker>

  <!-- Loop through dynamic form controls -->
  <div class="form-group" repeat.for="control of controls" if.bind="control.type !== 'hidden'">
    <label class="control-label">${control.label}</label>
    <div>
      <compose containerless class="form-control"
        view-model="resources/elements/${control.type}/${control.type}" 
        model.bind="{'control': control, 'model': model, 'readonly': readonly}">
        </compose>
    </div>
  </div>
</template>

使用以下控件数据:

controls = [
  {label: 'Entry Date', type: 'my-datepicker', bind: 'acc_entry_date'},
  {label: 'Code', type: 'my-textbox', bind: 'acc_entry_code'},
  {label: 'Ref', type: 'my-textbox', bind: 'acc_entry_ref'},
  {label: 'Description', type: 'my-textarea', rows: '3', bind: 'acc_entry_description'},
  {label: 'Status', type: 'my-dropdown', bind: 'acc_entry_status', enum: 'AccountEntryStatus'},
  {type: 'hidden', bind: 'acc_entry_period_id'}];

如您所见,我想静态和动态地使用<my-textbox>and 。<my-datepicker>自定义元素绝对是最好的方法。但是,如果不创建两个并行组件,我看不出如何实现这一点——一个设计为自定义元素,一个设计为可组合的视图/视图模型。

4

3 回答 3

3

这个解决方案怎么样?在我的解决方案中,两个控件基本上是相同的,但在真正的解决方案中,它们会有不同的行为,但这是一个很好的起点。

这是一个例子:https ://gist.run?id=e6e980a88d7e33aba130ef91f55df9dd

应用程序.html

<template>
  <require from="./text-box"></require>
  <require from="./date-picker"></require>

  <div>
    Text Box
    <text-box value.bind="text"></text-box>
  </div>
  <div>
    Date Picker
    <date-picker value.bind="date"></date-picker>
  </div>

  <button click.trigger="reset()">Reset controls</button>

  <div>
    Dynamic controls:
    <div repeat.for="control of controls">
      ${control.label}
      <compose view-model="./${control.type}" model.bind="control.model" ></compose>
      <div>
        control.model.value = ${control.model.value}
      </div>
    </div>
  </div>

  <button click.trigger="changeModelDotValueOnTextBox()">Change model.value on text box</button>
  <button click.trigger="changeModelOnTextBox()">Change model.value on text box and then make a copy of the model</button>
</template>

应用程序.js

export class App {
  text = 'This is some text';
  date = '2017-02-28';

  controls = getDefaultControls();

  reset() {
    this.controls = getDefaultControls();
  }

  changeModelOnTextBox() {
    this.controls[1].model = {
      value: 'I changed the model to something else!'
    };
  }

  changeModelDotValueOnTextBox() {
    this.controls[1].model.value = 'I changed the model!';
  }
}

 function getDefaultControls(){
   return[
     {label: 'Entry Date', type: 'date-picker', model: { value: '2017-01-01' }},
     {label: 'Code', type: 'text-box', model: { value: 'This is some other text'}}
   ];
 }

日期选择器.html

<template>
  <input type="date" value.bind="value" />
</template>

日期选择器.js

import { inject, bindable, bindingMode, TaskQueue } from 'aurelia-framework';
import { ObserverLocator } from 'aurelia-binding'; 

@inject(Element, TaskQueue, ObserverLocator)
export class DatePicker {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) value;
  model = null;
  observerSubscription = null;

  constructor(el, taskQueue, observerLocator) {
    this.el = el;
    this.taskQueue = taskQueue;
    this.observerLocator = observerLocator;
  }

  activate(model) {
    if(this.observerSubscription) {
      this.observerSubscription.dispose();
    }

    this.model = model;

    this.observerSubscription = this.observerLocator.getObserver(this.model, 'value')
                                    .subscribe(() => this.modelValueChanged());
    this.hasModel = true;

    this.modelValueChanged();
  }

  detached() {
    if(this.observerSubscription) {
      this.observerSubscription.dispose();
    }
  }

  modelValueChanged() {
    this.guard = true;

    this.value = this.model.value;

    this.taskQueue.queueMicroTask(() => this.guard = false)
  }

  valueChanged() {

    if(this.guard == false && this.hasModel) {
      this.model.value = this.value;
    }
  }
}

文本框.html

<template>
  <input type="text" value.bind="value" />
</template>

文本框.js

import { inject, bindable, bindingMode, TaskQueue } from 'aurelia-framework';
import { ObserverLocator } from 'aurelia-binding'; 

@inject(Element, TaskQueue, ObserverLocator)
export class TextBox {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) value;
  model = null;
  observerSubscription = null;

  constructor(el, taskQueue, observerLocator) {
    this.el = el;
    this.taskQueue = taskQueue;
    this.observerLocator = observerLocator;
  }

  activate(model) {
    if(this.observerSubscription) {
      this.observerSubscription.dispose();
    }

    this.model = model;

    this.observerSubscription = this.observerLocator.getObserver(this.model, 'value')
                                    .subscribe(() => this.modelValueChanged());
    this.hasModel = true;

    this.modelValueChanged();
  }

  detached() {
    if(this.observerSubscription) {
      this.observerSubscription.dispose();
    }
  }

  modelValueChanged() {
    this.guard = true;

    this.value = this.model.value;

    this.taskQueue.queueMicroTask(() => this.guard = false)
  }

  valueChanged() {

    if(this.guard == false && this.hasModel) {
      this.model.value = this.value;
    }
  }
}
于 2017-02-28T20:23:08.683 回答
3

还有另一种策略,不确定它是否更好。您可以创建一个custom-compose以您想要的方式运行的行为。例如:

import { 
  bindable, 
  inlineView, 
  noView, 
  inject, 
  TemplatingEngine,
  bindingMode } from 'aurelia-framework';

@noView
@inject(Element, TemplatingEngine)
export class DynamicElement {

  @bindable type;
  @bindable({ defaultBindingMode: bindingMode.twoWay }) model;

  constructor(element, templatingEngine) {
    this.element = element;
    this.templatingEngine = templatingEngine;
  }

  bind(bindingContext, overrideContext) {
    this.element.innerHTML = `<${this.type} value.bind="model"></${this.type}>`;
    this.templatingEngine.enhance({ element: this.element, bindingContext: this });
  }

  detached() {
    this.element.firstChild.remove();
    this.view.detached();
    this.view.unbind();
    this.view = null;
  }
}

用法:

<div repeat.for="control of controls">
  ${control.label}
  <dynamic-element type.bind="control.type" model.bind="control.value"></dynamic-element>
  <div>
    control.value = ${control.value}
  </div>
</div>

我不舒服bindingContext: this。可能有更好的方法来做到这一点。

可运行示例https://gist.run/?id=827c72ec2062ec61adbfb0a72b4dac7d

你怎么看?

于 2017-03-01T19:58:58.277 回答
1

为了实现自定义元素的动态创建,我实现了一个元自定义元素,用于if.bind动态实例化正确的自定义元素(一般思路如下)。

元视图模型:

import {bindable} from 'aurelia-framework';

export class MyMetaElement {

  @bindable control;                // control definition object
  @bindable model;                  // data for binding
  @bindable readonly = false;       // flag to make controls view-only

}

元视图:

<template>

  <my-textbox if.bind="control.type == 'my-textbox" label.bind="control.label" value.bind="model[control.bind]" readonly.bind="readonly"></my-textbox>
  <my-datepicker if.bind="control.type == 'my-datepicker" label.bind="control.label" value.bind="model[control.bind]" readonly.bind="readonly"></my-datepicker>
  <my-textarea if.bind="control.type == 'my-textarea" label.bind="control.label" value.bind="model[control.bind]" rows.bind="control.rows" readonly.bind="readonly"></my-textarea>
  <my-dropdown if.bind="control.type == 'my-dropdown" label.bind="control.label" value.bind="model[control.bind]" enum.bind="control.enum" readonly.bind="readonly"></my-dropdown>

</template>

尽管动态创建控件似乎需要做很多额外的工作,但它比使用 具有很多优势<compose>,特别是因为自定义元素控件也可以在独立设置中使用(静态实例化)。

于 2017-02-28T16:00:46.913 回答