I have an adapter something like this:
var Adapter = DS.RESTAdapter.extend({
host: 'http://localhost:4200'
});
if (config.environment === 'production') {
Adapter.reopen({
host: 'http://foo.example.com'
});
}
This has been working for a while, but recently something breaks. My ember app interfaces with a number of different subdomains (eg foo.example.com and bar.example.com). My understanding is that reopen changes all instances, which is what I think happens. When I browse to '/bar' it uses the correct adapter in production, but browsing to '/foo' still uses the bar.example.com endpoint.
My question is two fold. Firstly, am I using the right approach here?
Secondly, I'd like to change the adapter based on a runtime setting. I need to work around IE8 lack of CORS support so am thinking that if I have an ie8 switch, the adapter should hit example.com/foo instead of foo.example.com. In my mind these two areas are conceptually related, but I'm happy to be wrong.
update
To simplify things, I essentially want to find the hostname of the request and pass that into the adapter. For example if I browse to www.example.com I want the adapter to go fetch a record from www.example.com/foo, or when browsing to www.example2.com I want the adapter to fetch the record from www.example2.com/foo. I hope that makes sense. Is this even possible?
I'm not 100% sure on this, but my guess is that the value is being cached. My recommendation is to make host a volatile computed property. This actually works out better for you, since that will allow you to better select the host based on runtime configuration. Try something like this:
var Adapter = DS.RESTAdapter.extend({
host: function() {
if (config.environment === 'production') {
return 'http://foo.example.com';
} else {
return 'http://localhost:4200';
}
}.property().volatile()
});
Because Ember-Data always uses Ember's get method to fetch properties, changing host from a normal property to a computed one should make no difference. And because of that, you're able to select the host at runtime, and make sure that it's computed every time. (Don't worry about the performance of not caching that value, I promise it won't make a noticeable difference.)
import ENV from 'ember-arp-roomviewer/config/environment';
export default DS.JSONAPIAdapter.extend({
namespace: 'arp/v1',
//host: 'http://localhost/arp/index.php/wp-json',
//host: 'http://www.acme.net/myroomplan/wp-json',
host: function() {
if (ENV.environment === 'production') {
return 'http://www.acme.net/myroomplan/wp-json';
} else {
return 'http://localhost/arp/index.php/wp-json';
}
}.property().volatile(),
headers: {
'Content-type': 'text/plain' // Stops OPTION headers being sent PITA
}
Related
I'm building an an ember app for demo purposes (important to note, as some of what I'm doing here is not secure/best practice). I have no back end and am publishing it on github pages (the purpose is to show Ember, not Rails or whatever other backend I might write).
The data this app is displaying is mostly from a public api (the Riot api if it matters]). I'll get my data from there quite easily, but obviously I can't modify and save it or anything. To that end, I set up a RESTAdapter:
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'https://prod.api.pvp.net',
namespace: 'api/lol/na/v1.3',
buildURL: function (x, y) {
var url = this._super(x, y);
return url + '?api_key=<my_api_key_here :)>';
}
});
I was somewhat surprised to see this error: No 'Access-Control-Allow-Origin' header is present on the requested resource.
It had built the url string correctly, but the request to their api failed. I googled around for quite awhile and discovered this is a common sense restriction by them to prevent cross-site abuse of the api or some such, and also to prevent javascript apps (such as mine!) from doing exactly what I was doing. Please note that I'm aware of why I shouldn't do this, and wouldn't if this were in any way a production app. Since it's just for my own ends, I figured I would be okay leaving my api key in the javascript and doing it this way, if only I could figure out a way around this error.
Why I was surprised it didn't work
I'm surprised because previous to using Ember data and the REST Adapter, I was loading models from this exact api with simple ajax promises, and it worked like a charm. Doing the following worked:
$.ajax({
url: 'https://prod.api.pvp.net/api/lol/na/v1.3/summoner/<some id>?api_key=<my_api_key>',
success: function (modelJson) {
store.createRecord('summoner', modelJson); //or somehing
}
});
Why should this work, while whatever the REST Adapter is trying to do isn't? Either way the request is coming from my framework right? How can I get Ember Data to request its data more closely to the above, which I know works?
Something else I tried
It's worth noting I tried the following initializer:
initialize: function () {
Ember.$.ajaxPrefilter(function (options) {
options.xhrFields = { withCredentials: true };
});
}
The error message changes to A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true.
I asked a question similar to this, here, specifically about how to implement specific settings for a specific controller. In short, I wanted to implement checkInSettings for the whole CheckInController so that my index, settings, and reports templates and controllers have access to the checkInSettings.
I did get my answer to that; however, I think that specific settings might be limiting and it would be better served by making a settings object or store, and defining something like settings.checkIn for the check in settings.
I've looked for resources online but haven't come up with many answers... So, how should I best go about creating application wide settings, with sub settings for specific areas of my app?
A note: I would like to refrain from using Ember Data since it is not Production Ready yet, and this app will eventually be consumer facing.
Thank you!
Ember Data is a different beast. Store them on the application controller. Or if you don't want o clutter the application controller, create a singleton instance of a settings controller and store them there. (The same thing can be done just on the application controller, just use application instead of settings).
App.SettingsController = Ember.Controller.extend({
someSettingOn: false,
someOtherSetting: null
});
And then in other routes/controllers:
App.AnyRoute = Ember.Route.extend({
anyMethod: function(){
this.controllerFor('settings').toggleProperty('someSettingOn');
}
})
App.AnyController = Ember.Controller.extend({
needs: ['settings'],
anyMethod: function(){
var setting = this.get('controllers.settings.someOtherSetting');
console.log(setting);
},
anyProperty: function(){
if(this.get('controllers.settings.someSettingOn')){
return 'yes';
}
return 'no';
}.property('controllers.settings.someSettingOn')
})
I would like to have a route substate not show up in the URL, but still be able to take advantage of having a route class on which I can define renderTemplate, model, setupController, etc. hooks. Is this possible with the v2 router? I am using Ember release candidate 2.
Here's an example.
Suppose I have the routes:
/exercise/:exercise_id
/exercise/:exercise_id/correct
/exercise/:exercise_id/incorrect
I would like all of these to show up in the URL as:
/exercise/:exercise_id
As I don't want the student to just directly type in /correct onto the end of the ULR and get to the correct answer. And although I have a way to prevent that from working, the full route still shows up in the URL. From the student's perspective, I only want them to think about the state as /exercise/:exercise_id.
Of course I could just store the state correct vs. incorrect in some controller variable, but then I loose the convenience of having route classes, ExerciseCorrectRoute and ExerciseIncorrectRoute, which I want to behave differently, and so the hooks, like renderTemplate and setupController, are nice to have defined cleanly in separate places.
Thoughts?
Kevin
UPDATE:
I went with Dan Gebhardt's suggestion because I like to keep things as much as possible within the framework's considered design cases, as this seems to reduce headaches given Ember is still evolving. Also I didn't get a chance to try out inDream's hack.
Although I still think it would be nice if the router added a feature to mask substates from the URL.
Every route must be associated with a URL for Ember's current router.
Instead of using multiple routes, I'd recommend that you use conditionals in your exercise template to call the appropriate {{render}} based on the state of the exercise. In this way you can still maintain separate templates and controllers for each state.
You can reference to my answer in Ember.js - Prevent re-render when switching route.
Reopen the location API you're using and set window.suppressUpdateURL to true if you want to handle the state manually.
Ember.HistoryLocation:
Ember.HistoryLocation.reopen({
onUpdateURL: function(callback) {
var guid = Ember.guidFor(this),
self = this;
Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
if(window.suppressUpdateURL)return;
// Ignore initial page load popstate event in Chrome
if(!popstateFired) {
popstateFired = true;
if (self.getURL() === self._initialUrl) { return; }
}
callback(self.getURL());
});
}
});
Ember.HashLocation:
Ember.HashLocation.reopen({
onUpdateURL: function(callback) {
var self = this;
var guid = Ember.guidFor(this);
Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
if(window.suppressUpdateURL)return;
Ember.run(function() {
var path = location.hash.substr(1);
if (get(self, 'lastSetURL') === path) { return; }
set(self, 'lastSetURL', null);
callback(path);
});
});
}
});
In my route I have a method that tries to request a list of models from the server
model: ->
App.MyModel.find
projectId: (#modelFor "project").id
Now obviously sometimes this might return a 404.
At the moment when this happens, Ember just stops doing anything. No view is rendered, no controller is setup.
So how can I properly handle the 404 (ie show an error view)?
Bad news: right now, ember-data doesn't do anything when it gets a 404 on find(). At all. The model sits in the 'loading' state forever.
There are no non-completely-stupid options, here, in my opinion. What I would probably do as a last resort is add a notFound attribute on my DS.Model, and instead of returning 404, return JSON with notFound set to true. It's painful, I know...
--- I had originally offered a solution of overriding find in a subclass of RESTAdapter. Then I noticed that find DOES NOT get passed the record instance it is supposedly loading. So, no go on handling 404s by putting the record into an error state.
[NOTE: ember-data has changed dramatically since March 2013, the information in this answer may no longer be operative]
Incidentally, the "new" BasicAdapter was just released now. The question for me was, will this make things easier to handle 404 errors.
Approach #1
My first approach - similar to what Christopher was suggesting - was to add an additional field containing the HTTP status.
status: DS.attr("number");
And then I used this AJAX call:
$.getJSON(url, data).then(null, function(xhr) {
return {
id: id,
statusCode: xhr.status
};
}).always(function(data) {
return process(data).load();
});
What this does is to transform the error response (xhr) to a hash containing the requested id and the status code. Finally, the successful result or the failed hash are passed to the store.
This kind of works, but isn't very practical: When you show a list of all model instances those "mock" instances have to be filtered out manually.
Approach #2
Another idea was to create a special error model.
App.Error = App.Model.extend({
status: DS.attr("number")
});
And the according query:
$.getJSON(url, data).then(null, function(xhr) {
return App.store.load(App.Error, {}, {
id: 0,
status: xhr.status
});
}).done(function(data) {
return process(data).load();
});
This will load and create a new instance of the error model and put it into the store.
The problem with this is that Ember wasn't really "accepting" this. The application just stopped routing, doing nothing anymore. So this seems like a dead end as well :(
I hit this issue as well today.
However, after looking at the source, it appears the model is actually setup to utilize Ember.Evented, and we can add our own handlers for these cases.
The two events that caught my eye were becameError and didLoad.
In my case I was able to do something like the following:
// Grab a user by id.
var user_rec = App.User.find( user.id );
// Oh no! Error!
user_rec.on('becameError', function() {
alert('I could not find you!');
});
// We should be good! Proceed!
user_rec.on('didLoad', function() {
alert('Your email: '+this.get('email'));
});
Here's the source on github: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/model.js
Hopefully, if this is indeed the way we should be handling things, there will be some added documentation in the guides in the near future.
Using the latest revision of Ember-Data and the RESTAdapter, is there a way of doing the following?
I have a resource called App and a API that responds to /apps by returning the correct JSON (with { apps: [...] }etc.)
Since this gets served from a static json on our server, it is quiet inappropriate to create server-side resources for every app that can be fetched as /apps/:app_id. Instead, it would be good if the RESTAdapter allways loaded /apps, even if it then only uses one single app out of the fetched ones.
Do I have to write my own Adapter to achieve this? If yes, what would be a good point to "hook into"?
Supposing you have an app model App.App, it should be enough to call App.App.find() when your application loads. This will make the AJAX call to /apps. Even if you don't cache the result in a variable, your data store will be populated with the returned records. Now whenever you call App.App.find(id), Ember Data will check your store and return the record if it has it. If it doesn't have the record, then it will try to call /apps/:id, but this shouldn't happen if your application is designed to use only a static collection.
There are a few different places you could put the App.App.find() call. I would probably put it in App.ready:
App = Ember.Application.create({
ready: function() {
// pre-load apps
App.App.find();
}
});
App.App = DS.Model.extend({
//...
});
It seems a little hacky (and probably is), but it looks like one can achieve this by overwriting the DS.Adapter:find().
In my case, to block calls to /app/:app_id I wrote this:
find: function(store, type, id) {
// Terminate calls for single app
if (type === App.App) {
// instead, load all apps and drop this request
App.App.find();
return;
}
// or continue as usual
this._super(store, type, id);
}
This also works when you have a hierarchy of embedded: 'always' records and Ember thinks it has to load a middle level. Just make sure you load the parent for sure when dropping requests like this!