8

何らかの形で尋ねる質問はたくさんあります。「ビューの一部がレンダリングされた後、どうすればよいですか?」(ここここ、そしてここにいくつかあげるだけです)。答えは通常:

  1. ビューが最初にレンダリングされるdidInsertElementときにコードを実行するために使用します。
  2. 作成されたDOM要素にアクセスする必要がある場合は、ビューの変更がフラッシュされたEmber.run.next(...)にコードを実行するために使用します。
  3. 必要なデータがロードされた後、オブザーバーisLoadedまたは同様のプロパティを使用して何かを実行します。

これについてイライラするのは、次のような非常に不器用な外観につながることです。

didInsertElement: function(){
    content.on('didLoad', function(){
        Ember.run.next(function(){
            // now finally do my stuff
        });
    });
}

isLoadedまた、ember-dataを使用している場合は、すでにtrueである可能性があるため、必ずしも機能するとは限りません(レコードが以前にロードされており、サーバーから再度要求されていない場合)。したがって、シーケンスを正しく行うことは困難です。

その上、おそらくすでに次のようにビューテンプレートでisLoadedを監視しています。

{{#if content.isLoaded}}
    <input type="text" id="myTypeahead" data-provide="typeahead">
{{else}}
    <div>Loading data...</div>
{{/if}}

コントローラーでもう一度実行すると、重複しているように見えます。

私は少し斬新な解決策を思いつきましたが、それは作業が必要であるか、実際には悪い考えです...どちらの場合も当てはまる可能性があります。

含まれているハンドルバーテンプレートが実行されると、カスタム名でイベントを発生させるという小さなハンドルバーヘルパー{{fire}}を作成しました(つまり、サブビューが再レンダリングされるたびに実行する必要がありますよね?)。

これが私の非常に初期の試みです:

Ember.Handlebars.registerHelper('fire', function (evtName, options) {
    if (typeof this[evtName] == 'function') {
        var context = this;
        Ember.run.next(function () {
            context[evtName].apply(context, options);
        });
    }
});

これは次のように使用されます:

{{#if content.isLoaded}}
    {{fire typeaheadHostDidRender}}
    <input type="text" id="myTypeahead" data-provide="typeahead">
{{else}}
    <div>Loading data...</div>
{{/if}}

これは基本的にそのまま機能しますが、私がすでに知っているいくつかの欠陥があります。

  1. コントローラのメソッドを呼び出します...代わりに、少なくとも「イベント」を祖先ビューオブジェクトに送信できるようにする方が、おそらくデフォルトの動作にする方がよいでしょう。試し{{fire typeaheadHostDidRender target="view"}}ましたが、うまくいきませんでした。ヘルパーに渡されたものから「現在の」ビューを取得する方法はまだわかりませんが、明らかに{{view}}ヘルパーはそれを行うことができます。
  2. ここでやっていることよりも、カスタムイベントをトリガーするより正式な方法があると思いますが、まだそれを学びませんでした。jQuery.trigger()は、ビューでは機能する可能性がありますが、コントローラーオブジェクトでは機能しないようです。これを行うための「燃えさし」の方法はありますか?
  3. このイベントがトリガーされたが、ビューが実際にはDOMに追加されなかった場合など、私にはわからないことがあるかもしれません...?

ご想像のとおり、私はBootstrapのTypeaheadコントロールを使用しており、<input>レンダリング後にワイヤリングする必要があります。これは、実際には、テンプレートでいくつかのネストされた{{#if}}ブロックがtrueと評価された後にのみ発生します。私もjqPlotを使用しているので、このパターンの必要性に何度も遭遇します。これは実行可能で便利なツールのように見えますが、このアプローチを馬鹿にするような全体像が欠けている可能性があります。または、検索に表示されていない別の方法がありますか?

誰かが私のためにこのアプローチを改善するか、それが悪い考えである理由を教えてもらえますか?

アップデート

私はいくつかのビットを理解しました:

  1. 私は最初の「本物の」含有ビューをoptions.data.view.get('parentView')...おそらく明らかなもので得ることができますが、それがそれほど単純であるとは思いませんでした。
  2. obj.trigger(evtName)実際には、任意のオブジェクトに対してjQueryスタイルを実行できます...ただし、オブジェクトはEmber.Eventedミックスインを拡張する必要があります。そのため、Emberでこの種のイベント送信を行う正しい方法だと思います。目的のターゲットが拡張されていることを確認しEmber.Eventedてください(ビューはすでに拡張されています)。

これまでに改善されたバージョンは次のとおりです。

Ember.Handlebars.registerHelper('fire', function (evtName, options) {
    var view = options.data.view;
    if (view.get('parentView')) view = view.get('parentView');

    var context = this;
    var target = null;
    if (typeof view[evtName] == 'function') {
        target = view;
    } else if (typeof context[evtName] == 'function') {
        target = context;
    } else if (view.get('controller') && typeof view.get('controller')[evtName] == 'function') {
        target = view.get('controller');
    }

    if (target) {
        Ember.run.next(function () {
            target.trigger(evtName);
        });
    }
});

今私が見逃しているのは、目的のターゲットを渡す方法を理解することだけです(たとえば、コントローラーやビュー-上記のコードは推測しようとします)。または、全体の概念を破る予期しない動作があるかどうかを把握します。

他の入力はありますか?

4

1 に答える 1

16

更新しました

Ember 1.0ファイナル用に更新されました。現在、Ember1.3.1でこのコードを使用しています。

さて、私はそれをすべて理解したと思います。これが「完全な」ハンドルバーヘルパーです。

Ember.Handlebars.registerHelper('trigger', function (evtName, options) {
    // See http://stackoverflow.com/questions/13760733/ember-js-using-a-handlebars-helper-to-detect-that-a-subview-has-rendered
    // for known flaws with this approach

    var options = arguments[arguments.length - 1],
        hash = options.hash,
        hbview = options.data.view,
        concreteView, target, controller, link;

    concreteView = hbview.get('concreteView');

    if (hash.target) {
        target = Ember.Handlebars.get(this, hash.target, options);
    } else {
        target = concreteView;
    }

    Ember.run.next(function () {
        var newElements;
        if(hbview.morph){
            newElements = $('#' + hbview.morph.start).nextUntil('#' + hbview.morph.end)
        } else {
            newElements = $('#' + hbview.get('elementId')).children();
        }
        target.trigger(evtName, concreteView, newElements);
    });
});

名前をからに変更して、{{fire}}Ember.Evented {{trigger}}/jQueryの規則により厳密に一致させました。この更新されたコードは、組み込みのEmberヘルパーに基づいており、テンプレート内の任意の引数{{action}}を受け入れることができるはずです。異なる点は次のとおりです(テンプレートセクションがレンダリングされるときに自動的に起動する以外):target="..."{{action}}{{action}}

  1. デフォルトでは、イベントをビューに送信します。デフォルトでルートまたはコントローラーに送信することは、おそらく主にビュー中心のアクションに使用されるため、あまり意味がありません(ただし、コントローラーにイベントを送信するために使用することがよくあります)。
  2. Ember.Eventedスタイルのイベントを使用するため、任意の非ビューオブジェクト(コントローラーを含む)にイベントを送信するには、オブジェクトはEmber.Eventedを拡張しリスナーを登録する必要があります。actions: {…}(明確にするために、それはハッシュ内の何かを呼び出しません!)

Ember.Viewのインスタンスにイベントを送信する場合、必要なのは同じ名前のメソッドを実装することだけであることに注意してください(docscodeを参照)。ただし、ターゲットがビュー(コントローラーなど)でない場合は、オブジェクトobj.on('evtName', function(evt){...})またはFunction.prototype.on拡張機能にリスナーを登録する必要があります。

これが実際の例です。EmberとBootstrapを使用して、次のテンプレートを使用したビューがあります。

<script data-template-name="reportPicker" type="text/x-handlebars">
    <div id="reportPickerModal" class="modal show fade">
        <div class="modal-header">
            <button type="button" class="close" data-dissmis="modal" aria-hidden="true">&times;</button>
            <h3>Add Metric</h3>
        </div>
        <div class="modal-body">
            <div class="modal-body">
                <form>
                    <label>Report Type</label>
                    {{view Ember.Select 
                        viewName="selectReport" 
                        contentBinding="reportTypes"
                        selectionBinding="reportType"
                        prompt="Select"
                    }}
                    {{#if reportType}}
                        <label>Subject Type</label>
                        {{#unless subjectType}}
                            {{view Ember.Select 
                                viewName="selectSubjectType" 
                                contentBinding="subjectTypes"
                                selectionBinding="subjectType"
                                prompt="Select"
                            }}
                        {{else}}
                            <button class="btn btn-small" {{action clearSubjectType target="controller"}}>{{subjectType}} <i class="icon-remove"></i></button>
                            <label>{{subjectType}}</label>
                            {{#if subjects.isUpdating}}
                                <div class="progress progress-striped active">
                                    <div class="bar" style="width: 100%;">Loading subjects...</div>
                                </div>
                            {{else}}
                                {{#if subject}}
                                    <button class="btn btn-small" {{action clearSubject target="controller"}}>{{subject.label}} <i class="icon-remove"></i></button>
                                {{else}}
                                    {{trigger didRenderSubjectPicker}}
                                    <input id="subjectPicker" type="text" data-provide="typeahead">
                                {{/if}}
                            {{/if}}
                        {{/unless}}
                    {{/if}}
                </form>
            </div>
        </div>
        <div class="modal-footer">
            <a href="#" class="btn" data-dissmis="modal">Cancel</a>
            <a href="#" {{action didSelectReport target="controller"}} class="btn btn-primary">Add</a>
        </div>
    </div>
</script>

この要素がDOMでいつ利用可能になるかを知る必要があったので、それに先行入力を付けることができました。

<input id="subjectPicker" type="text" data-provide="typeahead">

だから、私{{trigger}}は同じブロックにヘルパーを置きました:

{{#if subject}}
    <button class="btn btn-small" {{action clearSubject target="controller"}}>{{subject.label}} <i class="icon-remove"></i></button>
{{else}}
    {{trigger didRenderSubjectPicker}}
    <input id="subjectPicker" type="text" data-provide="typeahead">
{{/if}}

そしてdidRenderSubjectPicker、私のビュークラスに実装されました:

App.ReportPickerView = Ember.View.extend({
    templateName: 'reportPicker',

    didInsertElement: function () {
        this.get('controller').viewDidLoad(this);
    }
    ,
    didRenderSubjectPicker: function () {
        this.get('controller').wireTypeahead();
        $('#subjectPicker').focus();
    }

});

終わり!これで、テンプレートのサブセクションが最終的にレンダリングされたとき(そしてそのときのみ)、先行入力が配線されます。ユーティリティの違いに注意してください。メイン(または「コンクリート」が適切な用語)ビューがレンダリングさdidInsertElementれるときに使用され、ビューのサブセクションがレンダリングされるときに実行されます。didRenderSubjectPicker

代わりに、イベントをコントローラーに直接送信したい場合は、テンプレートを次のように変更します。

{{trigger didRenderSubjectPicker target=controller}}

そして私のコントローラーでこれを行います:

App.ReportPickerController = Ember.ArrayController.extend({
    wireTypeahead: function(){
        // I can access the rendered DOM elements here
    }.on("didRenderSubjectPicker")
});

終わり!

1つの注意点は、ビューのサブセクションがすでに画面に表示されている場合(たとえば、親ビューが再レンダリングされた場合)にこれが再び発生する可能性があることです。しかし、私の場合、先行入力の初期化を再度実行することはとにかく問題なく、必要に応じて検出してコーディングするのは非常に簡単です。また、この動作が望ましい場合もあります。

私はこのコードをパブリックドメインとしてリリースしています。保証は与えられず、責任も負いません。これを使用したい場合、またはEmberの人々がベースラインに含めたい場合は、すぐに実行してください。(個人的にはそれは素晴らしいアイデアだと思いますが、それは驚くべきことではありません。)

于 2012-12-13T10:28:08.717 に答える