Make clicked button look active, other buttons should look inactive

Hi everyone,

So I have a group of buttons and each button is its own component. When I click on a button component, I set its ‘isActive’ property to true, which will add a class to distinguish it from unclicked button components. But when I click on another button component, I want to set 'isActive` to false on the previous clicked button component so that only one button component ever looks like it’s toggled active. How would I do this the Ember way? Is it not a good idea to make each button its own component?

Here is a link to a simple twiddle example: https://ember-twiddle.com/e06d287600d72c123a34df81bf3afd16?numColumns=2&openFiles=styles.app.css%2Cstyles.app.css

Let me know if I can do anything else to help you help me, thanks.

I’ve created a new twiddle that adapts the one you created to demonstrate one way of doing what you’re asking. Since all the buttons have a shared state that drives their active appearance I’ve set up a parent component called filter-selector that handles the shared state between all the buttons.

This solution is following the data-down actions-up pattern that is the preferred method for handling state changes among components. Values for tag and activeTag are handed down to each button. The action setTagActive is called by a button when clicked, sending its tag text up to the filter-selector. The action changes the value of activeTag in the filter-selector. That change in the activeTag property flows down through each filter-tag-btn template causing each button to re-evaluate whether its tag matches the active tag and therefore if it should display the active state.

The highlights of the code are shown here for a quick look without going to the twiddle…

// filter-selector.js
import Ember from 'ember';
export default Ember.Component.extend({
  tags: null,
  activeTag: null,
  actions: {
    setTagActive(tag) {
      this.set('activeTag', tag)
    }
  }
});

// filter-selector.hbs
{{#each tags as |tag|}}
	{{filter-tag-btn tag=tag activeTag=activeTag setTagActive=(action 'setTagActive')}}
{{/each}}
// filter-tag-btn.js
import Ember from 'ember';
export default Ember.Component.extend({
  tagName: 'button',
  classNameBindings: ['isActive:is-active'],

  tag: null,
  activeTag: null,
  isActive: Ember.computed('tag', 'activeTag', function() {
    return this.get('tag') === this.get('activeTag')
  }),
  click() {
    this.setTagActive(this.get('tag'))
  }
});
3 Likes

I hope this thread is still active. I’m thinking of creating a new topic but will try to communicate here first.

I’m new to ember and basically i’m trying to achieve the same OP’s goal: making clicked button active and other buttons look inactive. But this time the group of buttons are in a single component.

I made everything work fine. My question is: are there any problems with my implementation? or is there an easier way to do this.

Any advice would be greatly appreciated. Hoping someday to contribute to emberjs as well.

I’d like to revive this as Ember has changed significantly and my solution in Modern Octane reveals some really good best practices on how to design component communications.

app/components/list-selector/index.js

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class ListSelector extends Component {
  @tracked selectedTag = null;

  get activeTag() {
    return this.selectedTag ?? this.args.initiallySelectedTag;
  }

  get isActive() {
    return { [this.activeTag]: true };
  }

  @action
  setActive(tag) {
    this.selectedTag = tag;
  }
}

app/components/list-selector/index.hbs

{{yield (hash
  activeTag=this.activeTag
  isActive=this.isActive
  setActive=this.setActive
)}}

app/components/active-button/index.hbs

<button
  type="button" 
  ...attributes
  data-state={{if @isActive "active" "inactive"}}
>
  {{yield}}
</button>

app/templates/application.hbs

{{#let (array "foo" "bar" "baz") as |fakeExampleData|}}
  <ListSelector as |selector|>
    {{#each fakeExampleData as |tag|}}
      <ActiveButton
        @isActive={{get selector.isActive tag}}
        {{on "click" (fn selector.setActive tag)}}
      >
        {{tag}}
      </ActiveButton>
    {{/each}}
  </ListSelector>
{{/let}}

app/styles/app.css

button[data-state=active] {
  background-color: lightgreen;
  border: inset;
}

button[data-state=inactive] {
  background-color: salmon;
}