How to pass a reference into an element inside a {{#each}} loop


#1

Sorry, but I’m pretty new using Ember and I am not sure about this… check out this loop:

my view.hbs

  {{#each model as |tweet|}}
    <div>{{tweet.text}}</div>
    <button class="btn btn-default" {{action "reTweet"}} disabled={{isDone}}>Retweet</button>
    </div>
  {{/each}}

my controller.js: import Ember from ‘ember’;

export default Ember.Controller.extend({
  isDone: false,
  actions: {
    reTweet: function() {
      this.set('isDone', true);
    }
  }
});

How could I make button to change disabled property just for each one oh them. I mean, individual… because actually each time I click that button every button inside the loop changes.


#2

I believe what you’re looking for is simply:

actions: {
  reTweet: function(tweet) {
    tweet.set('isDone', true);
  }
}

Then pass the model directly to the action.

<button class="btn btn-default {{action "reTweet" model}} disabled={{model.isDone}}>

(assuming that the “tweet” model has the isDone property).

One alternate thought…

Consider making that <button a component that takes a tweet model. That way the code that handles the button press can be specific to it’s own model. Your view might look like:

view.hbs

 {{#each tweet as |model|}}
  <div>
  {{tweet.text}}
  {{tweet-button model=model}}
  </div>
{{/each}}

components/tweet-button.js

import Ember from 'ember';

export default Ember.Component.extend({
  actions: {
    reTweet: function() { this.set('isDone', true); }
  }
}

templates/components/tweet-button.hbs

<button class="btn btn-default" {{action "reTweet"}} disabled={{model.isDone}}>Retweet</button>

Warning: written before coffee has taken effect. YMMV


#3

The way I see it you have two options and it comes down to if you want this state to be persisted.


Option 1

I’m not sure what the nature of isDone is, but it sounds like it could be a property on the model that is saved to the server. If that’s the case, you would do something like:

View:

{{!-- templates/my-view.hbs --}}
{{#each tweet as |model|}}
  <div>{{model.text}}</div>
    <button class="btn btn-default" {{action "reTweet" model}} disabled={{model.isDone}}>Retweet</button>
  </div>
{{/each}}

Controller:

// controllers/my-controller.js
export default Ember.Controller.extend({
  isDone: false,
  actions: {
    reTweet: function(model) {
      model.set('isDone', true);
      model.save();
    }
  }
});

In this case you would send the model as an argument to your action. The action would toggle your property and save it to the server.


Option 2:

If isDone is really just view state and it doesn’t have to persist, then you should make a component to house the state.

View:

{{!-- templates/my-view.hbs --}}
{{#each tweet as |model|}}
  {{#tweet-message model=model as |tweetMsg|}}
    <div>{{model.text}}</div>
      <button class="btn btn-default" {{action "reTweet" target=tweetMsg}} disabled={{tweetMsg.isDone}}>Retweet</button>
    </div>
  {{/tweet-message}}
{{/each}}

Component js:

// components/tweet-message.js
export default Ember.Component.extend({
  isDone: false,
  
  actions: {
    reTweet: function() {
      this.set('isDone', true);
    }
  }
});

Lastly, component template:

{{!-- templates/component/tweet-message.hbs --}}
{{yield this}}

So in this example, we’re create a “helper component” that houses the isDone state. You’ll see in the last code snippet that the component yields itself ({{yield this}}). If you don’t know, that yielded value will end up as the tweetMsg value in the first snippet ({{#tweet-message model=model as |tweetMsg|}}). This yielding is the connective tissue. It gives your view template a reference to the component so you can access it’s state and send actions.

IMHO this option is the “ember way”.


#4

Ha. It looks like @palmergs was thinking along the same lines as I was with making a component. Jinx :stuck_out_tongue:


#5

Sorry, I made a mistake.

Instead of: {{#each tweet as |model|}}

I mean: {{#each model as |tweet|}}

Now it’s ok.

I don’t know if this changes anything… I tried both options but they don’t seem to work :confused: Prolly because the syntaxis.


#6

Do you think you could take what you have and build an ember-twiddle? I’d be happy to help you get it figured out from there.


#7

I appreciate your help, guys. Really. Prolly it’s my fault because I’m doing something bad…

My core question was how to change an element attribute inside an each loop just for that INDIVIDUAL element, and not for all of them at the same time! Again, prolly I’m doing something bad… but thanks :)!


#8

How could I make button to change disabled property just for each one oh them. I mean, individual… because actually each time I click that button every button inside the loop changes.

So I took this to mean that you wanted to have a button one per tweet that would be disabled after pressed.

I am actually trying to help you solve the core problem. I promise I’m not trying to take you down a rabbit hole here. I built an ember-twiddle based on what I thought you were asking: https://ember-twiddle.com/085d035e28c48f637c7c

If that’s not what you’re referring to can you please clarify your question. Try explaining more about your use case and about the state/data, in particular isDone. I’m happy to try to help.


#9

It’s EXACTLY what I expected. Thank you so much!!! :smile:


#10

@pepper You’re welcome! Happy to help.