Observe error property on Ember.Model - ember.js

I'm trying to create a component, which creates an input field for a specific field on a Ember.model.
When calling model.save() I would like to display the possible error messages regarding that field, right beneath the field. But I can't seem to get notified when my Errors property on Ember.Model changes.
Is there a way to observe the model.errors property, in order to display the correct error messages?
I tried:
.property{'model.errors'} and Ember.computed
.observes('model.errors')
.observes('model').on('becameInvalid')
I think i'm pretty close, as my errors with the solution below, are being dipslayed, however when I change my input to something else invalid, and try to save again, my original errors do not get cleared, and the new ones do not get added.
When I put breakpoints, or console.logs, I can see that the code never enters that particular section for displaying errors again, so my guess is that the computed property is not working. And my template is never updated with new errors.
Here's my code at the moment:
My component: components/inputfield-text.js
import Ember from 'ember';
export default Ember.Component.extend({
value: Ember.computed('model', 'field', {
get: function() {
return this.get('model').get(this.get('field'));
},
set: function(key, value) {
this.get('model').set(this.get('field'), value);
return value;
}
}),
errors: function(){
var errors = this.get('model.errors');
console.log(errors)
return errors.errorsFor(this.get('field'));
}.property('model.errors', 'model', 'field')
});
My Component's template: templates/components/inputfield-text.hbs
{{input type='text' value=value class='form-control' placeholder=placeholder}}
{{#each errors as |error|}}
<div class="error">
{{error.message}}
</div>
{{/each}}
And for the sake of completeness, the code I use for embedding the component in a template:
{{inputfield-text model=model field='name'}}

Found it, I had to add [] to my computed property, correct code below, notice difference between.
property('model.errors', 'model', 'field')
And
property('model.errors.[]')
Correct component code: components/inputfield-text.js
import Ember from 'ember';
export default Ember.Component.extend({
value: Ember.computed('model', 'field', {
get: function() {
return this.get('model').get(this.get('field'));
},
set: function(key, value) {
this.get('model').set(this.get('field'), value);
return value;
}
}),
errors: function(){
var errors = this.get('model.errors');
console.log(errors)
return errors.errorsFor(this.get('field'));
}.property('model.errors.[]')
});

Related

Ember #each won't iterate over array

I'm experiencing a really weird behaviour wherein I have an Ember array that has a length, a first object, but I can't iterate over it.
I have a session object which queries the user's team members:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Service.extend({
store: Ember.inject.service(),
...
teamMembers: Ember.computed('token', function() {
const promise = this.get('store').findAll('teamMember', {include: 'user,organization'});
return DS.PromiseObject.create({ promise: promise });
})
});
As far as I can see this is working correctly, because when I access it from inside my template, I can access the array length, and the first object:
<p>The length of the array is {{session.teamMembers.length}}</p>
<p>The first entry in the array is {{session.teamMembers.firstObject.name}}</p>
These work perfectly, returning 2 and my own name, respectively. However, when expressed as an each statement, it returns nothing:
<ul>
{{#each session.teamMembers as |teamMember|}}
<li>{{teamMember.name}}</li>
{{/each}}
</ul>
The ul element is completely empty. If I have an {{else}} clause, the else clause appears until the promise fulfills, and then I'm left with an empty ul element. The Ember Inspector shows all the values have been loaded correctly.
If I change the method as follows:
teamMembers: Ember.computed('token', function() {
return [{name: 'Paul Doerwald', role: 'lead'}, {name: 'Justin Trudeau', role: 'member'}];
})
Then everything works as expected.
I'm clearly doing something wrong in the teamMembers method, presumably returning the wrong array type or something, but I can't figure out what.
Many thanks for your help!
For array there is DS.PromiseArray. Wrapping promise into this will make it work with each helper as is. You can use if guard around to display loading state.
//service
teamMembers: Ember.computed('token', function() {
const promise = this.get('store').findAll('teamMember', {include: 'user,organization'});
return DS.PromiseArray.create({promise});
})
// template
{{#if session.teamMembers.isFulfilled}}
{{#each session.teamMembers as |teamMember|}}
<li>{{teamMember.name}}</li>
{{/each}}
{{else}}
loading...
{{/if}}

Filtering an array in ember

Ok so I'm fairly new to programing, I know how to run a filter on a JSON Array but I cant seem to figure it out when I'm pulling the data from firebase and viewing it in an Ember app.
this is my route.js code:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('user');
}
});
This is my template.hbs code the href="#!" is the generic from materialize for the dropdown buttons:
<div class="list-wrapper col-xs-10 col-xs-offset-1">
<div class="col-xs-12 button-div">
{{#md-btn-dropdown text='Filter' class="col-xs-2" belowOrigin=true}}
<li>Female</li>
<li>Male</li>
<li>All</li>
{{/md-btn-dropdown}}
</div>
{{#each model as |info|}}
<div class="col-xs-3 user-card">
<div class="card-info">
<ul>
<li>Last Name- {{info.lastName}}</li>
<li>First Name- {{info.firstName}}</li>
<li>Gender- {{info.gender}}</li>
<li>{{info.email}} </li>
</ul>
</div>
</div>
{{/each}}
</div>
{{outlet}}
This is my controller.js code which I no is all wrong:
import Ember from 'ember';
export default Ember.Controller.extend({
customFilter: function(gender) {
return function(el) {
var r = el.user;
return r.gender === gender;
};
}
});
and this is my model:
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
lastName: DS.attr('string'),
firstName: DS.attr('string'),
gender: DS.attr('string'),
email: DS.attr('string')
});
I've searched high and low and I'm sure I'm just missing something basic and stupid. What I want is for the dropdown menu to be able to filter and display only female, male or all. Again I'm new to this stuff so I apologize if this is a pretty basic thing. Thank You
What your missing is an action that updates your controller when an item in the dropdown is actually selected.
Some helpful reading:
Triggering changes with actions
Computed Properties
Here's how to put actions in your dropdown component
{{#md-btn-dropdown text='Filter' class="col-xs-2" belowOrigin=true}}
<li><a {{action "filterUpdated" "female"}}>Female</a></li>
<li><a {{action "filterUpdated" "male"}}>Male</a></li>
<li><a {{action "filterUpdated"}}>All</a></li>
{{/md-btn-dropdown}}
In your controller you then need to handle this action like so:
import Ember from 'ember';
export default Ember.Controller.extend({
// the people property is an alias of the model object
// which essentially makes people a synonym for model
// read more http://emberjs.com/api/classes/Ember.computed.html#method_alias
people: Ember.computed.alias('model'),
// holds the currently selected gender, e.g., "female". A null value indicates there is no filter.
currentFilter: null,
/*
filteredPeople is a computed array containing people models.
The array is recomputed every time the model changes or the currentFilter changes,
see the .property() bit at the end.
read more: http://emberjs.com/api/classes/Ember.computed.html#method_filter
*/
filteredPeople: Ember.computed.filter('people', function(person/*, index, array*/) {
// this function is passed each item in the model array, i.e., the person argument
// there's no need to use the index nor array arguments, so I've commented them out
if(this.get('currentFilter') === null) {
// when the filter is null, we don't filter any items from the array
return true;
} else {
// otherwise only return true if the gender matches the current filter
return person.gender === this.get('currentFilter');
}
}).property('people', 'currentFilter'),
actions: {
filterUpdated: function (value) {
if (Ember.isEmpty(value)) {
// an empty value means we should clear the filter
this.set('currentFilter', null);
}
else {
this.set('currentFilter', value);
}
}
}
});
Finally, edit your template to change
{{#each model as |info|}}
to
{{#each filteredPeople as |info|}}
Also at a meta level, don't apologize for asking questions! Everyone is new at something at somepoint, and often asking is the best way to learn. That's what stackoverflow is all about :)
Something like this would work:
gender: 'All',
filteredModel: Ember.computed.filter('model', function(person) {
return person.gender === this.get('gender');
}).property('gender'),
this assumes that it starts on all, and then when the dropdown changes the value of gender, then the filteredModel will get updated. You can then in your hbs file change the result to:
{{#each filteredModel as |info|}}

Controller computed property based on model only valid in loop

I have a the following in Ember
Route
model: function() {
return this.store.findAll('competency');
}
Controller
calendarItems: Ember.computed('model', function() {
return this.get('model').map(function(competency) {
return {
'name': competency.get('title'),
'start': competency.get('endDate')
};
});
})
Template
{{#each model as |item|}}
{{log calendarItems}}
{{/each}}
{{log calendarItems}}
For some reason unknown to me the {{log calendarItems}} inside the loop displays correctly with all of the store items in the models mapped correctly. But only when the {{log calendarItems}} is not present outside the loop.
When the {{log calendarItems}} is also present outside the loop it causes all 4 log statements to return [] as though the model had nothing to map.
If {{log calendarItems}} is on its own it also returns [].
Am I missing something fundamental about Ember here?
Thanks in advance,
Ryan
This won't necessarily fix the logging, but it should fix the computed property in that it should update as records become available (if the real problem is that the objects are loading asynchronously, which is kind of my suspicion)
calendarItems: Ember.computed('model.#each.{title,endDate}', function() {
return this.get('model').map(function(competency) {
return {
'name': competency.get('title'),
'start': competency.get('endDate')
};
});
})

Can't load form to create new instance of model

router.js has
Router.map(function() {
this.route('concepts');
this.route('create-concept', { path: '/concepts/new' });
this.route('concept', { path: '/concepts/:id' });
});
controllers/create-concept.js has
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
create: function() {
var concept = this.store.createRecord('concept', {
description: this.get('description').trim()
});
this.set('description', '');
concept.save();
}
}
});
templates/create-concept.js has
{{textarea value="description"}}
{{action 'create'}}
routes/create-concept.js is just the default
import Ember from 'ember';
export default Ember.Route.extend({
});
When I go to the URL /concepts/new in my browser, however, I just get the error
Uncaught TypeError: Cannot read property 'getAttribute' of undefined
in my browser. I have no idea what the cause of this generic error would be, and documentation on this seems to be pretty sparse...
EDIT -- when I am visiting /concepts and click on a link to /concepts/new, it shows
Preparing to transition from 'concepts' to ' create-concept'
Transitioned into 'create-concept'
so it would appear that it does find the correct route
Since it's hard to copy and paste this for some reason, here's a screenshot of part of the stack trace:
EDIT 2 -- Minimal code demonstrating problem
I don't think you really want value="description", you probably mean value=description
One assigns the literal string description to the value property, the other binds the property description to the value property.
Your action needs to actually be attached to something! A div, button, anchor tag.
<div {{action 'create'}}>blah</div>
<button {{action 'create'}}>blah</button>
http://guides.emberjs.com/v1.10.0/templates/actions/

How does one access model data in a router/controller?

Bear with me please, I'm new.
Been breaking my head over this problem and sort of here as last resort. It's about how to access a model's data when that route loads. For instance, when /meals/2 loads, I want a function to run that sets the background of the document using that model's background-image string property. Or when /meals loads, the a function that uses a property of the collection's first item.
Any help on 'the ember way' to do this would be much appreciated.
Menu.hbs
{{#each meal in model}}
<span {{action 'mealSelected' meal.image_large}}>
{{#link-to 'menu.meal' meal tagName="li" class="meal-block" href="view.href"}}
[...]
{{/link-to}}
</span>
{{/each}}
<div id="meal-info-wrapper">
{{outlet}}
</div>
Model:
export default DS.Model.extend({
name: DS.attr('string'),
image: DS.attr('string')
});
Router.js
export default Router.map(function() {
this.route('about');
this.route('menu', { path: '/' }, function() {
this.route('meal', { path: '/meal/:id/:slug' });
});
});
routes/menu.js
export default Ember.Route.extend({
model: function() {
return this.store.find('menu');
},
afterModel: function() {
Ember.$(document).anystretch('temp-images/bg-1.png');
}
});
What I want to do in routes/menu.js for instance would be to have that image url be supplied by the model.
afterModel will run only once the model has been resolved, and the model is passed as an argument. So, based on my understanding of your app, you can adjust your routes/menu example to:
export default Ember.Route.extend({
model: function() {
return this.store.find('menu');
},
afterModel: function(model) {
Ember.$(document).anystretch(model.get('firstObject.image'));
}
});
Correct me if I misunderstood something, what you want to do is:
Change the background image of a DOM element based on a property found
in each Model's record.
Model loading is an async operation, you want to do the image swaping once you are sure the data is loaded. You used the afterModel hook to guarantee that, but that is not enough.
You want to modify the DOM inside your template, but you need to make sure that the template has been rendered. So, the DOM manipulation logic, instead of placing it in afterModel, it belongs to the didInsertElement event that Views have.
I suggest you use a component (its a view too), something like:
// your template
{{#each meal in model}}
{{meal-component content=meal}}
{{/each}}
// the meal-component
didInsertElement: function() {
var imgURLProperty = this.get('content.imgURLProperty');
Ember.$(document).anystretch(imgURLProperty);
}
Of course, you can't copy paste any of that. It just shows you the main mechanic of how you can modify a template based on the properties of a model.