Search code examples
cssember.jshandlebars.js

How to conditionally apply CSS classes in Ember?


I created a resource in my Ember app and I'm using an ArrayController to display a table with the data. I had no problem making the table sortable using ArrayController's sortProperties and sortAscending properties.

Now based on the value of these two properties I need to conditionally apply CSS styles to the table's column headers, but because of the Handlebars limited logic, I need to create a boolean property (sortByName, sortByValue2, sortByValue2) on the controller for each column and use the following code to apply the correct class to the each column header:

{{#if sortByName}}
    <th {{action "setSortBy" "name"}} {{bind-attr class="sortAscending:sorting_asc:sorting_desc"}}>Name</th>
{{else}}
    <th {{action "setSortBy" "name"}} class="sorting">Name</th>
{{/if}}

Imagine the above repeated 10 times and you get the idea of what I'm dealing with. This code is very verbose and not DRY at all. What's the best way to solve this problem?

Ideally I would like to use an expression, which takes a parameter inside of the template and returns a string with CSS class names like this:

<th {{action "setSortBy" "name"}} {{bind-attr class="isSortedBy('name')"}}>Name</th>

Where isSortedBy('name') would return either sorting_asc, sorting_desc or nothing.

Is this possible using standard Ember features? Will this be possible using HTMLBars?

How would you solve a similar problem?


Solution

  • This is the perfect use case for a component.

    Provided that you have:

    sortedBy: Ember.Object.create({name: 'name', direction: 'asc'})

    in your controller.

    An implementation could be:

    App.SortableThComponent = Ember.Component.extend({
      tagName: 'th',
      classNameBindings: ['sortedByMe'],
      sortedByMe: function() {
        if (this.get('sortedBy.name') === this.get('name')) {
          return this.get('sortedBy.direction');
        }
      }.property('sortedBy.name', 'sortedBy.direction'),
      click: function() {
        this.sendAction('action', this.get('name'));
      }
    });
    

    Then, in your main template:

    {{#sortable-th name="name" sortedBy=sortedBy action="setSortBy"}} Name {{/sortable-th}}

    And, in your component template, just:

    {{yield}}

    Not tested. But you can get the idea. Hope it helps!