I have implemented an authentication based on this article
I thus create an App.Session object at application initialization:
Ember.Application.initializer({
name: 'session',
initialize: function (container, application) {
App.Session = Ember.Object.extend({
init: function () {
this._super();
this.set('authToken', $.cookie('auth_token'));
this.set('authAccountId', $.cookie('auth_accountId'));
this.set('authAccountLanguage', $.cookie('auth_accountLanguage'));
},
authAccountIdChanged: function () {
var authAccountId = this.get('authAccountId');
$.cookie('auth_accountId', authAccountId);
//Load the actual account record from the server if the authAccountId is set
//Used to have for example the full name or other properties of the account
if (!Ember.isEmpty(authAccountId)) {
this.set('authAccount', this.store.find('account', authAccountId));
}
}.observes('authAccountId'),
...
I have an observer on authAccountId; thus each time the accountId (the id of the logged in user) is changed, I want to retrieve all details of that user (full name, preferences, etc.).
Before Ember data version 1.0.0, I was using:
this.set('authAccount', App.Account.find(authAccountId));
And this worked. Now I use: this.set('authAccount', this.store.find('account', authAccountId));
And I receive the error: Uncaught TypeError: Cannot call method 'find' of undefined. Also in debugger, this.get('store') results in undefined. I have the impression that the store is not available in Application.initializer. Can somebody help resolving this issue ?
You can use container.lookup('store:main') this will return the store registered in container:
var store = container.lookup('store:main');
this.set('authAccount', store.find('account', authAccountId));
Related
I have the following dialog model:
var DialogModel = Backbone.Model.extend({
user: null,
constructor: function () {
// Initialize user model
this.user = new BW.user.UserModel();
Backbone.Model.apply(this, arguments);
},
parse: function (attributes) {
_.isObject(attributes) || (attributes = {});
// update user's model data
this.user.set(attributes.user);
delete attributes.user;
return attributes;
},
toJSON: fucntion () {
var json = Backbone.Model.prototype.toJSON.call(this);
// serialize user's model data to JSON
_.extend(json, user: this.model.toJSON());
}
});
As you can see from the code above, I store user model into DialogModel and set data on parse and serialize in toJSON.
As some point in time I get dialog:update socket message with the following data:
{
id: 1,
message: 'message',
user: {
<JSON USER DATA>
}
}
To update dialog based on this message data I do the following:
eventBus.on('dialog:update', function (json) {
dialogModel.set(json);
});
But the problem is that user model don't get updated becase parse method does't execute.
So my question is how could I update user model on set method?
One option is to override model.set. Adding something like the following to your DialogModel will do the trick:
set: function (attributes, options) {
this.parse(attributes);
Backbone.Model.prototype.set.apply(this, arguments);
},
Note that I've chosen to call the parse method, since it nicely sets the user data and deletes it from the attributes for us (thus preventing that data from being set on dialogModel).
Click here for a fiddle demonstrating this solution.
Embsters!
I am trying to figure out why my model isn't refreshed after I create a new record and save it to the store.
My route computes the model as follows:
model: function (params) {
var postID = params.post_id,
userID = this.get('session.currentUser.id');
var post = this.store.findRecord('post', postID) ;
var followings = this.store.query('post-following', {
filter: { post: postID }
}) ;
var userFollowing = this.store.queryRecord('post-following', {
filter: { post: postID, user: userID }
}) ;
return new Ember.RSVP.hash({
post: post,
followings: followings,
userFollowing: userFollowing
});
}
My template then renders a list and a button:
{{#each model.followings as |following|}}
...
{{/each}}
{{#if model.userFollowing}}
<button {{action 'follow'}}>Follow</button>
{{else}}
<button {{action 'unFollow'}}>Unfollow</button>
{{/if}}
And my controller creates/deletes the relevant post-following record:
actions: {
follow: function () {
var user = this.get('session.currentUser'),
post = this.get('model.post') ;
this.store.createRecord('post-following', {
user: user,
post: post
}).save();
},
unFollow: function () {
this.get('model.userFollowing').destroyRecord() ;
}
}
When I click the [Follow] button:
a successful POST request is sent
the button is not updated
the list is not updated
When I (refresh the page then) click the [Unfollow] button:
a successful DELETE request is sent
the button is not updated
the list is updated
Do you have any idea of what I'm doing wrong?
Could it be a problem with my payload?
EDIT: Solved!
Well, it sounds like I was expecting too much from ember.
The framework won't automatically update my post-followings array on store.createRecord('post-following', {...}) call.
I then adjusted my controller logic to "manually" update my model:
// in follow action…
userFollowing.save().then( function(){
var followings = store.query('post-following', {
filter: { post: postID }
}).then( function (followings) {
_this.set('model.userFollowing', userFollowing);
_this.set('model.followings', followings);
}) ;
});
// in unFollow action…
userFollowing.destroyRecord().then( function () {
_this.set('model.userFollowing', null);
_this.notifyPropertyChange('model.followings') ;
});
Please note that my backend API design has been criticized by #duizendnegen (see comments). More best practices in this article.
Thanks you for all your help !!!
Brou
For these kind of questions, it really helps to have a smaller, replicated problem (e.g. through Ember Twiddle)
Fundamentally, the new post-following record doesn't match the filter: it is filtered for an attribute { post: 123 } and your post-following object contains something in the lines of { post: { id: 123, name: "" } }. Moreover, your post-following object doesn't contain a property called filter or what it could be - i.e. the query it executes to the server are different than those you want to filter by on the client.
My approach here would be to, as a response to the follow and unfollow actions, update the model, both the userFollowing and followings.
Your issue is that you aren't re-setting the property model to point to the newly created object. You are always accessing the same model property, even after creating a new one.
First thing to be aware of is that, after the model hook in your route, the setupController hook is called that executes:
controller.set('model', resolvedModel)
meaning that the model property on your controller is, by default, set every time the route loads (the model hook resolves). However, this doesn't happen after you create a new record so you must do it explicitly:
let newModel = this.store.createRecord('post-following', {
user: user,
post: post
})
// since model.save() returns a promise
// we wait for a successfull save before
// re-setting the `model` property
newModel.save().then(() => {
this.set('model', newModel);
});
For a more clear design, I would also recommend that you create an alias to the model property that more specifically describes your model or override the default behavior of setupController if you are also doing some initial setup on the controller. So either:
export default Ember.Controller.extend({
// ...
blog: Ember.computed.alias('model') // more descriptive model name
// ...
});
Or:
export default Ember.Route.extend({
// ...
setupController(controller, resolvedModel) {
controller.set('blog', resolvedModel); // more descriptive model name
// do other setup
}
// ...
});
Your model is set when you enter the page. When changes are made, your model doesn't change. The only reason why the list is updated when you destroy the record is because it simply doesn't exist anymore. Reload the model after clicking the follow button or unfollow button, or manually change the values for the list/button.
I have an EmberJS application generated using ember-cli. I'm currently using simple-auth with a custom authenticator.
In the authenticator, when the user logs in I want to save his details so that I can use it later.
I have the following code:
authenticate: function(options) {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject){
API.user.login(options.username, options.password, true).done(function(data) {
// #TODO: Save current user
resolve(data.id);
}).fail(function() {
reject();
});
});
},
User data is available in the variable data.user.
I tried using Ember.set('App.currentUser', data.user); but it's not working. What should I do?
I think it works easiest to use an initializer. Theres several ways you can resolve the user, I think it is easiest if you pass the user_email alongside the grant token from the API
//initializers/session-user.js
import Ember from "ember";
import Session from "simple-auth/session";
export function initialize(container) {
Session.reopen({
setCurrentUser: function() {
var accessToken = this.get('access_token');
var self = this;
if (!Ember.isEmpty(accessToken)) {
return container.lookup('store:main').find('user', {
email: self.get('user_email')
}).then(function (users){
self.set('currentUser', users.get('firstObject'));
});
}
}.observes('access_token')
});
}
export default {
name: 'session-user',
before: 'simple-auth',
initialize: initialize
};
Check this thread for where the idea of this came from: http://discuss.emberjs.com/t/best-practice-for-loading-and-persisting-current-user-in-an-authenticated-system/6987
And if you are using simple-auth > 0.8.0-beta.1 you will need to adjust the initializer
I ended up creating a custom Sessions controller and setting the current user object there, and then creating an alias from the application controller.
Something like what's in this article.
I am building a SPA app with the default Durandal setup. I have multiple views returning data with ajax calls however, it is not working perfectly. I created my shell page with a search box so I can search through a list of employees shown here.
Shell.js
define(['require', 'durandal/plugins/router', 'durandal/app', 'config'],
function (require, router, app, config) {
var shell = {
router: router,
searchData: searchData,
employees: ko.observable(),
search: search,
activate: activate,
};
var searchData = ko.observable('');
function search(searchData) {
var url = '#/employeeSearch/' + searchData.searchData;
router.navigateTo(url);
}
return shell;
function activate() {
router.map(config.routes);
return router.activate(config.startModule);
}
});
shell.html
<div class="input-group">
<input type="text" class="form-control" placeholder="Search Employees" data-bind="value: searchData" />
<span class="input-group-btn">
<button type="submit" class="btn btn-default" data-bind="click: search">Search</button>
</span>
</div>
The user puts in a search and when they click the search button the view below navigates to the employeeSearch page. This does work and return the data and view I need it to here.
define(['require', 'durandal/plugins/router', 'durandal/app', 'config', 'services/logger'],
function(require, router, app, config, logger) {
var goBack = function() {
router.navigateBack();
};
function details(employee) {
var url = '#/employee/' + employee.Id + '/profile';
router.navigateTo(url);
}
var vm = {
goBack: goBack,
employees: ko.observable(),
details: details,
};
return {
activate: function (route) {
var self = this;
return self.getEmployees(route.q);
},
getEmployees: function (query) {
return $.ajax(app.url('/employees?q=' + query),
{
type: "GET",
contentType: 'application/json',
dataType: 'json',
}).then(querySucceeded).promise;
function querySucceeded(result) {
self.employees = result;
logger.log(query + ' Search Activated!', null, 'employeeSearch', true);
}
},
};
});
So then, if I try to search for another name, the url will navigate, the logger will show the value I searched for, however the view itself will not show the new results. Exploring in the Chrome debugger, I view the employees object to contain the new result set, but the view has still not updated. If I refresh the page however, the view does properly show up I have viewed multiple questions on here with similar issues about keeping your data calls in the activate method, make sure the data returns a promise before completing the activate method, and putting DOM manipulation in the viewAttached.
Javascript is not rendering in my SPA
How to use observables in Durandal?
HotTowel: Viewmodel lose mapping when navigating between pages
After putting those practices in my code, I am still having problems getting the view to update correctly.
Are there any other Durandal/Knockout properties I need to be aware of? How can I get the bindings to update every time I navigate to a view with (router.navigateTo(url);). I have set my shell properties (cacheViews: true) to true and false but nothing seems to change.
This is one of many problems I have been having with building SPA apps with Durandal. Trying not to give up yet.
I cant test this quick but a think you handle the observable wrong.
I suspect the "result" var is an array of employees. In this case you might handle this with an observableArray (http://knockoutjs.com/examples/collections.html)
And you cant set the value directly like self.employees
You must call the observable function to set the value like
function querySucceeded(result) {
self.employees(result)
logger.log(query + ' Search Activated!', null, 'employeeSearch', true);
}
In your samples you have not shown/mentioned where knockout is being loaded. If you are using Durandal 2.0 then add the following line to the top of your main.js file above your existing define statement
define('knockout', ko);
I'm improving my knowledge about Backbone.js and have this code sample taken from a tutorial. (http://bardevblog.wordpress.com/2012/01/16/understanding-backbone-js-simple-example/)
This example will not access the server for now, so to simulate the retrieval of data from the server I have a file name movies.json.
What I am trying to do:
Add json data in local storage (using localStorage adapter)
For this I am using Backbone.ajaxSync, Which Is Given to the alias Backbone.sync by the localStorage adapter: I created the method refreshFromServer () to do this
The reason for doing this is that I'm trying to implement a way to get data only one time (and only refresh when i need to)
My issues:
I'm having an error "Uncaught Error: 'url' property or function must be specified" when I call refreshFromServer ().
I do not understand why because I set the url collection. (url : "scripts/data/movies.json" )
Sample code
var Theater = {
Models : {},
Collections : {},
Views : {},
Templates : {}
}
Theater.Models.Movie = Backbone.Model.extend({})
Theater.Collections.Movies = Backbone.Collection.extend({
model : Theater.Models.Movie,
localStorage : new Backbone.LocalStorage("MovieStore"), // Unique name within your app.
url : "scripts/data/movies.json",
refreshFromServer : function() {
return Backbone.ajaxSync.apply(this, arguments);
},
initialize : function() {
console.log("Movies initialize")
}
});
Theater.Templates.movies = _.template($("#tmplt-Movies").html())
Theater.Views.Movies = Backbone.View.extend({
el : $("#mainContainer"),
template : Theater.Templates.movies,
initialize : function() {
this.collection.bind("reset", this.render, this);
},
render : function() {
console.log("render")
console.log(this.collection.length);
}
})
Theater.Router = Backbone.Router.extend({
routes : {
"" : "defaultRoute"
},
defaultRoute : function() {
console.log("defaultRoute");
Theater.movies = new Theater.Collections.Movies()
new Theater.Views.Movies({
collection : Theater.movies
});
Theater.movies.refreshFromServer();
//Theater.movies.fetch();
console.log(Theater.movies.length)
}
})
var appRouter = new Theater.Router();
Backbone.history.start();
Notes:
If a comment localStorage property in the collection
Theater.Models.Movie = Backbone.Model.extend({})
Theater.Collections.Movies = Backbone.Collection.extend({
model : Theater.Models.Movie,
//localStorage : new Backbone.LocalStorage("MovieStore")
...
});
and then in router call normal fetch method
Theater.Router = Backbone.Router.extend({
routes : {
"" : "defaultRoute"
},
defaultRoute : function() {
Theater.movies = new Theater.Collections.Movies()
new Theater.Views.Movies({
collection : Theater.movies
});
//Theater.movies.refreshFromServer();
Theater.movies.fetch();
}
})
I can see the json list correctly in my view
If I use the localStorage property in the collection and then call the standard fetch () method, I see only an empty list (I think it is normal as it is read from the local storage and is empty)
The error only occurs when using the method refreshFromServer () witch use Backbone.ajaxSync (alias for backbone.sync)
Err... my bad. The refreshFromServer implementation is from my answer to your earlier question., and it's completely, uselessly wrong.
Backbone.sync expects arguments (method, model, options), but as it stands, it doesn't get what it needs from refreshFromServer because the refresh method simply sends forward whatever arguments it gets. Sorry for the mistake.
The correct, working implementation would be:
refreshFromServer : function(options) {
return Backbone.ajaxSync('read', this, options);
}
It can be used either via success / error callbacks passed to the options hash:
this.collection.refreshFromServer({ success: function() { /* refreshed... */ });
Or via the jqXHR Promise API:
this.collection.refreshFromServer().done(function() { /* refreshed... */ })
Or not signing up for callbacks and waiting for the collection reset event like in your example:
this.collection.bind("reset", this.render, this);
this.collection.refreshFromServer();
This should work. Please let me know if it doesn't. I fixed my answer in the previous question too, in case someone stumbles onto it.
Edit: To save the data to local storage after refreshing you need to manually save each of the models:
var collection = this.collection;
collection.refreshFromServer({success: function(freshData) {
collection.reset(freshData);
collection.each(function(model) {
model.save();
});
}});