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

当您使用 ember-data 时,这甚至不一定有效,因为isLoaded可能已经是真的(如果之前已经加载了记录并且没有再次从服务器请求)。所以要正确排序是很困难的。

最重要的是,您可能已经在您的视图模板中观看 isLoaded,如下所示:

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

并在您的控制器中再次执行此操作似乎是重复的。

我想出了一个稍微新颖的解决方案,但它要么需要工作,要么实际上是个坏主意……这两种情况都可能是真的:

我编写了一个名为 Handlebars 的小助手{{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()似乎不适用于控制器对象,尽管它可能适用于视图。有没有“Ember”的方式来做到这一点?
  3. 可能有些事情我不明白,例如会触发此事件但视图实际上不会添加到 DOM 的情况......?

正如您可能猜到的,我正在使用 Bootstrap 的 Typeahead 控件,并且我需要在<input>渲染后连接它,这实际上只发生{{#if}}在我的模板中的几个嵌套块评估为 true 之后。我也使用 jqPlot,所以我经常需要这种模式。这似乎是一个可行且有用的工具,但可能是我遗漏了一些使这种方法变得愚蠢的大图景。或者也许还有另一种方法没有出现在我的搜索中?

有人可以为我改进这种方法或告诉我为什么这是一个坏主意吗?

更新

我已经弄清楚了一些问题:

  1. 我可以通过 ...obvious 获得第一个“真实”包含视图options.data.view.get('parentView'),但我认为它不会那么简单。
  2. 您实际上可以obj.trigger(evtName)在任意对象上执行 jQuery 样式...但是该对象必须扩展Ember.Eventedmixin!所以我认为这是在 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 final 更新,我目前在 Ember 1.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}}更改{{trigger}}为更接近 Ember.Evented/jQuery 约定。这个更新的代码基于内置的 Ember{{action}}帮助程序,并且应该能够接受target="..."模板中的任何参数,就像现在一样{{action}}。它的不同之处{{action}}在于(除了在渲染模板部分时自动触发):

  1. 默认情况下将事件发送到视图。默认情况下发送到路由或控制器没有多大意义,因为这可能主要用于以视图为中心的操作(尽管我经常使用它将事件发送到控制器)。
  2. 使用 Ember.Evented 样式事件,因此要将事件发送到任意非视图对象(包括控制器),该对象必须扩展 Ember.Evented,并且必须注册一个侦听器。(要清楚,它不会在actions: {…}哈希中调用某些东西!)

请注意,如果您向 Ember.View 的实例发送事件,您所要做的就是实现一个同名的方法(请参阅文档代码)。但是如果您的目标不是视图(例如控制器),您必须在对象上注册一个监听器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 中可用,所以我可以附加一个 typeahead 到它:

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

完毕!

需要注意的是,当视图子部分已经在屏幕上时(例如,如果重新渲染父视图),这可能会再次发生。但在我的情况下,再次运行 typeahead 初始化无论如何都很好,如果需要的话,它会很容易检测和编码。在某些情况下可能需要这种行为。

我将此代码作为公共领域发布,不提供任何保证或承担任何责任。如果您想使用它,或者 Ember 的人们想将它包含在基线中,请继续!(我个人认为这是一个好主意,但这并不奇怪。)

于 2012-12-13T10:28:08.717 回答