Ember.js — How do I target outlets in nested/repeated views, and what are the best practices for such a ui layout?

余生长醉 提交于 2019-12-02 16:01:18

Okay, after hours and hours of playing around with this, it seems best to conditionally include a named outlet in a level directly above my nested view.

This is because you need a single, reliable outlet to target within a template, and there doesn't seem to be a nice way to programmatically name an outlet at runtime.

Further, the outlet you hope to target needs to exist somewhere within a parent template in the nesting of routers, controllers, and rendered templates used to render the current state of the screen. If an outlet hasn't been put in place by a parent route object, you cannot hope to target it in your leaf-node route.

Let me explain with an example:

SInce I originally asked my question, our UI has changed from a list of devices to a list of people, but the principle remains the same.

I have a list of people with their photos, and they can be clicked to show some more info next to their result.

I have a people template that looks something like:

<script type="text/x-handlebars" data-template-name="people" >
    <h3>People in this group</h3>
    <div class="peeps">
        {{#each controller}}
             {{ view App.PersonView }}
             {{#if selected }}
                   <div class='too-personal'>
                   {{ outlet veryPersonalDetails }}
                   </div>
             {{/if}}
         {{/each}} 
    </div>
</script>

And a person template that's kinda like this:

<script type="text/x-handlebars" data-template-name="person" >
        {{#linkTo person this}}
            <div data-desc="person-item" class="item">
                <div class="portrait">
                    <img class="avatar" {{bindAttr src="gravatarUrl"}}/>
                </div>
                <div class="statuslabel"><span {{bindAttr class=":label statusLabel"}}>{{statusText}}</span></div>
                <div class="cTitle">{{fullName}}</div>
            </div>
        {{/linkTo}}     </script>

And the details template with the extra information and edit options

<script type="text/x-handlebars" data-template-name="person/details" > 
various edit options    
</script>

On clicking a person result, my router picks up the url update, and stubs in the details template into the parent people template. My router is set up something like this:

App.Router.map(function() {
...
    this.resource('people', { path: '/people'}, function() {
        this.resource('person', { path: '/:person_id' },
            function() {
                this.route('details');
            }
        );
    });

});

With the individual routes:

App.PeopleRoute = Ember.Route.extend({
    model: function() {
        return App.People.find();
    },
    setupController: function(controller, model) {
        controller.set('content', model);
    }
});

App.PersonRoute = Ember.Route.extend({
    model: function(params) {
        return App.People.peepsById[params.person_id];
    },

    setupController: function(controller, model) {
        this._super(controller, model);
        var person = model;
// this block ensures that only one person has a selected status at a time
// ignore the overly-verbose code. I'm cleaning it up tomorrow 
        var ls = App.People.lastSelected;
        if (ls) {
            ls.set('selected', false);
        }
        person.set('selected', true);
        App.People.lastSelected = person;
        controller.set('content', model);
    },

    renderTemplate: function() {
        // person is actually stored in router 'context' rather than 'model'
        // omg!
        var x = this.controllerFor('personDetails').set('content', this.get('context'));
        this.render('person/details', { // render person/details
            into:'people', // into the people template
            outlet: "veryPersonalDetails", // at the veryPersonalDetails outlet
            controller: x // using the personDetails controller for rendering
        });

// additional rendering in sidebar outlet for testing
        this.render('person/details',{
            into:'people',
            outlet: "personDetails",
            controller: x
        });

        console.log("@@@ we did it!");
    }
});

I originally tried to have the outlet within the PersonView, but this wouldn't work, as my person template was actually rendered within the people route. By the time the application reaches the person route, the controller has already rendered all the people in the list. The individual person I want to target has been passed by long ago.

By having the route act as an intermediary between the parent and desired child templates, I was able to make this happen.

Finally, regarding the question of whether to explicitly declare nested views within templates, or to just use outlets, I believe outlets are far cleaner. While this solution involved a bit of working around the routes, it's far preferable to having overly complicated template objects. Now I can hopefully achieve arbitrarily complex nestings of templates without touching anything but the routes involved in their rendering.

Further, this solution eliminates the overhead of unused outlets. I believe you should only have outlets you intend to use, rather than littering the 'dom' with a bunch of empty container objects.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!