How to only allow selected data to react with own toggle switch in loop?

I was trying to implement toggle switch to enlarge the picture, but the picture is in an array so i render it with loop.

{{#each pictures as |pic|}}
   <PaperSwitch @value={{annoViewer}} @onChange={{action (mut annoViewer)}}>
	 <b>View Annotation</b>
   </PaperSwitch>

   {{if annoViewer}}
       {{#paper-card-content}}
          //I render the enlarge pic by pass in to another component
          <Component/>
        {{/paper-card-content}}
    {{else}}
       //Render original size of the pic
    {{/if}}

As the screenshot shown above, if i switch on the first toggle switch the second of the picture will be turn on too. Is it possible to switch on the specific toggle switch when it’s only get trigger?

Hi @kelvin, I think it’s important to consider the state here to realize what’s happening and how to fix it. When you have this code:

@onChange={{action (mut annoViewer)}}

it’s the same as writing

@onChange={{action (mut this.annoViewer)}}

And actually you should probably use this. explicitly, there’s even a lint rule for that, but that’s a side topic. Anyway, when you set this value you’re setting it on the backing class, either controller or component. This means you’re simply setting a boolean on your backing class. Makes sense. But when you are in the loop …

{{#each pictures as |pic|}}
  ...
  {{#if this.annoViewer}}
    {{#paper-card-content}}
  {{else}}
    ...
  {{/if}}
  ...
{{/each}}

you can think of it as just expanding what’s inside the loop a bunch of times, something like this:

  ...
  {{#if this.annoViewer}}
    {{#paper-card-content}}
  {{else}}
    ...
  {{/if}}
  ...
  {{#if this.annoViewer}}
    {{#paper-card-content}}
  {{else}}
    ...
  {{/if}}
  ...
  {{#if this.annoViewer}}
    {{#paper-card-content}}
  {{else}}
    ...
  {{/if}}
  ...

So now it’s easier to see what’s going on. Every single if block is referencing the same backing class state, so they will all respond the same way.

There are a number of ways to solve this, and which direction you go depends on how you want it to behave. If you only want one picture expanded at a time you can stick with one backing class property and use an id instead of a boolean. The id would be the id of the “currently expanded” picture. Then each picture simply needs to check its id against the value, something like

{{#if (eq this.annoViewer pic.id}}
  ...

But if you want an arbitrary number of pictures to be expandable you have to store the state in some other way. You could abstract a “picture card” (e.g. if/else block) into a component, and let the component store its own expanded state (this would probably be my approach). Or you could store an array of picture ids and check if each picture id is in the array to know whether or not its expanded.

1 Like

By using this. in annoViewer will not work because the annoViewer is a flag for me to toggle the switch. I’ve set it at the beginning on the component to false.

I might try your approach to solve this. Thanks for your explanation and idea!

1 Like

By the way, i found another way to solve the problem too. At the code onChange={{action (mut annoViewer)}}, instead of using mut write an action at the component and point the onChange to the action based. And pass the pic with the action so we can check which of the picture’s toggle is get triggered. The code at template will be looks like…

onChange={{action "annoViewer" pic}}

At the component actions will be looks like…

annoViewer(record){
   record.toggleProperty('annoViewer');
}

By using the record.toggleProperty(), this will handle the toggle’s behave on each individual of these pictures.

Nice glad you found a solution you like! My only caution there is that you want to be careful when you’re mutating properties on a model to ensure it won’t cause other side effects.