Danny Varod
Danny Varod

Reputation: 18068

How to get Ember.js's bindAttr to refresh?

I am trying to create "sort by" buttons which change sorting and css-classes when clicked on using Ember.js.

The sorting part and the initial class assignments work, however, the class assignments are not refreshed when I update the dependant properties.

What am I missing?

This is in my HTML:

<script type="text/x-handlebars" data-template-name="sort-option-item">
    <dd {{bindAttr class="IsActive:active IsDesc:reversed"}}
        {{action sortBy on="click"}}><a href="#">{{Name}}</a></dd>
</script>

<script type="text/x-handlebars">
    {{#each option in controller.sortOptions}}
        {{view App.SortOptionView controllerBinding="option"}}
    {{/each}}
</script>

An this is in my Javascript:

var App = null;

$(function () {

    App = Ember.Application.create();

    // Define Types:

    App.SortOptionCtrl = Ember.Controller.extend({
        Name: null,
        Predicate: null,
        Controller: null,
        IsActive: false,
        IsDesc: false,
        sortBy: function () {
            if (this.Controller != null)
                this.Controller.sortBy(this.Predicate);
        },
        Check: function () {
            this.IsActive = this.Controller != null
                            && this.Controller.isSortedBy(this.Predicate);
            this.IsDesc = this.Controller != null
                          && this.Controller.isSortedDescBy(this.Predicate);
            // adding an alert(this.IsActive); here
            // proves that the function is indeed called and works as expected
        }
    });

    App.ProductsController = Ember.ArrayController.extend({

        initialized: false,
        content: [],
        viewContent: [],
        sortProperties: ['Order'],
        sortAscending: true,
        sortOptions: [],

        initialize: function () {

            if (this.initialized == true)
                return;

            this.initialized = true;
            var ctrl = this;

            this.sortOptions.pushObject(App.SortOptionCtrl.create({
                Name: 'Unsorted',
                Predicate: null,
                Controller: ctrl,
            }));
            this.sortOptions.pushObject(App.SortOptionCtrl.create({
                Name: 'By Name',
                Predicate: 'Name',
                Controller: ctrl,
            }));
            this.sortOptions.pushObject(App.SortOptionCtrl.create({
                Name: 'By Date',
                Predicate: 'Date',
                Controller: ctrl,
            }));

            this.sortOptions.forEach(function (opt) { opt.Check(); });
        },

        load: function () {

            this.initialize();
            // ....
        },

        sortBy: function (predicate) {

            var prevPredicate = this.sortProperties[0];

            if (predicate == prevPredicate && predicate != null) {
                this.sortAscending = !(this.sortAscending);
            }
            else {
                this.sortAscending = true;
            }

            this.sortProperties.length = 0;

            if (predicate)
                this.sortProperties.pushObject(predicate);
            else
                this.sortProperties.pushObject('Order');

            this.sortOptions.forEach(function (opt) { opt.Check(); });
        },

        isSortedBy: function (predicate) 
        {
            if (predicate == null)
                predicate = 'Order';

            var activePredicate = this.sortProperties[0];

            if (predicate == activePredicate) {
                return true;
            }
            else {
                return false;
            }
        },

        isSortedDescBy: function (predicate) {

            if (predicate == null)
                predicate = 'Order';

            var activePredicate = this.sortProperties[0];

            if (predicate == activePredicate) {
                if (this.sortAscending)
                    return false;
                else
                    return true;
            }
            else {
                return false;
            }
        },

    });

    App.SortOptionView = Ember.View.extend({
        templateName: 'sort-option-item'
    });

    // Create Instances:

    App.productsController = App.ProductsController.create({
    });

    App.productsController.load();

    App.initialize();
});

Versions: Ember: 1.0.0-rc.2, handlebars: 1.0.0-rc.3

Upvotes: 0

Views: 1173

Answers (2)

Danny Varod
Danny Varod

Reputation: 18068

After Joe's help, more reading of Ember's manual and clean up of my code, I got to this sorter solution:

Sort option controller:

App.SortOptionCtrl = Em.Controller.extend({
    Name: null,
    Predicate: null,

    _isActive: false,
    isActive: function () {
        return this.get('_isActive');
    }.property('_isActive'),

    _isDesc: false,
    isDesc: function () {
        return this.get('_isDesc');
    }.property('_isDesc'),

    controller: null,

    updateState: function () {
        if (!this.Predicate) {
            this.set('_isActive', (this.get('controller')
                .get('activePredicate') == null));
            this.set('_isDesc', false);
        }
        else {
            this.set('_isActive', (this.get('controller')
                .get('activePredicate') == this.Predicate));
            this.set('_isDesc', (this.get('_isActive')
                && !this.get('controller').get('sortAscending')));
        }
    }.observes('controller.activePredicate', 'controller.sortAscending'),

    sortBy: function () {
        if (this.get('controller') != null) {
            this.get('controller').sortBy(this.Predicate);
        }
    },
});

Products controller:

App.ProductsController = Ember.ArrayController.extend({

    content: [],
    viewContent: [],
    activePredicate: null,
    sortProperties: ['Order'],
    sortAscending: true,
    sortOptions: [],

    filter: function (obj) {
        return true;
    },

    init: function () {

        this._super();

        var ctrl = this;

        this.sortOptions.pushObject(App.SortOptionCtrl.create({
            Name: 'Unsorted',
            Predicate: null,
            controller: ctrl,
        }));
        this.sortOptions.pushObject(App.SortOptionCtrl.create({
            Name: 'By Name',
            Predicate: 'Name',
            controller: ctrl,
        }));
        this.sortOptions.pushObject(App.SortOptionCtrl.create({
            Name: 'By Date',
            Predicate: 'Date',
            controller: ctrl,
        }));

        this.sortOptions.forEach(function (opt) {
            opt.updateState();
        });
    },

    sortBy: function (predicate) {

        var prevPredicate = this.sortProperties[0];

        if (predicate == prevPredicate && predicate != null) {
            this.set('sortAscending', !(this.get('sortAscending')));
        }
        else {
            this.set('sortAscending', true);
        }

        this.set('activePredicate', predicate);
        this.set('sortProperties.length', 0);

        if (predicate)
            this.get('sortProperties').pushObject(predicate);
        else
            this.get('sortProperties').pushObject('Order');
    },

});

Upvotes: 0

MilkyWayJoe
MilkyWayJoe

Reputation: 9092

If you want your views to react to whatever happens in the controller, you should create computed properties (via fn(){}.property('dependency')). However, in order for computed properties to work properly, you need to use Ember's get() and set() property accessors.

In your code you are doing things like

this.IsActive = this.Controller != null && 
                this.Controller.isSortedBy(this.Predicate);

When you should be doing something like this:

this.set('active',
     this.get('controller') != null &&
     this.get('controller').isSortedBy(this.get('Predicate'))
);

You might have noticed that this code is setting a value into active, but the template is listening to isActive. That property has been changed into a computed property:

isActive: function() {
    return this.get('active');
}.property('active')

It will listen for changes in the active property, and whenever that happens, it will cache the new value, and notify all subscriber objects to refresh/update.

Using Ember's get and set accessors is indicated in order to properly use the observables that make this chain of events possible.

I have modified your sample applying get and set where appropriate. You can see it in this fiddle: http://jsfiddle.net/schawaska/fRMYu/

Upvotes: 2

Related Questions