ember-data isValid, isSaving and isError - ember.js

I have a simple ember-data model:
WZ.Exercise = DS.Model.extend
name: DS.attr 'string'
description: DS.attr 'string'
group: DS.belongsTo 'WZ.Group'
I want to display a confirmation message to the user if a new record has been saved or if an error has occurred. The error could be that the the object is invalid and an error json is returned like below:
{"errors":{"description":["can't be blank"]}}
I can see that each model comes with an isSaving, isValid property and an isError property.
Can anybody tell me how I can use these properties to display the correct notifications to the users?

I can't help you with the validations part, but if you want to display information to the user based on the state of the data you can use these status in your view template like so:
{{#if content.isNew }}
<button {{ action save }} >Save</button>
{{/if}}
{{#if content.isSaving }}
<i>Saving record...</i>
{{/if }}
{{#if content.isLoaded }}
<b>Record created</b>
{{/if }}
{{#unless content.isValid }}
<error>Error saving data</error>
{{/unless }}

Additional to sly7_7's first link (adding the ObserverSaveOnce function to DS.Model), you could patch the RESTadapter to catch the server-side error messages.
An example implementation you can find here: https://gist.github.com/3981832
(I did not paste the code here because I may update the gist for newer versions of ember-data)

Related

Ember unable to retain dropdown value

I am using Ember 2.5.0. In my application, I have created a page with a dropdown using the ember-select-list plugin.
I am able to render the dropdown, but unable to retain the value of the dropdown. Whenever I select the value, I am getting the following exception in the chrome console:
Assertion Failed: Cannot call get with 'id' on an undefined object
Please find the following code, for reference:
Template :
{{select-list content=roles
optionValuePath="role"
optionLabelPath="role"
value=role
action=(action (mut role))}}
Route.js
export default Ember.Route.extend({
model(){
return Ember.RSVP.hash({
books : this.store.findAll("book"),
roles : this.store.findAll("role")
});
},
setupController(controller,model){
controller.set('users',model.books);
controller.set('roles',model.roles);
}
});
Model
export default DS.Model.extend({
role:DS.attr()
});
In the router, when I pass Array(roles: this.store.findAll("role").toArray()) instead of model, I can retain the value, but it throws an error while passing model.
Anyone, could you please help me to resolve this issue?
The ember-select-list documentation and unit test indicate that an array is required for the content property on the {{select-list}} helper.
This is why Ember.RSVP.hash seems to fail, as hash expects and returns an object, which is a type that ember-select-list is not configured to use.
Instead of hash, you should use Ember.RSVP.all, as all expects and returns an array, which should work.
I've created an Ember Twiddle example to demonstrate.
If you'd rather not use ember-select-list, you might find it easier to merely use Ember's each helper to build out your own select list, like this:
<select onchange={{action "selectRole" value="target.value"}}>
{{#each roles as |role| }}
<option value="{{ role }}" label={{ role }}>
{{ role }}
</option>
{{/each}}
</select>
I would suggest you use ember-power-select addon.
To your question, you can try this,
setupController(controller,model){
controller.set('users',model.books);
controller.set('roles',model.roles.toArray());
}
let me know if this is not working

Ember Data 2 - Submit Two Associated Models in One Form

Total Ember newb here. My back-end is written with Rails. That piece is fine, so assume all is well on the server side. Here are the specifics:
I have two models
Project (Has Many Project Details)
Project Details (Belongs To Project)
I have confirmed that:
I am able to create a new Project from my projects/new.hbs template
My Index route retrieves all Projects and the Project Details associated with them.
My Show route retrieves an individual Project and the Project Details associated with them.
What I would like to do is, submit a project model along with a project detail model from my projects/new.hbs template.
I understand that the form in new.hbs should be in a component but I am keeping it simple for now. Once, I am able to create my associated models I will work on doing the same as a form component.
Project Model
export default DS.Model.extend({
project_details: DS.hasMany('project_detail', { async: true }),
project_name: DS.attr('string'),
status: DS.attr('string')
});
Project Detail Model
export default DS.Model.extend({
project: DS.belongsTo('project', { async: true }),
project_id: DS.attr('number'),
feature_name: DS.attr('string'),
hours_billed: DS.attr('number'),
available_hours: DS.attr('number'),
amout: DS.attr('number')
});
Projects/New.js Route
export default Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
projects: this.store.createRecord('project'),
project_details: this.store.createRecord('project_detail')
});
},
actions: {
create: function() {
var self = this;
this.controller.get('model').save().then(
function() {
self.transitionTo('projects.index');
});
}
}
});
Projects/New.hbs
<form {{ action "create" on="submit" }}>
<label for="project_name">Project Name</label>
{{ input value=project_name }}
<label for="status">Status</label>
{{ input value=status }}
<label for="feature_name">Feature Name</label>
{{ input value=feature_name }}
<label for="available_hours">Available Hours</label>
{{ input value=available_hours }}
<label for="hours_billed">Hours Billed</label>
{{ input value=hours_billed }}
<label for="amount">Amount</label>
{{ input value=amount }}
<button type="submit">Save</button>
</form>
Router.js
Router.map(function() {
this.route('home');
this.route('dashboard');
this.route('login');
this.resource('projects', function() {
this.route('new');
this.route('show', {path: '/:id'});
this.route('edit', {path: '/:id/edit'});
});
});
When I submit the form, the action appears to be called, but all data that is submitted is NULL, so evidently, my inputs are not being recognized. I have searched for a couple days and have not seen a solution for submitting multiple models. My code is a result of expanding on a standard POST along with how I handled retrieving my associated models via the RSVP Hash. Any advice would be great. Thanks!
You've got some things that are not wired up correctly.
When you return the following from your route:
model: function() {
return Ember.RSVP.hash({
projects: this.store.createRecord('project'),
project_details: this.store.createRecord('project_detail')
});
},
In your template, you can use {{model.something}} to access that information, in your case you have access to {{model.projects}} and {{model.project_details}}.
You probably want to do the following in your route to setup easier access to your models in the templates.
setupController(controller, model) {
//this._super(controller, model) I'll explain why this is commented
controller.set('project', model.projects);
controller.set('project_details', model.project_details);
}
If you do this, then in your template you can do things like this:
<label>Project Name</label>
{{input value=project.project_name}}
{{project.project_name}}
Which brings to the next thing. When you have in your template the following:
{{ input value=project_name }}
What you're doing is binding the input value to the project_name property of your controller. If you use the Ember Inspector (hint, if you're not using it, you should. It makes life so much easier) you can probably check that the controller has that property project_name with the value you typed.
Before Ember 2.0 there was this thing called the Object Controller (which you don't use anymore) that allowed proxying the model to the controller. Which meant that you could on your route do this:
model() {
return this.store.createRecord('project');
}
And in your template you effectively to this:
<label>Project Name</label>
{{project_name}}
Today you have to do this (Unless you install ember-legacy-controllers which I don't recommend):
<label>Project Name</label>
{{model.project_name}}
As you can see, you need to add the model before the project_name. This is because the model returned from the route is set as a property of the controller by default. That's something we can change, of course. You could also do the following in your route:
setupController(controller, model) {
controller.set('model', model.projects);
controller.set('myProjectDetails', model.project_details);
}
And in the template you could now do the following:
<form {{ action "create" on="submit" }}>
<label for="project_name">Project Name</label>
{{ input value=model.project_name }}
<label for="status">Status</label>
{{ input value=model.status }}
<label for="feature_name">Feature Name</label>
{{ input value=myProjectDetails.feature_name }}
<label for="available_hours">Available Hours</label>
{{ input value=myProjectDetails.available_hours }}
<label for="hours_billed">Hours Billed</label>
{{ input value=myProjectDetails.hours_billed }}
<label for="amount">Amount</label>
{{ input value=amount }}
<button type="submit">Save</button>
</form>
Which means that your save function now works because the "project" is now your model. If you used my example of setupController where I did this:
controller.set('project', model.projects);
Then you would have to change your create action to this:
actions: {
create: function() {
var self = this;
this.controller.get('project').save().then(
function() {
self.transitionTo('projects.index');
});
}
}
Also, do notice that you have your models declared with
//Project
project_details: DS.hasMany('project_detail', { async: true }),
//Project Detail
project: DS.belongsTo('project', { async: true }),
But you're not setting the relations in the code you posted. If you want the models to be related, in your create action you would need something like this (or in any other place really). I'm assuming in the following snippet that my setupController example is in the route so that the project is in the controller as project and the project_details as project_details.
create: function() {
var self = this;
var project = this.controller.get('project');
var projectDetails = this.controller.get('project_details');
project.get('project_details').pushObject(projectDetails);
project_details.set('project', project);
project.save().then(function() {
self.transitionTo('projects.index');
});
}
Assuming you're using Ember Data 2.0, there's also another problem with this. When you save the project it will only make a post request to save the project itself (it won't save the newly created project_details with it). At the moment JSONAPI(the standard Ember Data 2.0 by default uses) does not have a solution for updating multiple resources in one go (see this github issue). This means that you would have to save the project_details first and only then then project (which probably doesn't make sense looking at your models as it seems the project_details only exists under a project.)
It's the current state of affairs, as far as I know. I'm also looking for a good solution for this problem (I've hacked together solutions like only allowing to create a project_detail after the project is created, or having an extra attribute in the project with is a string of the serialized project_detail json and then have the backend to the things it needs)

Ember Data belongsTo with Select dropdown view

I am trying to do something pretty basic with Ember and Ember Data.
1) Posts belongsTo Users; Users hasMany Posts
2) In the create new Post form, I want to have a select/dropdown of all Users
3) When I edit a post (using the same form), I want to bind it correctly back to the dropbox
Question: What is the best practice to do the dropdown that binds to the list of users?
How can I bind the edit form to populate the dropdown again?
User Model:
App.User = DS.Model.extend({
posts: DS.hasMany('post', {async: true}),
});
Post Model:
App.Post = DS.Model.extend(Ember.Validations.Mixin, {
user: DS.belongsTo('user', {async: true}),
});
Create New Post Form:
{{view Em.Select
content=users <<<<< How do I bind this.store.find("user") here?
optionValuePath='content.id'
optionLabelPath='content.name'
}}
I don't understand the best practice to bind the select content with users.
Attempt 1:
*I am using Ember-Form
{{em-select
property="user_id"
label="user"
prompt="Select user:"
content=controllers.users.content
optionValuePath="content.id"
optionLabelPath="content.first_name"
}}
And on the save action:
newItem.set('user', this.store.getById('user', this.get('user_id')));
I tried to use user_id as my property for the form, and translate back to a user object to assign to the post when I save. However, this method is kind of stupid because I am actively translating user_id to user object every time I save. I feel like there should be a way that that is done automatically if I did the correct binding, instead of jumping through hoops to bind the property as user_id. This also makes it hard for me to bind back when I use the same form for editing the post. Since all the relationship has been setup, I have a feeling that I am doing something wrong here. I think there must be a better way.
Thanks!
As of September 2014 there is an issue with Ember.Select plus { async: true }. They don't play well together.
myModel.get('myAsyncRel') will return a promise, Ember.Select is not promise aware, so it cannot handle this properly.
One workaround is to use a non-async relationship.
https://github.com/emberjs/data/issues/1405
https://github.com/emberjs/data/issues/2111
Another workaround is to use something like this:
http://emberjs.jsbin.com/rwjblue/43/edit?html,css,js,output
The ember team has opened an issue (#5259) for this problem, they are planning to rewrite the whole Ember.Select part.
In the meantime you can use this Add-on from thefrontside:
{{#x-select value=bob action="selectPerson"}}
{{#x-option value=fred}}Fred Flintstone{{/x-option}}
{{#x-option value=bob}}Bob Newhart{{/x-option}}
{{/x-select}}
Install it with ember install emberx-select
EDIT As Stefan Penner wrote, you can now use the ember select as follows (jsbin):
<select onchange={{action "changed" value="target.value"}}>
{{#each model.choices key="#identity" as |choice|}}
<option value={{choice}} selected={{is-equal currentValue choice}}>{{choice}}</option>
{{/each}}
</select>
When you say dropdown I presume you mean a select box? Ember's API has a select view that handles all of the data binding. Here are the docs for Ember.Select.
You can use it in your post form like so:
{{view Em.Select
content=users
selection=user
optionValuePath='content.id'
optionLabelPath='content.name'
}}
If you're using Dockyard's awesome ember-easyForm library you can utilize an Em.Select like this:
{{input user type='as'
collection='users'
selection='user'
optionValuePath='content.id'
optionLabelPath='content.name'
prompt='Choose type...'
}}
Advanced UI using Chosen can be easily integrated with Ember like this.
New deprecation information was added in Ember 1.13 for Ember Select, see http://emberjs.com/deprecations/v1.x/#toc_ember-select.
The new recommended approach in Ember 1.13/2.0 is to implement a component yourself:
e.g.
/components/my-select.hbs
<select>
{{#each users "id" as |user|}}
{{log address}}
<option value="{{user.id}}">
user.name
</option>
{{/each}}
</select>
The ember link shows how to implement selection with selected={{is-equal item selectedValue}} and creation of a is-equal helper.

Ember.js server side form validation display

I have a form in Ember JS and I want to:
1. Display the errors for the fields right next to each field in error ;
2. Display any general errors at the top of the form ;
3. Have these errors persist while the user correct the form.
Edit: JSBin here
I have the following template for the form:
{{#if isSaving}}
<p>Saving Record</p>
{{/if}}
{{#if isError}}
<p>There was an error saving the record</p>
Base errors( {{errors.base}} )
{{/if}}
<form {{action 'create' this on='submit'}}>
<p>Title: {{input type="text" value=title}}</p>
<p>Title Errors( {{errors.title}} )</p>
<p>Body: {{textarea value=body}}</p>
<button>Create</button>
</form>
Right now my server is returning the following
{"errors":{"body":["can't be blank"],"title":["should begin with a capital letter"],"base":["General error message here"]}}
errors.title above is returning an object. How can I get the message out of it.
When the user starts typing, the object message is wiped out.
The isError never seems to fire.
What am I doing wrong?
First: Your errors.title is (like all the other properties of your server response) an Array so you'd get the title with errors.title[0]. As you cannot reference it like this from Handlebars directly you'd either have to provide it from the controller or pass your server response to a custom helper.
Second: It's hard (or better to say impossible) to tell why isError is never triggered/set without code...
errors is an array, and the proper way to iterate over them is the following:
{{#each errors.fieldname}}
{{this.message}}
{{/each}}

link-to action parameters are not working in ember js

Hi i am very new to ember js. i pass action parameters(id ) on link-to action in template but i did not get the values in my controller.
My Template code as follows:
index.html:
<script type="text/x-handlebars" data-template-name="search">
{{#each model.results}}
// here i pass id value along with action
{{#link-to 'profile' id action="profileinfo"}}
</script>
app.js:
App.SearchController = Ember.ObjectController.extend({
id: '',
actions:{
profileinfo: function(id){
// Here i access id value like this
console.log(id);
var id = this.get('id');})
when i click on the link action goes to Searchcontroller, but i get id value is empty.I follow some solutions in stack overflow but unfortunately i did not get anything. Please provide some solution
I don't get why you're using the {{#link-to}} helper for triggering an action on your controller. Maybe you could simply use the {{action}} helper ?
If you try doing it that way, would it work ?
<button type="button" {{action "profileinfo" id}}>Click me !</button>
From there, your console.log(id); should get your value.
EDIT
Would also work for a <a> tag
<a href="#" {{action "profileinfo" id}}>Click me !</a>
I've created popular addon for doing just that:
{{#link-to 'profile' id invokeAction='profileinfo'}}
Simply install it:
ember installember-link-action
Please leave a star if you enjoy it or leave feedback if you feel anything is missing. :) It works with Ember 1.13 and 2.X (tested on Travis CI).