如何在 Ember.js 中的控制器之间进行通信 [英] How to communicate between controllers in Ember.js

查看:20
本文介绍了如何在 Ember.js 中的控制器之间进行通信的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个页面,其中左侧的固定视图(一些过滤器)适用于右侧的结果.

例如,左侧是过滤器,可按流派、标题、创作年份过滤电影....右侧是不同的图表和表格,它们会根据所选的过滤器进行更新.

所以我想在左边有一个固定的视图,然后在右边有一个会根据路线改变的出口.

这是正确的方法吗?如果是这样,我无法弄清楚如何将过滤器更改传达给右侧的控制器.

此 JSFiddle 显示了类似的设置:http://jsfiddle.net/DEHcK/2/

在 ContentRoute -> setupController 中,我得到了一个 NavigationController 的实例,但后来我不知道如何更改 someValue.

在上面的例子中,ContentController 如何获取 NavigationController 中 someValue 的变化?

这是实现我在 ember 中描述的应用程序的正确方法吗?

JavaScript:

<代码>window.App = Ember.Application.create();App.Router.map(函数(){this.route('内容');});App.ContentRoute = Ember.Route.extend({设置控制器:功能(控制器){controller.set('navigationController', this.controllerFor('navigation'));}});App.ContentController = Ember.ObjectController.extend({导航控制器:空,观察者的某些值:函数(){this.set('observedValue', this.get('navigationController.someValue'));}.observes('navigationController', 'navigationController.someValue'),观察值:空});App.IndexRoute = Ember.Route.extend({重定向:函数(){this.transitionTo('内容');}});App.NavigationView = Ember.View.extend({初始化:函数(){this._super();this.set('controller', this.get('parentView.controller').controllerFor('Navigation'));},模板名称:导航"});App.NavigationController = Ember.ObjectController.extend({someValue: 'xx',观察值:空,观察者的某些值:函数(){this.set('observedValue', this.someValue);}.observes('someValue')});

HTML:

<script type="text/x-handlebars" data-template-name="navigation" >更改 {{view Ember.TextField valueBinding="controller.someValue"}}<div>在同一视图中观察到的值:{{controller.observedValue}}</div><script type="text/x-handlebars" data-template-name="content" ><div style="margin-top: 2em">另一个视图中的观察值:{{observedValue}}

解决方案

我已经提前并且为您创建了一个 JSFiddle 具有您所追求的基本实现.我认为值得一试,这样你就可以掌握 Ember.

路由器

此时我们只是在配置我们的 IndexRoute,我们将在其中存储 {{outlet}} 的所有歌曲.

App.IndexRoute = Ember.Route.extend({设置控制器:功能(控制器){控制器.set('内容', [Ember.Object.create({ title: 'Stairway to Heaven', 类型: 'Metal' }),Ember.Object.create({ title: 'Ratts of the Capital', 类型: 'Post Rock' }),Ember.Object.create({ title: 'Wonderwall', 流派: 'Brit Pop' }),Ember.Object.create({ title: 'Last Flowers', 类型: 'Indie Rock' })]);}});

此代码很有可能会被对后端 Ruby/PHP 的某种 AJAX 调用替换.不过暂时,我们将让 IndexRoute 负责设置控制器(因此是 setupController).这个责任也可以由控制器本身承担,并且抽象出 AJAX 调用可能是一个好主意,因为您将有许多类似的 AJAX 调用.

您也可以决定使用 Ember 的 DataStore,这将再次更改控制器的实现.

索引控制器

接下来我们要设置我们的IndexController(实际上就是我们的SongsController),我们希望这个控制器有两个职责:

  1. 存储歌曲(也可能从后端获取歌曲);
  2. 根据传入的过滤器过滤歌曲.

为此,我们创建了一个计算属性来过滤掉内容,因为我们不想直接操作特殊的 content 数组.

App.IndexController = Ember.ArrayController.extend({内容: [],排除流派:[],过滤内容:函数(){var excludeThisGenres = this.get('excludeGenres').mapProperty('genre');返回 this.get('content').filter(function(model) {return Boolean(jQuery.inArray(model.get('genre'), excludeThisGenres) === -1);});}.property('excludeGenres.length', 'content.length')});

excludeGenres 将采用流派对象数组.例如,如果Post Rock"包含在 excludeGenres 中,那么我们将不会显示任何Post Rock"相关歌曲,但如果它不存在,那么我们将显示它们!IndexController 不负责维护这个数组,但它有责任过滤它的内容,以便在这个数组更新.

流派控制器

也许是我们小应用程序中最令人困惑的控制器,因为它实际上没有任何自己的内容,而是依赖于 IndexController 的内容.

App.GenresController = Ember.ObjectController.extend({需要:['索引'],流派:函数(){返回 this.get('controllers.index').mapProperty('genre').uniq();}.property('controllers.index.length'),切换:功能(流派名称){var indexController = this.get('controllers.index'),流派 = indexController.get('excludeGenres'),流派 = indexController.findProperty('流派', 流派名称);如果(流派.findProperty('流派',流派名称)){流派.removeObject(流派);返回;}流派.pushObject(流派);}});

流派控制者的职责可以这样定义:

  1. 监控IndexControllercontent数组,并在其长度变化时获取唯一的流派名称;
  2. 当用户单击列表中的流派以包含/排除它时填充 excludeGenres 数组.

要调用 toggle 方法,我们需要在我们的流派视图中指定一个操作以在单击时调用它:{{genre}}</a>.现在每当用户点击一个流派,toggle 方法就会被调用,流派名称作为第一个参数传递.

一旦我们进入 toggle 方法,它将确定是否已经排除了流派.如果它被排除在外,那么它将被删除,反之亦然.一旦我们添加/删除了流派,filteredContent 计算属性将再次被触发,并且索引视图将无缝更新.

GenresController 实际上没有自己的内容的原因是,当两者有关系时管理两个 content 数组似乎很愚蠢.由于可以从应用程序中出现的歌曲中确定流派,因此控制器可以从该列表中收集信息,然后提取所需的信息——在我们的例子中是流派.

这样,如果添加/删除歌曲,则流派列表可以保持同步.

结论

我认为您最初的问题的答案是,为了让控制器相互通信,您需要指定它需要(使用needs代码>).

I'd like to create a page where on the left I have fixed view (some filters) which are applied to results on the right.

For example on the left are filters to filter movies by genre, title, year of creation.... On the right are different charts and tables, which update based on the filters selected.

So I was thinking of having a fixed view on the left, then on the right an outlet which would change based on the route.

Is this the correct way? If it is, I can't figure out how to communicate filter changes to controller on the right.

This JSFiddle shows a similar setup : http://jsfiddle.net/DEHcK/2/

In the ContentRoute -> setupController I get an instance of NavigationController, but then I can't figure out how to get changes to someValue.

In the example above, how can ContentController get the changes to someValue in NavigationController?

Is this the correct way to implement the app I described in ember?

JavaScript:


    window.App = Ember.Application.create();

    App.Router.map(function () {
        this.route('content');
    });

    App.ContentRoute = Ember.Route.extend({
        setupController: function (controller) {
            controller.set('navigationController', this.controllerFor('navigation'));
        }
    });

    App.ContentController = Ember.ObjectController.extend({
        navigationController: null,
        observerOfSomeValue: function () {
            this.set('observedValue', this.get('navigationController.someValue'));
        }.observes('navigationController', 'navigationController.someValue'),
        observedValue: null
    });

    App.IndexRoute = Ember.Route.extend({
        redirect: function () {
            this.transitionTo('content');
        }
    });

    App.NavigationView = Ember.View.extend({
        init: function () {
            this._super();
            this.set('controller', this.get('parentView.controller').controllerFor('Navigation'));
        },
        templateName: "navigation"
    });

    App.NavigationController = Ember.ObjectController.extend({
        someValue: 'xx',
        observedValue: null,
        observerOfSomeValue: function () {
            this.set('observedValue', this.someValue);
        }.observes('someValue')
    });

HTML:

<script type="text/x-handlebars" data-template-name="application" >
    <div>
        {{view App.NavigationView}}
    </div>
    <div>
        {{ outlet }}
    </div>
</script>

<script type="text/x-handlebars" data-template-name="navigation" >
    Change {{view Ember.TextField valueBinding="controller.someValue"}}
    <div>observed value in same view: {{controller.observedValue}}</div>
</script>

<script type="text/x-handlebars" data-template-name="content" >
    <div style="margin-top: 2em">
        observed value in another view: {{observedValue}}
    </div>
</script>

解决方案

I've gone ahead and created you a JSFiddle with a basic implementation of what you're after. I think it's worth running through it all though so that you can get a grip of Ember.

Router

We're just configuring our IndexRoute at this point where we store all of our songs for the {{outlet}}.

App.IndexRoute = Ember.Route.extend({
    setupController: function(controller) {
        controller.set('content', [
            Ember.Object.create({ title: 'Stairway to Heaven', genre: 'Metal' }),
            Ember.Object.create({ title: 'Ratts of the Capital', genre: 'Post Rock' }),
            Ember.Object.create({ title: 'Wonderwall', genre: 'Brit Pop' }),
            Ember.Object.create({ title: 'Last Flowers', genre: 'Indie Rock' })
        ]);
    }
});

There's a good chance this code will be replaced with an AJAX call of some sort to your back-end Ruby/PHP. For the time being though, we'll give the IndexRoute the responsibility of setting up the controller (hence setupController). This responsibility could just as well lie on the controller itself, and is probably a good idea to abstract out the AJAX call since you'll have many similar AJAX calls.

You may also decide to use Ember's DataStore, which will change the implementation of the controller again.

Index Controller

Next we're going to setup our IndexController (which is our SongsController really), we want this controller to have two responsibilities:

  1. To store the songs (and perhaps to fetch the songs as well from the back-end);
  2. To filter songs based on filters that are passed into it.

For this we create a computed property to filter out the content, since we don't want to actually manipulate the special content array directly.

App.IndexController = Ember.ArrayController.extend({
    content: [],
    excludeGenres: [],

    filteredContent: function() {
        var excludeTheseGenres = this.get('excludeGenres').mapProperty('genre');
        return this.get('content').filter(function(model) {
            return Boolean(jQuery.inArray(model.get('genre'), excludeTheseGenres) === -1);
        });
    }.property('excludeGenres.length', 'content.length')
});

The excludeGenres will take an array of genre objects. For example, if "Post Rock" is contained within excludeGenres, then we won't show any "Post Rock" related songs, but if it isn't present, then we we'll show them! The IndexController doesn't have the responsibility of maintaining this array, but it does have the responsibility of filtering its content for when this array is updated.

Genres Controller

Perhaps the most confusing controller in our little application, because it doesn't actually have any content of its own, but rather depends on the IndexController's content.

App.GenresController = Ember.ObjectController.extend({
    needs: ['index'],

    genres: function() {
        return this.get('controllers.index').mapProperty('genre').uniq();        
    }.property('controllers.index.length'),

    toggle: function(genreName) {
        var indexController     = this.get('controllers.index'),
            genres              = indexController.get('excludeGenres'),
            genre               = indexController.findProperty('genre', genreName);

        if (genres.findProperty('genre', genreName)) {
            genres.removeObject(genre);
            return;
        }

        genres.pushObject(genre);
    }
});

Genre controller's responsibilities can be defined as so:

  1. To monitor the content array of the IndexController and fetch the unique genre names when its length changes;
  2. To populate the excludeGenres array when a user clicks on a genre in the list to include/exclude it.

To have the toggle method called, we need to specify an action in our genre view to invoke it when clicked: <a {{action "toggle" genre}}>{{genre}}</a>. Now whenever a user clicks on a genre, the toggle method will be invoked, and the genre name passed as the first argument.

Once we're in the toggle method, it will determine whether the genre is already being excluded. If it is being excluded, then it will be removed, and vice-versa. Once we've added/removed the genre, the filteredContent computed property will be fired again, and the index view will be updated seamlessly.

The reason the GenresController doesn't actually have its own content is that it would seem silly to manage two content arrays when the two have a relationship. Since genres can be determined from the songs present in the application, the controller can gather the information from that list, and just pull out the information it requires -- in our case, genres.

This way, if a song is added/removed, then the genre list can be kept in sync.

Conclusion

I think the answer to your original question is, however, that in order for controllers to be communicating with one another, you need to be specifying what it needs (with needs).

这篇关于如何在 Ember.js 中的控制器之间进行通信的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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