I have a widget model which has a shallow parent-child relationship. A given widget may be a "root" widget and not have any parent, or it may be a child widget which has a parent.
The ember data model looks like this:
export default DS.Model.extend({
name: DS.attr('string'),
parentWidget: DS.belongsTo('widget', { async: true, inverse: null }),
isSubWidget: DS.attr('boolean')
})
I'm trying to add a "displayName" property that will show the name for root widgets, or "parent name - child name" for child widgets
displayName: Ember.computed('name', 'parentWidget.name', 'isSubLob', function() {
if this.get('isSubWidget') {
return "#{this.get('parentWidget.name')} - #{#get('name')}"
}
else {
return "#{this.get('name')}"
}
})
This is not working, however. The child lob's displayName always comes as
undefined - WidgetName
The json is being returned like so:
{
"widgets": [
{
"id": 2,
"name": "Widget Name",
"is_sub_widget": true,
"parent_widget_id": 1
},
...
}
For the record, all the records are being returne by the json at the same time.
I feel like Ember should be asyncronously resolving the parent widget and the string should be updated as well, however it doesn't seem to be working. Any idea what I'm doing wrong here?
I would say you have two issues:
You're not declaring an inverse to your parentWidget relationship, which means that Ember Data is guessing the inverse (and likely guessing wrong). You should change that declaration to look like this, just to be sure:
parentWidget: DS.belongsTo('widget', { async: true, inverse: null }),
I doubt that will fix your issue, but it's good practice.
You're not waiting for your promise to resolve before trying to use the name. You've specified the parentWidget relationship as being asynchronous, which means that #get('parentWidget') will not return a model. It's going to return a promise that will eventually resolve to your model. Normally this would be fine as the computed property would just recompute when the promise resolves, except that you're not watching the proper key.
/* PS: Assuming that your comma was misplaced on this line */
displayName: Ember.computed('name', 'parentWidget', function() {
^^^^^^^^^^^^
As seen, you're only watching the parentWidget property. So if the name property on the parentWidget every updates, you won't be notified. Change that line to this and you should be good to go:
displayName: Ember.computed('name', 'parentWidget.name', function() {
Just keep in mind that the first few times through, parentWidget.name will still be undefined. It won't be the value you want until the promise resolves, which means the computed property could run several times before it does resolve.
Related
I'm defining my state with an object, initialized with some nested objects to an empty string and an empty array, as such:
state: {
displayedFarmer: {
name: "",
arrivalDates: []
// some more fields...
},
// more vuex stuff
}
I would expect that if I console.log the displayed farmer, arrivalDates would appear. Here's what I did to track it in my component:
computed: {
...mapState([ 'displayedFarmer' ]),
// more code
},
watch: {
displayedFarmer: {
handler() {
console.log("displayedFarmer", this.displayedFarmer);
},
deep: true,
immediate: true
}
}
The first log line appearing shows the displayedFarmer object, with the arrivalDates and name missing:
displayedFarmer
Object { … }
(basically only the prototype and the __ob__ objects appear when I expand it in the console)
That behavior is unclear to me, and has forced me to use a small and harmless hack to initialize the fields the first time they are being accessed.
For this question, what I want to know is:
Why can't I see the objects I initialized in my state when I access them via the component?
How can I do this differently, so that when I first access the object, all the nested items are initialized?
watch with immediate: true fires before components hook:created. This is probably the reason you don't have access to the initialized object.
I don't know what you are trying to achieve, can you expand your question?
The first thing that came to my mind is to try to call method (which does whatever you want) on a hook:mouted. Then call the same method in watcher, but with immediate: false (which is a default btw)
Having an object similar to:
const data = {
tasks: {
projects: [
name:'Project Name',
filters: [
{
name:'First Project Filter',
checked:false,
onChange:(event) => {
console.log(this.checked)
}
},
...
],
...
],
...
},
...
}
The problem at hand is how to reference the checked property without drilling through the entire object.
In the case above, it throws an error because this is undefined, so referencing this.checked is invalid.
I could extract from the event data properties so that I can get the
whole reference such as tasks.projects[0].filters[0].checked, but I
am wondering if an easier method is available.
The ideal solution would be a way to reference the surrounding properties of the function without traversing the entire object. Surely the function has a way to know that it is inside of an object so maybe something like parent().checked ?
If relative: I am using node.js and react to use this object to render a filtered sidebar that works with context to filter the data-set. I don't think that is relative as this seems like a pure JavaScript OOP situation.
If I have data like so in Vue JS 2:
data : {
newBus: {
name: '',
hours: {
sunday: '',
}
}
}
And I set it here:
<input v-model="newBus.hours.sunday" type="text" placeholder="Sunday">
Adding a business this way works, the issue comes when I try to update them. The user clicks on the item from a list and then the saved data fills in the form like so:
<div v-for="bus in businesses" v-on:click="editBus(bus)" class="list-group-item bus-list-item">{{bus.name}}</div>
Method:
editBus: function (bus) {
/*Set post values to form*/
this.newBus = bus
},
But I get the error:
vue.js:1453 TypeError: Cannot read property 'sunday' of undefined
This happens immediately when the item to be updated is clicked. Any ideas?
EDIT: It appears to be related to the hours property not being avaliable on bus. If I console.log(bus) it doesn't show. But I need to add this data to this business. Is there a way to have it ignore data it doesn't yet have? What I don't understand is that if it was not nested...aka sunday: '' instead of hours: { sunday: ''} it works fine.
EDIT: Here is the CodePen recreation.
The problem here isn't really a Vue problem it's a Javascript problem. You cannot access a property of undefined.
Using the example data structure from your pen.
newComment: {
name: '',
comment: '',
votes: 0,
time: timeStamp(),
subcomment: {
name: ''
}
}
In this case, you've set everything up with a defined value, so everything is going to be resolved. The problem comes when you are clicking your edit button, the object received looks like this:
{
name: "some name",
comment: "some comment",
time: "4/5/2017",
votes: 145,
.key: "-Kh0PVK9_2p1oYmzWEjJ"
}
Note here that there is no subcomment property. That means that the result of this code
newComment.subcomment
is undefined. But in your template, you go on to reference
newComment.subcomment.name
So you are essentially trying to get the name property of undefined. There is no name property of undefined. That is why you get your error.
One way you can protect yourself from this error is to check to make sure the subcomment property exists before rendering the element using v-if (forgive me if this is the wrong pug syntax-I'm not that familiar with it)
input#name(type="text")(placeholder="Name")(v-model="newComment.subcomment.name")(v-if="newComment.subcomment")
That will prevent the error.
Alternatively in your editComment method, you could check to see if subcomment exists, and if it doesn't, add it.
if (!comment.subcomment)
comment.subcomment = {name: ''}
this.newComment = comment
Finally, you ask, why does it work if it's not nested data. The key difference is this: if newComment exists, say like this
newComment: {}
and you try to get newComment.name then the returned value of name is undefined. If you have a template that has something like v-model="newComment.name" nothing is going to crash, it's just going to set the value of the input element to undefined, and when you change it, newComment.name will get the updated value.
In other words, when you try to reference newComment.subcomment.name you are trying to reference the name property of something that doesn't exist whereas when you try to reference newComment.name, the object exists, and it doesn't have the name property.
EDIT: I've set up an actual repro of the issue on JSBIN
Been trying to resolve this for a while now and I'm clearly not understanding how the relationship between model and setupController works. I have a model which is returning a hash; the result of two find calls:
model(params) {
return Ember.RSVP.hash({
course: this.store.find('course', params.course_id),
topics: this.store.find('topic', { course_id: params.course_id })
});
},
The first time setupController gets called, the value of model if as expected, a hash like { course: <Class>, topics: <Class> }. Awesome, that's what I want.
However, the next time setupController gets called (for example, transition to another route and then press the back button in the browser), the model is now just the course <Class>:
setupController(controller, model) {
// when first called model will be { course: <Class>, topics: <Class> }
// next time entered, model will just be <Class> (just the value of "course" )
// why is the model object not preserved?
controller.set('model', model.course);
controller.set('topics', model.topics);
}}
If I just make model() return a single resource, it's the same every time:
model(params) { return this.store.find('course', params.course_id); }
// now `model` will always be "course" in setupController
Why is the original model not preserved when using a hash result? Am I doing something wrong?
You're sending the model color when you're linking here:
{{#link-to 'color' color}}{{color.name}}{{/link-to}}
Because of that, the model hooks aren't run. If you change that to color.id, it'll work.
It's mentioned here.
In the above example, the model hook for PhotoRoute will run with
params.photo_id = 5. The model hook for CommentRoute won't run since
you supplied a model object for the comment segment. The comment's id
will populate the url according to CommentRoute's serialize hook.
Looking at it, the original model will not be preserved because on setupController, you are calling controller.set('model', model.course). When it first loads, its called the model(params {} function appropriately, but on back button transitions and certain {{link-to}} calls, that isn't always the case.
In your setupController, try changing it to controller.set('course', model.course);, that way you aren't overwriting your model on execution as well and it will always be able to find it.
I have a model like this:
App.Category = DS.Model.extend({
title: DS.attr('string'),
items: DS.hasMany('item', {async: true}),
itemCount: function() {
return this.get('items').get('length');
}.property('items')
});
and it seems I cannot use "property" there if I want to have the UI update everytime a user adds or removes items.
From what I can tell I should be using "observes", but when I use that in place of "property" the handlebars {{itemCount}} tag just renders the function itself as a string.
Any help on getting this to render properly is much appreciated.
I think you can simply use :
{{items.length}}
in your handlebars template.
There's absolutely no need for an observer, computed properties do updates themselves.
And if you really want a computed property named itemCount, it would be :
itemCount: function() {
return this.get('items.length');
}.property('items.length')
Or even better :
itemCount: Ember.computed.alias('items.length')
Like #florent-blanvillain said, just use Ember.computed.alias. But in the future, when writing computed properties based on arrays, you need to use the #each syntax to get it to respond to changes in property values:
itemCount: function() {
return this.get('items').filterBy('isSelected');
}.property('items.#each.isSelected')
Something like that. See the docs on computed properties for more info.