Ember.js - 使用Handlebars帮助器检测子视图呈现 [英] Ember.js - Using a Handlebars helper to detect that a subview has rendered

查看:70
本文介绍了Ember.js - 使用Handlebars帮助器检测子视图呈现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有许多问题以某种方式提出:如何呈现某部分视图后,我该怎么做? (这里此处此处仅举几例)。答案通常是:

There are numerous questions that ask in one way or another: "How do I do something after some part of a view is rendered?" (here, here, and here just to give a few). The answer is usually:


  1. 使用 didInsertElement 在视图为原始渲染

  2. 使用 Ember.run.next(...)运行你的代码> 之后,如果需要访问创建的DOM元素,则视图更改将被刷新。

  3. isLoaded 或类似的属性在您需要的数据之后执行某些操作。

  1. use didInsertElement to run code when a view is initially rendered.
  2. use Ember.run.next(...) to run your code after the view changes are flushed, if you need to access the DOM elements that are created.
  3. use an observer on isLoaded or a similar property to do something after the data you need is loaded.

什么是刺激关于这一点,它会导致一些非常笨拙的东西:

What's irritating about this is, it leads to some very clumsy looking things like this:

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

而且,当您使用ember-数据因为 isLoaded 可能已经是真的(如果记录已经被加载,并且不再从服务器请求)。所以得到排序权是很难的。

And that doesn't really even necessarily work when you're using ember-data because isLoaded may already be true (if the record has already been loaded before and is not requested again from the server). So getting the sequencing right is hard.

除此之外,你可能已经在你的视图模板中看到了。

On top of that, you're probably already watching isLoaded in your view template like so:

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

并在控制器中再次执行,似乎重复。

and doing it again in your controller seems like duplication.

我想出了一个小小的解决方案,但它需要工作,或者实际上是一个坏主意...这两种情况都可以这样:

I came up with a slightly novel solution, but it either needs work or is actually a bad idea...either case could be true:

我写了一个小的Handlebars帮助器,名为 {{fire}} ,当执行包含句柄模板时,将使用自定义名称触发事件(即应该是每次重新呈现子视图?)。

I wrote a small Handlebars helper called {{fire}} that will fire an event with a custom name when the containing handlebars template is executed (i.e. that should be every time the subview is re-rendered, right?).

这是我的非常早期尝试:

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

这本质上是按原样工作的,但它有一对我已经知道的缺陷:

This essentially works as is, but it has a couple of flaws I know of already:


  1. 它调用控制器上的方法...它可能会更好,至少是>可以将事件发送到祖代视图对象,甚至可能使其成为默认行为。我尝试了 {{fire typeaheadHostDidRender target =view}} ,这没有工作。我看不到如何从被传递到帮助器中的当前视图,但显然, {{view}} 帮助者可以做到这一点。 li>
  2. 我猜想有一种更正式的方法来触发一个自定义事件,而不是我在这里做的,但是我还没有学到。 jQuery的 .trigger()在控制器对象上似乎不起作用,尽管它可能在视图上有效。有没有一个Ember的方法来实现?

  3. 可能会有一些我不明白的东西,比如这个事件将被触发,但是这个观点实际上不会要添加到DOM ...?

  1. It calls the method on the controller...it would probably be better to at least be able to send the "event" to the ancestor view object instead, perhaps even to make that the default behavior. I tried {{fire typeaheadHostDidRender target="view"}} and that didn't work. I can't see yet how to get the "current" view from what gets passed into the helper, but obviously the {{view}} helper can do it.
  2. I'm guessing there is a more formal way to trigger a custom event than what I'm doing here, but I haven't learned that yet. jQuery's .trigger() doesn't seem to work on controller objects, though it may work on views. Is there an "Ember" way to do this?
  3. There could be things I don't understand, like a case where this event would be triggered but the view wasn't in fact going to be added to the DOM...?

