2017-02-28 69 views
1

我瞭解Aurelia自定義元素與<compose>的優缺點; Jeremy Danyow的blog post有幫助。但是,我想have my cake and eat it tooAurelia動態創建自定義元素,無需撰寫

我想創建自定義元素,我也可以動態編寫。因爲<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><my-datepicker>靜態和動態。自定義元素絕對看起來是最好的方法。然而,我沒有看到如何在不創建兩個並行組件的情況下實現這一點 - 一個設計爲自定義元素,另一個設計爲可組合視圖/視圖模型。

+0

'無容器'*搖頭和拍打LStarky在手腕上* maaaan多少次我得告訴你不要使用無容器,除非它是絕對必要的B/C的好理由(例如傳統的CSS) –

+0

如果我不' t使用無容器,Bootstrap在''周圍顯示一個可見的框。我不認爲它會影響綁定等。但無論如何,我真正的興趣在於可以靜態和動態實例化(並且與Aurelia驗證一起工作)的構建良好的自定義控件。 – LStarky

+0

那麼,你去了,你給了一個有理由使用'無容器':-) –

回答

3

這對於解決方案如何?在我的解決方案中,兩個控件基本上都是相同的,但是在一個真實的解決方案中,它們會有不同的行爲,但這是一個很好的起點。

下面是一個例子:https://gist.run?id=e6e980a88d7e33aba130ef91f55df9dd

app.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> 

app.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'}} 
    ]; 
} 

日期picker.html

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

日期picker.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> 

文本box.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; 
    } 
    } 
} 
+0

我正在通過這個建議,基本上似乎是創建混合組件作爲正常的自定義元素工作,但也可以通過撰寫來激活。我正確地認爲'activate(model)'只會在由''實例化時才被調用? – LStarky

+0

是的。這種策略的唯一真正限制是這些自定義元素不能也是路由器加載的頁面。儘管通過檢查第一個參數的類型來激活,也可以解決這個問題。 –

+0

@AshleyGrant對我的回答有什麼想法? –

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>,尤其是因爲自定義元素控件也可以在獨立設置(靜態實例化)中使用。

+0

這看起來效率很低,乍一看,IMO –

+0

是的,我同意。如何改進?從控制對象數組動態構建表單有什麼更好的方法? – LStarky

+0

我會發表我的想法作爲對這個問題的答案 –

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。可能有更好的方法來做到這一點。

Runnable的例子https://gist.run/?id=827c72ec2062ec61adbfb0a72b4dac7d

你覺得呢?

+0

我在這裏遇到的問題是這可能會導致內存泄漏,因爲實際上沒有API來拆卸「增強「元素。 –

+0

嗯......這非常重要。所以,在父元素分離之後,增強元素是否仍然在內存中?我們能做些什麼來清潔它嗎? –

+0

我記得看到一個關於我們沒有在「增強」元素上調用「分離」的問題。你必須自己調用這個方法。我會看看我能否找到它。 –