Toggle dynamically generated elements in template


#1

Let’s say I’ve got a list of students. I’m trying to figure out a way to show only a specific student in the list if one of the filter buttons is used. I’m not sure what property to even check for as indicated by the ??? below or how to really setup the action. This is what I’ve got so far. I’m not even sure if I’m on the right track.

//In a .hbs template
{{#each students as |student|}}
  {{#if ???}}
    {{student.name}}
  {{/if}}
{{/each}}


<b>Filter student</b>
{{#each students as |student|}}
  <div {{action toggleStudent student}}>{{student.name}}</div>
{{/each}}
//In corresponding controller to template
...
actions: {
  toggleStudent(student){
    this.toggleProperty("???")
  }
}
...

#2

Just so that we understand your use case. Are you saying that there is a filter button that corresponds to each student in the list and that button essentially toggles the visibility of the student in another list (presumable showing the details of that student)?

This should be a pretty simple thing. I’d be happy to build you an ember-twiddle, just want to make sure I understand what you’re asking.


#3

Hey @workmanw

Close. So you are right, there are buttons that corresponds to each student in the list. Let me provide more details.

Let’s say I have something like

Jack Joe Jill Jack Jack Jill

If I click on the “Jack” filter button, it should only show Jack in the list. Clicking it again will bring back the other students. Filter in the sense of, quickly seeing the entries of the student that I have chosen and no one else.

Hope that makes sense.


#4

Whoa and by the way, ember-twiddle looks sweet! I’ve never heard about it until you mentioned it earlier!


#5

Okay cool. One final question, can you only see all students or one at a time? OR could you filter it to say Jack and Jill, but not Joe?


#6

I think one at a time is fine. So if one filter is turned on, other filters should not be clickable. Thanks so much @workmanw!


#7

Here you go: https://ember-twiddle.com/beaf3ab55baf4b2e20b6

This is one of the problems where I think there are a few different ways to go about this. My solution was to create a computed property that filters the list of students on the javascript component. Then the template binds to the filtered list. Example:

visibleDetails: Ember.computed('filteredStudent', 'studentDetails.[]', function() {
  let studentDetails = this.get('studentDetails') || [],
      filteredStudent = this.get('filteredStudent');
  return filteredStudent ? 
    studentDetails.filterBy('student', filteredStudent) : 
    studentDetails;
}),

So that’s pretty simple, it just uses Ember’s filterBy array function. Updating either the filteredStudent property or the list of details will cause that to be recomputed. It may seem inefficient with larger lists, but the new glimmer engine does a really good job about being smart with it’s DOM updates.


The other, and sometimes more desirable, option would be use an ember helper. Here is a great repo with truth helpers: https://github.com/jmurphyau/ember-truth-helpers . If you had included that in your project, you could do something like this:

{{#each studentDetails as |details|}}
  {{#if (or (not filteredStudent) (eq details.student filteredStudent) )}}
    <tr>
      <td>{{details.student.id}}</td>
      <td>{{details.student.name}}</td>
      <td>{{details.text}}</td>
    </tr>
  {{/if}}
{{/each}}

In this case the template does iterate over all of the details and only renders markup for if there is no filter, or if the details matches the student filter. This one is a little uglier to look at because it has nested helpers. But it’s worth noting because it’s such a powerful concept.

Lets say instead of filtering out students, you wanted to “highlight” the selected student. You could do something like this:

{{#each studentDetails as |details|}}
  <tr class="{{if (eq details.student highlightStudent) 'highlight-row'}}">
    <td>{{details.student.id}}</td>
    <td>{{details.student.name}}</td>
    <td>{{details.text}}</td>
  </tr>
{{/each}}

So that let’s you loop through all the details and add a class conditionally.

Hopefully that all helps! I’m happy to provide any follow up. I’m going to bed shortly, but I’ll be around in the AM.


#8

Thanks @workmanw for this twiddle! I’ve never used computed properties much and this is definitely helpful. Can components communicate with each other? Say I’d like to separate the filters and the details list into two separate components could they communicate with each other via actions? Would it be easier to setup a parent component and wrap that around two child components and send actions from the children to the parent?

Just trying to figure out how to achieve this without relying on the controller, since Ember is moving away from the use of controllers.


#9

Yea, two components can communicate with each other, but there are some limitations. If you’re talking about a component communicating an ancestor you would follow DDAU (Date Down, Actions Up) pattern. If you’re talking about two sibling components it gets a little more tricky. Depending on the type of communication you’re talking about, those two sibiliting components could use a common [singleton] service. Or they could communicate through their parent.


So I forked that Twiddle and modified it building three components. https://ember-twiddle.com/5640745fb46c6e25ec9a. In this case, I created a parent component called “student-details”. That component includes two new sub components, one for handling the filter UI and one for handling the list UI.

In the not too distant future, we’ll have “routable components”. Controllers will be gone (as you’ve mentioned) and we’ll stitch together subcomponents with a single parent component that is built and populated by the router. I’ve done my best to demonstrate how that might look with this example.

That said, we’re still in intermediary state where we have to use controllers while we wait for Routable Components to be ready. @emberigniter wrote a post about this more recently: http://emberigniter.com/should-we-use-controllers-ember-2.0/


#10

To add to workmanw’s great answer, here I discuss communication between components: upward, downward and sideways: http://emberigniter.com/communication-between-distant-components/

Always prefer Data Down Actions Up – unless it’s so contrived you are better off injecting a Service.


#11

Thanks @workmanw and @emberigniter. Looks like I’ve got some studying to do as I’m still stuck in the pre-2.0 way of building stuff. I’m reading up on components and trying to understand how they work with each other. Will report back as I make progress.


#12

Do twiddles not persist? I’m clicking on your twiddle and it just loads a blank twiddle.


#13

Twiddles are saved via github as a secret gist. So for example: gist.github.com/workmanw/5640745fb46c6e25ec9a . When I load that link it works for me. Maybe it was just a momentary hiccup?