I'm trying to develop an extremely simple auth system in Ember.js 1.5 but I am having some trouble for the front-end part.
I am very new to ember so please excuse my ignorance.
So here are my routes and their implementations
App.Router.map(function() {
this.route('login');
this.resource("posts");
});
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (Ember.isEmpty(App.Auth.get('authToken'))) {
this.transitionTo('login');
}
}
});
App.LoginRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('login_template');
},
actions: {
createSession: function() {
var router = this;
var email = this.controller.get('email_login');
var password = this.controller.get('password_login');
if (!Ember.isEmpty(email) && !Ember.isEmpty(password)) {
$.post('/api/v1/session', {email: email, password: password}, function(data) {
var authToken = data.session.auth_token;
var user_id = data.session.user_id;
App.Store.authToken = authToken;
App.Auth = Ember.object.create({
authToken: authToken,
user_id: user_id
});
router.transitionTo('index');
});
}
}
}
});
App.ApplicationRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.find('post');
}
});
App.ProceduresRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.all('post');
}
});
// Ommitting the models because they aren't necessary for this question
So the main concern of course is that App.Auth is initially undefined. I've copied most of the code from a tutorial and I don't know if ember has any session variables that I can initialize.
In any case, I can always just define App.Auth at the beginning with initial values of null for it's properties but that might have other consequences that I am not aware of (again I am new to ember).
So my question here is that how can I properly store the response from the server (auth_token and user_id) in a nice manner. Also, have the user redirected to the login route if auth_token is not set (which is what I am trying to do with the authenticated route part).
You do not need to provide a full answer if you cannot, even some tips / comments will be extremely helpful, thanks!
I'd probably use the container to register an auth manager, and inject it into everything.
App.initializer({
name: "auth",
before: ['ember-data'],
initialize: function (container, application) {
var auth = Ember.Object.extend({
isAuthenticated: Em.computed.notEmpty('authToken')
});
application.register("my:authToken", auth);
application.inject("controller", "auth", "my:authToken");
application.inject("route", "auth", "my:authToken");
application.inject("store:main", "auth", "my:authToken");
application.inject("adapter", "auth", "my:authToken");
}
});
Then on your adapter you could hook up headers easily (if needed)
App.ApplicationAdapter = DS.RESTAdapter.extend({
headers: function() {
return {
authToken: this.get('auth.authToken')
};
}.property("auth.authToken")
});
From any controller/route you could easily access the auth property from this.auth
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!this.auth.get('isAuthenticated')) {
this.transitionTo('login');
}
}
});
Here's an example with the injection (note, I'm using Ember Data 1.0 beta 8, some of this functionality, the headers, isn't available in older versions of Ember Data).
http://emberjs.jsbin.com/OxIDiVU/639/edit
Related
In my project, I need to maintain a common data object for all the modules in the application.
This is where I store all the REST API's and the app wide data. (Something like store in react redux)
dataService.js
define(['jquery', 'app'], function($, app) {
var url = app.serviceURL;
function loginUser(data) {
data.type = "login";
return $.ajax({
url: url + '/authentication.php',
data: data,
method: "POST"
});
};
function logoutUser(data) {
data.type = "logout";
return $.ajax({
url: url + '/authentication.php',
data: data,
method: "POST"
});
};
return {
actions: {
loginUser: loginUser,
logoutUser: logoutUser
},
user: {
isLoggedIn: ''
}
}
});
I am requiring dataService.js file in ViewModel files to call login and logout services.
dataService.actions.loginUser(data)
.then(function(data) {
dataService.user.isLoggedIn = true; // I changed the app wide data here
}
});
My problem is, when I requiring the dataService in some other ViewModel, the value of dataService.user.isLoggedIn is set as default value. How can I preserve this as an app wide data?
It may be instantiating a new object for each module. You can create a global variable by doing:
document.dataService = new dataService();
Try using the above method. This might solve your problem.
Edit 11/16/14: Version Information
DEBUG: Ember : 1.7.0 ember-1.7.0.js:14463
DEBUG: Ember Data : 1.0.0-beta.10+canary.30d6bf849b ember-1.7.0.js:14463
DEBUG: Handlebars : 1.1.2 ember-1.7.0.js:14463
DEBUG: jQuery : 1.10.2
I'm beating my head against a wall trying to do something that I think should be fairly straightforward with ember and ember-data, but I haven't had any luck so far.
Essentially, I want to use server data to populate a <select> dropdown menu. When the form is submitted, a model should be created based on the data the user chooses to select. The model is then saved with ember data and forwarded to the server with the following format:
{
"File": {
"fileName":"the_name.txt",
"filePath":"/the/path",
"typeId": 13,
"versionId": 2
}
}
The problem is, the typeId and versionId are left out when the model relationship is defined as async like so:
App.File = DS.Model.extend({
type: DS.belongsTo('type', {async: true}),
version: DS.belongsTo('version', {async: true}),
fileName: DS.attr('string'),
filePath: DS.attr('string')
});
The part that is confusing me, and probably where my mistakes lie, is the controller:
App.FilesNewController = Ember.ObjectController.extend({
needs: ['files'],
uploadError: false,
// These properties will be given by the binding in the view to the
//<select> inputs.
selectedType: null,
selectedVersion: null,
files: Ember.computed.alias('controllers.files'),
actions: {
createFile: function() {
this.createFileHelper();
}
},
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path'
});
var gotDependencies = function(values) {
//////////////////////////////////////
// This only works when async: false
file.set('type', values[0])
.set('version', values[1]);
//////////////////////////////////////
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this);
Ember.RSVP.all([
selectedType,
selectedVersion
]).then(gotDependencies);
}
});
When async is set to false, ember handles createRecord().save() POST requests correctly.
When async is true, ember handles GET requests perfectly with multiple requests, but does NOT add the belongsTo relationships to the file JSON during createRecord().save(). Only the basic properties are serialized:
{"File":{"fileName":"the_name.txt","filePath":"/the/path"}}
I realize this question has been asked before but I have not found a satisfactory answer thus far and I have not found anything that suits my needs. So, how do I get the belongsTo relationship to serialize properly?
Just to be sure that everything is here, I will add the custom serialization I have so far:
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeIntoHash: function(data, type, record, options) {
var root = Ember.String.capitalize(type.typeKey);
data[root] = this.serialize(record, options);
},
keyForRelationship: function(key, type){
if (type === 'belongsTo') {
key += "Id";
}
if (type === 'hasMany') {
key += "Ids";
}
return key;
}
});
App.FileSerializer = App.ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
type: { serialize: 'id' },
version: { serialize: 'id' }
}
});
And a select:
{{ view Ember.Select
contentBinding="controller.files.versions"
optionValuePath="content"
optionLabelPath="content.versionStr"
valueBinding="controller.selectedVersion"
id="selectVersion"
classNames="form-control"
prompt="-- Select Version --"}}
If necessary I will append the other routes and controllers (FilesRoute, FilesController, VersionsRoute, TypesRoute)
EDIT 11/16/14
I have a working solution (hack?) that I found based on information in two relevant threads:
1) How should async belongsTo relationships be serialized?
2) Does async belongsTo support related model assignment?
Essentially, all I had to do was move the Ember.RSVP.all() to after a get() on the properties:
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
file.set('type', values[0])
.set('version', values[1]);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this));
}
So I needed to get() the properties that were belongsTo relationships before I save the model. I don't know is whether this is a bug or not. Maybe someone with more knowledge about emberjs can help shed some light on that.
See the question for more details, but the generic answer that I worked for me when saving a model with a belongsTo relationship (and you specifically need that relationship to be serialized) is to call .get() on the properties and then save() them in then().
It boils down to this:
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
// belongsTo set() here
file.set('type', selectedType)
.set('version', selectedVersion);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
// Save inside then() after I call get() on promises
file.save().then(onSuccess, onFail);
}.bind(this));
(Warning: newbie) Going around the block on this one, as each example I see does it differently (and none work for me)
var InitiativesNewRoute = Ember.Route.extend({
model: function(params) {
return this.store.createRecord('initiative', params);
},
actions: {
submit: function() {
var initiative = this.get('model');
initiative.save().then(function(model) {
this.transitionTo('initiatives.show', initiative);
});
}
}
});
Saving barfs as initiative is undefined, but I see a record created in the ember chrome plugin. So it looks like creating the record in model works, but fetching it in the action doesn't.
Also tried this example:
submit: function(initiative) {
initiative.save().then(function(model) {
this.transitionTo('initiatives.show', initiative);
});
}
Without passing passing params to createRecord above, and I get the same error. How do I do this?
Using ember-cli 0.0.39 and easy-form, with the fixture adapter.
Got some help on #emberjs and managed to get it working like this:
var InitiativesNewRoute = Ember.Route.extend({
model: function(params) {
return this.store.createRecord('initiative', params);
},
actions: {
submit: function() {
var _this = this;
var initiative = this.get('controller.model');
initiative.save().then(function(model) {
_this.transitionTo('initiatives.show', model.get('id'));
});
}
}
});
in my app I need that when I visit the root, it redirects to the view of the most recent model that in this case is always the firstObject in the collection.
App.Router.map(function() {
this.resource('threads', { path: '/' }, function() {
this.route('view', { path: ':thread_id' });
});
});
App.ThreadsRoute = Ember.Route.extend({
model: function() {
return this.store.find('thread');
},
afterModel: function(threads) {
this.transitionTo('threads.view', threads.get('firstObject'));
}
});
This is working without problems, but wheter I directly go to the root url or the view one 2 identical requests to /threads are made. As soon I comment the afterModel section the redirection obviously doesn't work anymore but the requests are back to 1.
Any help is gladly accepted!
Since Threads/View are nested routes, the ThreadsRoute will be also called on the View route.
I think you should just call the ThreadsRoute -> ThreadsIndexRoute or separate model and afterModel hooks this way:
(Not tested code)
App.ThreadsRoute = Ember.Route.extend({
model: function() {
// console.log('in model);
return this.store.find('thread');
}
});
App.ThreadsIndexRoute = Ember.Route.extend({
afterModel: function(threads) {
// console.log('in afterModel);
this.transitionTo('threads.view', threads.get('firstObject'));
}
});
Your example is identical to this one:
App.Router.map(function() {
this.resource('threads', { path: '/' }, function() {
this.route('view', { path: ':thread_id' });
});
});
App.ThreadsRoute = Ember.Route.extend({
model: function() {
return this.store.find('thread');
}
});
App.ThreadsIndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('thread');
}
});
If you check the inspector for which route you're in when you visit '/', you'll see that you're inside of
threads.index, having transitioned into each of them in turn, which is why you're seeing the call to find twice.
You can fix this by only having the model hook in ThreadsIndexRoute (e.g. rename your ThreadsRoute to ThreadsIndexRoute)
I'd like to be able to inject my Session singleton into my Ember models. The use case I'm trying to support is having computed properties on the model that react to the user's profile (a property on the Session object).
App = window.App = Ember.Application.create({
ready: function() {
console.log('App ready');
this.register('session:current', App.Session, {singleton: true});
this.inject('session:current','store','store:main');
this.inject('controller','session','session:current');
this.inject('model','session','session:current');
}
});
The injection works fine into the controller but I'm having trouble with getting it to the model. Is there any limitation here? Any special techniques?
-------- Additional Context ---------
Here's an example of what I'd like to be able to do in my model definition:
App.Product = DS.Model.extend({
name: DS.attr("string"),
company: DS.attr("string"),
categories: DS.attr("raw"),
description: DS.attr("string"),
isConfigured: function() {
return this.session.currentUser.configuredProducts.contains(this.get('id'));
}.property('id')
});
By default injections in models don't work. To do so you need to set the flag Ember.MODEL_FACTORY_INJECTIONS = true:
Ember.MODEL_FACTORY_INJECTIONS = true;
App = window.App = Ember.Application.create({
ready: function() {
console.log('App ready');
this.register('session:current', App.Session, {singleton: true});
this.inject('session:current','store','store:main');
this.inject('controller','session','session:current');
this.inject('model','session','session:current');
}
});
The downside of this, is that it create some break changes:
If you have App.Product.FIXTURES = [...] you neeed to use App.Product.reopenClass({ FIXTURES: [...] });
productRecord.constructor === App.Product will evaluate to false. To solve this you can use App.Product.detect(productRecord.constructor).