正如你可能猜到的,我使用的是Bootstrap的Typeahead控件,在< input> 呈现之后,我需要连线,实际上只有在几个嵌套的 {{#if}} blocks在我的模板中评估为true。我也使用jqPlot,所以我遇到了这种模式的需要很多。这似乎是一个可行和有用的工具,但可能是我错过了一些使这种方式愚蠢的大图。或者也许还有另一种方法可以在我的搜索中显示出来?

As you might be able to guess, I'm using Bootstrap's Typeahead control, and I need to wire it after the <input> is rendered, which actually only happens after several nested {{#if}} blocks evaluate to true in my template. I also use jqPlot, so I run into the need for this pattern a lot. This seems like a viable and useful tool, but it could be I'm missing something big picture that makes this approach dumb. Or maybe there's another way to do this that hasn't shown up in my searches?

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

Can someone either improve this approach for me or tell me why it's a bad idea?

我已经弄清楚了几点:


  1. 我可以使用 options.data.view.get('parentView')获取包含第一个真实视图 ...显然也许,但我不认为这样会很简单。

  2. 你实际上可以做一个jQuery风格的 obj.trigger(evtName)任何对象...但对象必须扩展 Ember.Evented mixin!所以我想是在Ember做这种事件发送的正确方法。只需确保目标扩展名为 Ember.Evented (视图已经执行)。

  1. I can get the first "real" containing view with options.data.view.get('parentView')...obvious perhaps, but I didn't think it would be that simple.
  2. You actually can do a jQuery-style obj.trigger(evtName) on any arbitrary object...but the object must extend the Ember.Evented mixin! So that I suppose is the correct way to do this kind of event sending in Ember. Just make sure the intended target extends Ember.Evented (views already do).

这是到目前为止的改进版本:

Here's the improved version so far:

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

现在我只是想知道如何传入预期的目标(例如控制器或视图 - 上述代码尝试猜测)。或者,弄清楚是否有一些意想不到的行为打破了整个概念。

Now just about all I'm missing is figuring out how to pass in the intended target (e.g. the controller or view--the above code tries to guess). Or, figuring out if there's some unexpected behavior that breaks the whole concept.

任何其他输入?

推荐答案

更新



为Ember 1.0最终更新,我目前在Ember 1.3.1上使用此代码。 >

UPDATED

Updated for Ember 1.0 final, I'm currently using this code on Ember 1.3.1.

好吧,我想我都知道了。这是完整的句柄助手:

Okay, I think I got it all figured out. Here's the "complete" handlebars helper:

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}} 不同之处在于(除了模板部分呈现之外还自动启动):

I changed the name from {{fire}} to {{trigger}} to more closely match Ember.Evented/jQuery convention. This updated code is based on the built-in Ember {{action}} helper, and should be able to accept any target="..." argument in your template, just as {{action}} does. Where it differs from {{action}} is (besides firing automatically when the template section is rendered):


  1. 默认情况下将事件发送到视图。默认情况下发送到路由或控制器不会有太多意义,因为这可能主要用于以视图为中心的操作(尽管我经常使用它来将事件发送到控制器)。

  2. 使用Ember.Evented样式事件,因此为了将事件发送到任意的非视图对象(包括控制器),对象必须扩展Ember.Evented,必须注册一个监听器。 (要清楚,它不会在操作中调用某些内容:{...} hash!)

  1. Sends the event to the view by default. Sending to the route or controller by default wouldn't make as much sense, as this should probably primarily be used for view-centric actions (though I often use it to send events to a controller).
  2. Uses Ember.Evented style events, so for sending an event to an arbitrary non-view object (including a controller) the object must extend Ember.Evented, and must have a listener registered. (To be clear, it does not call something in the actions: {…} hash!)

请注意,如果您将事件发送到Ember.View的实例,则只需执行相同名称的方法(请参阅 docs 代码)。但是如果您的目标不是视图(例如控制器),则必须使用 obj.on('evtName',function(evt){...}) Function.prototype.on 扩展名。

Note that if you send an event to an instance of Ember.View, all you have to do is implement a method by the same name (see docs, code). But if your target is not a view (e.g. a controller) you must register a listener on the object with obj.on('evtName', function(evt){...}) or the Function.prototype.on extension.

所以这里是一个真实的例子。我有一个使用以下模板的视图,使用Ember和Bootstrap:

So here's a real-world example. I have a view with the following template, using Ember and 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中可用,所以我可以附加一个类型头:

I needed to know when this element was available in the DOM, so I could attach a typeahead to it:

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

所以,我把一个 {{trigger}} helper in the same block:

So, I put a {{trigger}} helper in the same block:

{{#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 在我的视图类中:

And then implemented didRenderSubjectPicker in my view class:

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

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

});

完成!现在,当最终渲染模板的子部分(并且只有当)模板的子部分才会连线。注意实用程序的差异,当 main (或者可能是具体是正确的术语)视图被呈现时,使用didInsertElement ,而 didRenderSubjectPicker 在渲染视图的子部分时运行。

Done! Now the typeahead gets wired when (and only when) the sub-section of the template is finally rendered. Note the difference in utility, didInsertElement is used when the main (or perhaps "concrete" is the proper term) view is rendered, while didRenderSubjectPicker is run when the sub-section of the view is rendered.

如果我想将事件直接发送到相反,我只是将模板更改为:

If I wanted to send the event directly to the controller instead, I'd just change the template to read:

{{trigger didRenderSubjectPicker target=controller}}

并在我的控制器中执行此操作:

and do this in my controller:

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

完成!

一个警告是当视图子部分已经在屏幕上时(例如,如果父视图被重新呈现),这可能再次发生 。但是在我的情况下,再次运行typeahead初始化是很好的,如果需要的话,很容易检测和编码。在某些情况下可能需要这种行为。

The one caveat is that this may happen again when the view sub-section is already on screen (for example if a parent view is re-rendered). But in my case, running the typeahead initialization again is fine anyway, and it would be pretty easy to detect and code around if need be. And this behavior may be desired in some cases.

我将此代码发布为公有领域,不承担任何保证或责任。如果你想使用这个,或者Ember的人希望把它包含在基线中,那就去吧! (个人我认为这是一个好主意,但这并不奇怪。)

I'm releasing this code as public domain, no warranty given or liability accepted whatsoever. If you want to use this, or the Ember folks want to include it in the baseline, go right ahead! (Personally I think that would be a great idea, but that's not surprising.)

这篇关于Ember.js - 使用Handlebars帮助器检测子视图呈现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