2

FINAL UPDATE: Kalman Hazins pointed me in the right direction, the computed property wasn't being called because of the fact that it wasn't rendered onscreen, so it wasn't "necessary" to re-compute it. I'll include here the final code for the controller:

App.QuestionController = Ember.ObjectController.extend({
    isEditing : false,

    resetIsEditing : function() {
        this.set('isEditing', false);
    }.observes('model.id'),

    canEditQuestion : function() {
        return this.get('author.id') === App.currentUser;
    }.property('author.id')
}

UPDATE: a "nice enough" solution has been provided by Raymond Liu below , it doesn't really answer the question "why this is happening", but it's the best I've got so far.


I'm learning Ember.js, follwing this book and making some changes. In my controller I have a property bound to the model, but when I change such model the property is not updated.

This is the controller:

App.QuestionController = Ember.ObjectController.extend({
    isEditing : false,

    canEditQuestion : function() {
        this.set('isEditing', false);
        return this.get('author.id') === App.currentUser;
    }.property('model')
});

This is the template:

<script type="text/x-handlebars" id="question">
    {{#if isEditing}}
        <!-- edit question form -->
    {{else}}
        <p id="question">{{question}}</p>
        {{#if canEditQuestion}}
            <a href="#" {{action "toggleEditQuestion"}}>Edit question</a>
        {{/if}}
    {{/if}}
</script>

Note that if I move the {{else}} branch content before {{#if isEditing}} it works as expected (but not how I want to).

Maybe the fact that question is a nested route matters:

App.Router.map(function() {
    this.resource('questions', function() {
        this.resource('question', { path : '/:question_id' });
    });
});

What I want is that even if I'm already editing a question, if I change model the isEditing property should go back to false and the question form is not shown. Apparently I can solve it if I always render the question, but that's not what I want. What am I missing?

EDIT: I'm adding the code for the question and the user model:

App.Question = DS.Model.extend({
    title : DS.attr('string'),
    question : DS.attr('string'),
    date : DS.attr('date'),
    author : DS.belongsTo('user', { async : true }),
    answers : DS.hasMany('answer', { async : true }) 
});

App.User = DS.Model.extend({
    fullname : DS.attr('string'),
    email : DS.attr('string'),
    questions : DS.hasMany('question', { async : true })
});

The App.currentUser property is set in the sign_in controller:

App.SignInController = Ember.Controller.extend({
    needs : [ 'application' ],

    actions : {
        signIn : function() {
            var email = this.get("email");
            var userToLogin = App.User.FIXTURES.findBy("email", email);

            if(userToLogin === void 0) {
                alert("Wrong email!");
                this.set("email", "");
            }
            else {
                localStorage.currentUser = userToLogin.id;
                App.set('currentUser', userToLogin.id);
            }
        }
    }
});

PS: the complete code for my app is available at https://github.com/goffreder/emberoverflow

PS2: I managed to get a functioning jsFiddle of my app. To reproduce you have to sign in using 'tom@dale.com' email. Then, if you click on the first answer, you should see an 'Edit question' link that toggles the question form. Now, with that form opened, if you change question the form will still be availabe, with the new question as content, which is the behaviour I want to avoid.

Community
  • 1
  • 1
goffreder
  • 503
  • 3
  • 17
  • Don't you want `author.get('id')`? In addition to the other points raised in the answers about writing the dependencies correctly. Remember that `.property('model')` does NOT mean that something IN the model changed, it means that the model itself--the model object--became a different model object. –  Dec 15 '14 at 16:00
  • EDIT: Sorry wrong target ^_^ Why should I want `author.get('id')`? It's a better way to access properties? I know that the computed property relies on the whole model to be different, and not a single property of it, it just seems that if I don't render it directly with its template, the model doesn't change. Maybe it's supposed to be this way, I don't know (that's why I'm asking ^_^) – goffreder Dec 15 '14 at 19:13
  • 1
    Agree completely with @torazaburo on this, definitely specify your dependencies correctly. Also, your template logic doesn't make sense. You want to check `isEditing` if `canEditQuestion` is true, not the other way around. Furthermore, you don't want to trigger property changes in computed properties. Observers do this, and properties should only ever be used to compute the property in question. Finally, the computed property is only calculated if it's accessed, so having it nested in a template conditional that starts as false means that it won't ever get updated by the property. – mpowered Dec 18 '14 at 21:24
  • @AdamRobertson: what do you mean by "specify your dependencies correctly"? Also, I see your point about the template, but if I'm editing I don't want the `{{question}}` template to be rendered, so I have to check `isEditing` before. Or am I missing something? – goffreder Dec 18 '14 at 21:40
  • As @torazaburo mentioned, the dependency is `author.id`, not `model`. You also can't specify the dependency for `App.currentUser` because you're using the global reference to it in the computed property, and I'm pretty sure observers don't work for globals. As for the #if blocks, your final edit should take care of that. I can't emphasize enough to avoid modifying the controller/models in computed property definitions--it's far cleaner and easier to test. – mpowered Dec 18 '14 at 22:12

4 Answers4

4

@goffredder and @Raymond Liu, the reason for canEditQuestion function/property not firing is as follows.

In Ember.js, the (computed) property evaluation is "magical", but as we all know - "magic" can be expensive. So, the value of a computed property gets cached, so that "magic" doesn't have to happen all the time and unless something changes that cached value doesn't get recomputed. Not only that, if the property is not being shown on the screen - why bother recomputing it? See this jsbin to see exactly what I am talking about. Notice how lazyPropDep never gets updated since lazyDep property is never shown on the screen.

Now, back to your example. canEditQuestion property is not being rendered to the screen when you are editing the question (since it's in the else block). Therefore, the canEditQuestion function which resets isEditing to false never gets called.

I think what you need is to place the resetting logic into an observer while leaving the other logic in the property as follows:

resetIsEditing : function() {
  this.set('isEditing', false);
}.observes('model.id'),

canEditQuestion : function() {
  return this.get('author.id') === App.currentUser;
}.property('model'),

This way EVERY TIME a new model (with a new model id) gets loaded into the controller (even if the previous model was in edit mode) the observer code will trigger resetting isEditing property to false

Kalman
  • 8,001
  • 1
  • 27
  • 45
  • That's what I wanted to read, thank you very much! I though it was a matter of not rendering something, but I thought it was the model's template, not the property itself. Thank you both for the solution and the explanation ^_^ – goffreder Dec 18 '14 at 21:19
2

update: I misunderstood your problem. Your want to disable form when go to another question, right? In your question_routs.js file, add this:

App.QuestionRoute = Ember.Route.extend({
  actions: {
    didTransition: function() {
      this.controller.set('isEditing', false);
      return true; // Bubble the didTransition event
    }
  }
})

These code will set 'isEditing' property to false when you transtion to another question. I noticed that the canEditQuestion function/property will not be called when toggle to another question just if isEiting property is set to true, I think that's the very problem need to be solved. And I have no idea why.

  • Thank you, this behaviour is exactly what I wanted. I'll keep the question unanswered for a while though, because I'd like to know why (as you also noticed) `canEditQuestion` is not called upon model change if `isEditing` is true. But your answer definitely "feels" right ^_^ – goffreder Dec 16 '14 at 09:36
1

It looks as though your computed property depends on two variables, author.id and App.CurrentUser that are not listed in the list of dependent keys. Instead you only listed model as a dependent key. I don't know what the relationship is between author and model, but you should list the actual properties/variables that you need in order to compute your property.

Sean O'Hara
  • 1,218
  • 1
  • 11
  • 19
  • I added more code, hopefully it is enough to get a grasp of what my application is doing. But I thoughy that only the result of my computed property depends on those two other properties, not the computation itself. Shouldn't `canEditQuestion` be computed regardless of what I do inside it? I tried to add a `console.log` inside and, once the question form is shown, is never executed... – goffreder Dec 15 '14 at 19:10
  • 1
    I think you need to get a more basic understand of what Ember's computed properties are meant to achieve. They allow you to use what's called Uniform Access to access both basic object properties, and functions that live on those objects. They also allow caching of the result of the function, but in order for this to work effectively and in a performant way, you need to let Ember know which other properties the function depends on. I'd suggest reading this: http://emberjs.com/guides/object-model/computed-properties/ Generally speaking, you can't just put `model` as the dependent key. – Sean O'Hara Dec 15 '14 at 19:13
  • So if I want to monitor the whole model change what should I do? Should I use an observer? If I got it right, the fact that I set the value of `isEditing` in the computed property contradicts this paragraph, am I correct? "Computed properties should not contain application behavior, and should generally not cause any side-effects when called." – goffreder Dec 15 '14 at 20:55
  • The body of your `canEditQuestion` computed property appears to be correct. I would try setting a jsbin perhaps. I haven't run the code in your repo but I think that part of the issue is that the dependent keys are not correct for that computed property, and so the value isn't getting recomputed. – Sean O'Hara Dec 15 '14 at 22:55
  • If it can be helpful, I added a [jsFiddle](http://jsfiddle.net/goffreder/7keawg1q/19/embedded/result/) with the whole application. To sign in use 'tom@dale.com' mail address. I really appreciate your help, thank you very much ^_^ – goffreder Dec 15 '14 at 23:14
0

You can do smthing like:

<script type="text/x-handlebars" id="question">
 {{#unless isEditing}}
    <p id="question">{{question}}</p>
    {{#if canEditQuestion}}
        <a href="#" {{action "toggleEditQuestion"}}>Edit question</a>
    {{/if}}
 {{else}}
    <!-- edit question form -->
 {{/unless}}
</script>
Hasib Mahmud
  • 806
  • 1
  • 10
  • 29
  • That doesn't seem to solve my issue, it just switches truthy and falsy branches. – goffreder Dec 15 '14 at 10:44
  • Have you tried it? In Handlebars, it is better to put nesting `if` conditions inside `if` block than `else` block. If you don't want to use `unless` than you have to change `isEditing` to `true`. – Hasib Mahmud Dec 15 '14 at 10:50
  • I have tried it, it behaves the same way as my original code. Since I'm new both to Ember and Handlebars, why is it better to use nesting `if` than use an `else` block? – goffreder Dec 15 '14 at 11:07