to pull a JSON into a model from an API, I would normally do this:
return Ember.$.getJSON('http://....');
However is there a difference in syntax if I am using Swagger?
I just got introduced to this the other day at work. The back-end developers are starting to use it... but this is new to all of us so I don't know how to utilize this on the front-end?
I'm not familiar with Swagger at all, but you can always use a promise and hook up the results that way.
Assuming the swagger connection
swagger = new SwaggerApi({
url: "http://petstore.swagger.wordnik.com/api/api-docs",
success: function() {
if(swagger.ready === true) {
// upon connect, fetch a pet and set contents to element "mydata"
}
}
});
model: function(){
return Ember.RSVP.Promise(function(resolve, reject){
swagger.apis.pet.getPetById({petId:1}, function(data) {
resolve(data.content.data);
});
});
});
Related
I have a pretty standard post model with a title and a text field. I have two routes for the model -- new route and show route. I want to create a post from new route and then transition to show route.
This is my router file
this.route('post-new', { path: '/posts/new' });
this.route('post-show', { path: '/posts/:postId' });
and submit action in post-new controller is something like this.
actions: {
submit() {
const { title, text } = this.getProperties('title', 'text');
let post = this.store.createRecord('post', {
title: title,
text: text
});
post.save().then(() => {
//success
this.transitionToRoute('post-show', post);
}, () => {
//error
});
}
}
So I am expecting this to redirect from http://localhost:4200/posts/new to something like http://localhost:4200/posts/23 (assuming 23 is id).
The save() is successful and record is created on the backend (which is rails) and I also see the post record updated in browser (it now has an ID) using Ember Inspector. But the redirection is happening to http://localhost:4200/posts/undefined.
How can I make this to redirect to something like http://localhost:4200/posts/23 after save ?
Btw, The versions are:
ember cli : 2.3.0-beta.1
ember : 2.3.0
ember data : 2.3.3
UPDATE
I was able to make it work by replacing this
this.transitionToRoute('post-show', post);
with this
this.transitionToRoute('/posts/' + post.id);
But I am hoping for a solution using the route name and not actual route path.
Try:
post.save().then(savedPost => {
//success
this.transitionToRoute('post-show', savedPost);
},
You can implement the serialize hook on your route.
serialize(model) {
return { postId: model.get('id') };
}
This will allow you to avoid calling the model hook if you already have the model. So, both of these will work as expected:
this.transitionToRoute('post-show', post); // this will NOT call model() hook
this.transitionToRoute('post-show', post.id); // this will call the model() hook
More information available in the API docs for Route.
My models work with the server via Ember's default RESTAdapter.
I just created a custom endpoint /mail on my server which sends an e-mail if provided a name, valid e-mail-adress and text.
How do I make Ember send that custom post-request? Is it possible without Ember.ajax at all?
For me personally, I wouldn't use Ember-Data to handle that scenario; I generally only use Ember-Data to handle my persisted models. If you try to use Ember-Data for other AJAX calls, it's just going to become a mess. Remember that Ember-Data's job is to manage your persisted data and one way that it can do that is with AJAX calls. That doesn't mean that anything that requires an AJAX call should be handled with Ember-Data.
I have this same issue and I wrote a utility module that has functions for all of my non-model AJAX stuff. This makes it really easy to swap out for testing. Here's a small example:
// utils/ajax.js
export function sendHelpEmail(comment) {
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: '/api/contact_us',
contentType: 'application/json',
data: JSON.stringify({ comment }),
processData: false,
statusCode: {
200: () => Em.run(null, resolve),
500: () => Em.run(null, reject)
}
});
});
}
Then, I can do something like this in my controller:
import { sendHelpEmail} from '../utils/ajax.js';
export default Em.Controller.extend({
actions: {
sendEmail() {
sendHelpEmail(this.get('comment'));
}
}
});
I'm using Ember Data and I can't seem to get the model's 'errors' property to populate with the error messages from my REST API. I'm pretty much following the example at this guide:
http://emberjs.com/api/data/classes/DS.Errors.html
My app looks like this:
window.App = Ember.Application.create();
App.User = DS.Model.extend({
username: DS.attr('string'),
email: DS.attr('string')
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.store.createRecord('user', {
username: 'mike',
email: 'invalidEmail'
});
},
actions: {
save: function () {
this.modelFor(this.routeName).save();
}
}
});
And my API returns this:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 125
{
"errors": {
"username": ["Username is taken!"],
"email": ["Email is invalid."]
}
}
After I call save() on the model, here is what I see on the user model:
user.get('isError') // true
user.get('errors.messages') // []
Even though the model is registering the isError property correctly, I can't seem to get the error messages to populate. How can I get this to work? I'm working on the latest beta build of Ember Data version 1.0.0-beta.8.2a68c63a
The docs are definitely lacking in this area, the errors aren't populated unless you're using the active model adapter.
Here's an example of it working, also check out Ember: error.messages does not show server errors on save where I say the same thing
http://jsbin.com/motuvaye/24/edit
You can fairly easily implement it on the RESTAdapter by overriding ajaxError and copying how the active model adapter does it.
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 422) {
var response = Ember.$.parseJSON(jqXHR.responseText),
errors = {};
if (response.errors !== undefined) {
var jsonErrors = response.errors;
Ember.EnumerableUtils.forEach(Ember.keys(jsonErrors), function(key) {
errors[Ember.String.camelize(key)] = jsonErrors[key];
});
}
return new DS.InvalidError(errors);
} else {
return error;
}
}
});
http://jsbin.com/motuvaye/27/edit
https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/activemodel-adapter/lib/system/active_model_adapter.js#L102
I've had a long and very frustrating experience with Ember Data's errors.messages property, so I thought I'd summarize all of my findings here in case anyone else tries to use this feature.
1) Documentation is out of date
As #kingpin2k mentioned in his answer, the documentation at http://emberjs.com/api/data/classes/DS.Errors.html is out of date. The example they provide on that page only works if you're using DS.ActiveModelAdapter. If you're using the default DS.RESTAdapter, then you need to do something like this. Note that I prefer this simpler approach instead of just copying ActiveModelAdapter's ajaxError implementation:
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function (jqXHR) {
this._super(jqXHR);
var response = Ember.$.parseJSON(jqXHR.responseText);
if (response.errors)
return new DS.InvalidError(response.errors);
else
return new DS.InvalidError({ summary: 'Error connecting to the server.' });
}
});
2) You must supply a reject callback
This is very strange, but when you call save() on your model, you need to provide a reject callback, otherwise, you'll get an uncaught 'backend rejected the commit' exception and JavaScript will stop executing. I have no idea why this is the case.
Example without reject callback. This will result in an exception:
user.save().then(function (model) {
// do something
});
Example with reject callback. Everything will work well:
user.save().then(function (model) {
// do something
}, function (error) {
// must supply reject callback, otherwise Ember will throw a 'backend rejected the commit' error.
});
3) By default, only the error properties that are part of the model will be registered in errors.messages. For example, if this is your model:
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
...and if this is your error payload:
{
"errors": {
"firstName":"is required",
"summary":"something went wrong"
}
}
Then summary will not appear in user.get('errors.messages'). The source of this problem can be found in the adapterDidInvalidate method of Ember Data. It uses this.eachAttribute and this.eachRelationship to restrict the registration of error messages to only those that are part of the model.
adapterDidInvalidate: function(errors) {
var recordErrors = get(this, 'errors');
function addError(name) {
if (errors[name]) {
recordErrors.add(name, errors[name]);
}
}
this.eachAttribute(addError);
this.eachRelationship(addError);
}
There's a discussion about this issue here: https://github.com/emberjs/data/issues/1877
Until the Ember team fixes this, you can work around this problem by creating a custom base model that overrides the default adapterDidInvalidate implementation, and all of your other models inherit from it:
Base model:
App.Model = DS.Model.extend({
adapterDidInvalidate: function (errors) {
var recordErrors = this.get('errors');
Ember.keys(errors).forEach(function (key) {
recordErrors.add(key, errors[key]);
});
}
});
User model:
App.User = App.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
4) If you return DS.InvalidError from the adapter's ajaxError (the one we overrode above), then your model will be stuck in 'isSaving' state and you won't be able to get out of it.
This problem is also the case if you're using DS.ActiveModelAdapter.
For example:
user.deleteRecord();
user.save().then(function (model) {
// do something
}, function (error) {
});
When the server responds with an error, the model's isSaving state is true and I can't figure out to reset this without reloading the page.
Update: 2014-10-30
For anyone who's struggling with DS.Errors, here's a great blog post that summarizes this well: http://alexspeller.com/server-side-validations-with-ember-data-and-ds-errors/
UPDATE: Ember Data 2.x
The above response are still somewhat relevant and generally pretty helpful but are now outdated for Ember Data 2.x(v2.5.1 at time of this writing). Here are a few things to note when working with newer versions of Ember Data:
DS.RESTAdapter no longer has an ajaxError function in 2.x. This is now handled by RESTAdapter.handleResponse(). You can override this method if any special handling or formatting of errors is required. RESTAdapter.handleResponse source code
The documentation for DS.Errors and DS.Model.errors(which is an instance of DS.Errors) is currently a little misleading. It ONLY works when errors in the response adhere to the JSON API error object specification. This means it will not be at all helpful or usable if your API error objects follow any other format. Unfortunately this behavior can't currently be overridden well like many other things in Ember Data as this behavior is handle in private APIs inside of Ember's InternalModel class within DS.Model.
DS.InvalidError will only be used if the response status code is 422 by default. If your API uses a different status code to represent errors for invalid requests you can override RESTAdapter.isInvalid() to customize which status codes(or other part of an error response) to check as representing an InvalidError.
As an alternative you can override isInvalid() to always return false so that Ember Data will always create a more generic DS.AdapterError instead. This error is then set on DS.Model.adapterError and can be leveraged as needed from there.
DS.AdapterError.errors contain whatever was returned on the errors key of the API response.
This may be abusing Ember, but I want to create a computed property for the number of items in the store.
I'm trying to prototype a UI that exists entirely client-side. I'm using fixture data with the local storage adapter. That way, I can start off with canned data, but I can also add data and have it persist across reloads.
As I'm currently working on the data layer, I've built a settings route that gives me a UI to reset various models. I would like to add a Handlebars expression like {{modelCount}} so I can see how many records there are in the store. That's quicker than using the Ember Data Chrome extension, which resets to the routes tab on every page reload.
The following will show me the number of records once, but does not change when the number of records changes:
modelCount: function() {
var self = this;
this.store.find("my_model").then(function(records) {
self.set("modelCount", records.get("length"));
});
}.property()
I get that the store is supposed to proxy an API in the real world, and find returns a promise, but that's about the limit of my knowledge. I don't know how tell Ember to that I want to know how many records there are, or if this is even a valid question.
Load the result of store.find into an Ember.ArrayController's content and then bind the length of content to modelCount. An example:
App.SomeRoute = Ember.Route.extend({
model: function(){
return this.store.find('my_model');
}
});
App.SomeController = Ember.ArrayController.extend({
modelCount: Ember.computed.alias('content.length')
});
See a working example in http://jsbin.com/iCuzIJE/1/edit.
I found a workable solution by combining the answer from #panagiotis, and a similar question, How to load multiple models sequentially in Ember JS route.
In my router, I sequentially load my models:
model: function() {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
self.store.find("model1").then(function(model1) {
self.store.find("model2").then(function(model2) {
self.store.find("model3").then(function(model3) {
resolve({
model1: model1,
model2: model2,
model3: model3
});
});
});
});
});
},
Then in my controller, I define simple computed properties:
model1Count: function() {
return this.get("model1.length");
}.property("model1.length"),
...
Sorry to ask such a simple question but I'm looking at migrating from jQuery to Ember and am trying to figure out calling / responding json without using ember-data. One question I have is how do people suggest having class methods. Say for example I have a post object like this:
Hex.Post = Ember.Object.extend({
id: null,
body: null
});
Would a reasonable findById look like this?
$(document).ready(function(){
Hex.Post.findById=function(id){
console.log("you are here");
$.getJSON("/arc/v1/api/post/" + id, function(data){
var post = Hex.Post.create();
post.set('id', data.id);
post.set('body',data.body);
return post;
});
};
});
Or is this just wrong for creating a findById class method?
When I run this from the chrome console, it comes back as undefined even though the JSON call works fine in a brower. What am I doing wrong?
thx
FROM CHROME CONSOLE:
You'd want to define it on the class, and return the ajax call, which is then a promise
Hex.Post = Ember.Object.extend({
id: null,
body: null
});
Hex.Post.reopenClass({
findById: function(id) {
return Ember.$.getJSON("/arc/v1/api/post/" + id).then(function(data){
var post = Hex.Post.create();
post.set('id', data.id);
post.set('body',data.body);
return post;
});
}
});
Using the promise
from a model hook, Ember will resolve the promise for you, example below
Hex.PostRoute = Em.Route.extend({
model: function(param){
return Hex.Post.findById(param.id);
}
});
as the promise
Hex.Post.findById(42).then(function(record){
console.log(record);
});
or
var promise = Hex.Post.findById(42);
promise.then(function(record){
console.log(record);
});
And here's a simple example:
http://emberjs.jsbin.com/OxIDiVU/21/edit