Declarative ember component which passes in a list of objects to be rendered / bound, while passing state that nested components can access?


#1

I’m having a hard time figuring out how to build an ember component which has nested components which are rendered as a list based on an input list, while also allowing state to be passed in which is accessible from the nested components.

I put the details in a stack overflow question if anyone is up for taking a look:

Thanks, Best, David


#2

Heyo,

This sparked some recent discussion, and we’re still hammering things out, but we’ll have a solution to this shortly, possibly favoring a Ruby-blockish approach. The one road we don’t really want to go down is having to make everyone think about transclusion/scope even though that’s the temptation because it affords brevity in some cases. Also, you’re correct that restrict + inherited scopes is what presently makes your approach to the accordion possible in Angular in a way it isn’t strictly analogously possible in Ember, but child scopes have issues of their own and negative implications on testability that we’d like to avoid by going isolation all the way, possibly with Ruby block-ish params.

I will be less vague once the details come in, but thank you for getting the ball rolling on this discussion.


#3

Thanks Alex, that’s awesome :slight_smile: I created an issue on github for this: https://github.com/emberjs/ember.js/issues/4056 I may attempt a PR too.


#4

Hey everybody,

I am not sure if what I am trying to build is fully related to this question: I am trying to create nested objects which have some internal state depending on their type, and that state must be passed in from the “outer” objects. In the longer run, this state will be serialized in the URL using Ember’s router.

For that to work, I have created a mixin with which an Ember object can mark properties as to-be-serialized; and which exposes a state string which contains the full internal state of these properties.

I guess it’s somehow the “other way around” than your approach (if I understand it correctly): Instead of sharing “scope” from outside to inside, I built a generic abstraction of “inner state” to be passed to “outside”.

Maybe that helps anybody :slight_smile: Greets, Sebastian


#5

Hey @davidjnelson

Check this out: https://gist.github.com/machty/30dd8ea75096c79e0104

This is a collection of ideas for how to go forward and why we don’t want to futz with scope as a solution. Can you read it, see if it’s clear, and let me know any questions/concerns you’d have about constructing the Ember equivalent of angular-accordion

@skurfuerst I’m having trouble parsing your example; does it relate to the issue/challenge/question of writing Ember.Components that can yield content


#6

Thanks Alex, this looks sweet!!

So, let’s say I have a setup like this:

<!doctype html>
<html>
<body>

<script type="text/x-handlebars" data-template-name="components/ember-accordion">
    {{#each listOfAccordionPaneObjects}}
        {{yield}}
    {{/each}}
</script>

<script type="text/x-handlebars" data-template-name="components/ember-accordion-header">
    {{yield}}
</script>

<script type="text/x-handlebars" data-template-name="index">
    from outside the component: {{test}}
    {{#ember-accordion |model, test|}}
        <!-- question 1 --> {{model.firstName}}<br />
        <!-- question 2 --> {{test}}

        {{#ember-accordion-header |model, test|}}
            <!-- question 3 --> {{model.firstName}}<br />
            <!-- question 4 --> {{test}}
            
            {{#ember-accordion-dismiss-button}}
                <!-- question 5 --> {{model.firstName}}<br />
                <!-- question 6 --> {{test}}
            {{/ember-accordion-dismiss-button}}
        {{/ember-accordion-header}}
    {{/ember-accordion}}
</script>

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.2.1/handlebars.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ember.js/1.2.0/ember.debug.js"></script>

<script>
    var Person = Ember.Object.extend({
            firstName: '',
            lastName: ''
        }),
        App = Ember.Application.create(),
        people = [],
        i = 0;

    App.EmberAccordionComponent = Ember.Component.extend();
    App.EmberAccordionHeaderComponent = Ember.Component.extend();

    for(i; i < 10; i++) {
        people.push(Person.create({
            firstName: 'first ' + i.toString(),
            lastName: 'last ' + i.toString()
        }));
    }

    App.IndexRoute = Ember.Route.extend({
        model: function() {
            return people;
        }
    });

    App.IndexController = Ember.Controller.extend({
        init: function() {
            this._super();

            this.set('test', 'TEST WORKED!');
        }
    });
</script>

</body>
</html>

Would these be the values output?

question 1: 'first n'
question 2: 'TEST WORKED!'
question 3: 'first n'
question 4: 'TEST WORKED!'
question 5: ''
question 6: ''

If so, I think this is an extremely elegant solution to the problem and should cover pretty much any use case around scope and nested components, from the handlebars perspective.

The second other part that would be really useful is being able to access these values from the component objects themselves, ie:

App.EmberAccordionComponent.model === [{ firstName: 'first 1', lastName: 'last 1' } ... ]
App.EmberAccordionComponent.test === 'TEST WORKED!'

I haven’t looked at the code in ember that glues the templates to the component javascript classes, so I’m not sure how something like that would work at all.

And lastly, if the block scope was in effect as in the above, and I set:

App.EmberAccordionHeaderComponent.expanded === true

I could then read it from:

App.EmberAccordionComponent.expanded   and it would === true there too.

Thanks again Alex, this is really looking fantastic.