I'm working on my first nodejs/backbone project. What I need to do is to extend user model api with additional methods. As REST uses universal post/get/put request, how do you extended backbone model with other api calls (ie. block user account where I don't want to update user and make /user/deactivate url)?
I can take ugly routes, but I looking for the "right way" from pros.
My backbone model
define(["jquery", "backbone"],
function($, Backbone) {
var User = Backbone.Model.extend({
urlRoot: '/user',
defaults: {
username: '',
password: '',
email: ''
}
});
return User;
}
);
My nodejs "router"
app.put('/user', userController.register);
app.post('/user', userController.update);
app.get('/user', userController.list);
app.delete('/user', userController.delete);
Why not add block and deactivate attributes to your model, then just use standard REST / CRUD API calls.
Then your actions block and deactivate would just be standard model updates, with logic to handle blocked and active model states in your API methods.
define(["jquery", "backbone"],
function($, Backbone) {
var User = Backbone.Model.extend({
urlRoot: '/user',
defaults: {
username: '',
password: '',
email: '',
blocked: false,
active: true
},
block: function () {
this.set('blocked', true);
this.save();
},
deactivate: function () {
this.set('active', false);
this.save();
},
});
return User;
}
);
EDIT - Based on your comment, the need to distinguish between updates
If you need to distinguish between field updates on the server, in order to run route specific validation for example. Then you're probably going to need to call custom routes for each specific action. A neat way of doing this would be to override the url during the call to save the model.
An updated model example.
define(["jquery", "backbone"],
function($, Backbone) {
var User = Backbone.Model.extend({
urlRoot: '/user',
defaults: {
username: '',
password: '',
email: '',
blocked: false,
active: true
},
block: function () {
this.set('blocked', true);
this.save(this, { url: '/user/block' });
},
deactivate: function () {
this.set('active', false);
this.save(this, { url: '/user/deactivate' });
},
});
return User;
}
);
Then you would have the following routes
app.put('/user', userController.register);
app.post('/user', userController.update);
app.get('/user', userController.list);
app.delete('/user', userController.delete);
app.post('/user/block', userController.block);
app.post('/user/deactivate', userController.deactivate);
Related
I'm using ember cli mirage to write some acceptance tests for my Ember app. I succeeded to mock server response for login but I'm not happy how I did it. Ember cli mirage have shorthands for route handlers and I would like to use them but everything I try throws me an error(except this solution). Can someone help me to refactor this response?
this.post('/login', ({ users, resources })=> {
let user = users.first();
if(!Ember.isEmpty(resources.first())){
return {
data: {
type: 'user',
id: user.id,
attributes: user,
relationships: {
resources: {
data: [
{ id: resources.first().id, type: 'resource' }
]
}
}
},
};
} else {
return {
data: {
type: 'user',
id: user.id,
attributes: user
}
};
}
});
I have both user and resource model and factory defined, with relationships between them in user and resource model(it's many to many relationship). Here's how I create user in tests
test('User can login', function(assert){
let resources = server.createList('resource', 2),
user = server.create('user', {resources: resources});
loginUser(user.email);
andThen(()=>{
assert.ok(find('a:contains("Logout")'));
assert.equal('resource.content', currentPath());
});
});
If it's many-to-many, you should explicitly create a join record, as direct m2m relationship support does not yet exist.
// mirage/models/user.js
import { Model, hasMany } from 'ember-cli-mirage';
export default Model.extend({
userResources: hasMany()
});
// mirage/models/resource.js
import { Model, hasMany } from 'ember-cli-mirage';
export default Model.extend({
userResources: hasMany()
});
// mirage/models/user-resource.js
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
user: belongsTo(),
resource: belongsTo()
});
test('User can login', function(assert){
let user = server.create('user');
let resources = server.createList('resource', 2),
// create the join records
resources.forEach(resource => {
server.create('user-resource', { user, resource });
});
loginUser(user.email);
andThen(() => {
assert.ok(find('a:contains("Logout")'));
assert.equal('resource.content', currentPath());
});
});
If you need to mock an endpoint that exposes the m2m directly it will take a bit more work. But in general I find that if your Ember app exposes CRUD operations on the relationship, it's good to expose the join record, too. Makes things simpler.
That being said, Mirage will eventually support m2m relationships.
Using pretty much copy paste from Ember.js docs
App.CresShowResultController = Ember.ArrayController.extend({
queryParams: ['county'],
county: null,
actions: {
displayQueryData: function(){
this.transitionTo({queryParams: {county: 'someCounty'}});
},
},
});
The action is called from another controller after a form is analysed.
I get an error: Uncaught TypeError: undefined is not a function
Here is the route as well.
App.CresShowResultRoute = Ember.Route.extend({
renderTemplate: function(){
this.render('showResult');
}
});
SideQuestion: How can I use transitionTo to change URL parameters straight from another controller without using the action "displayUseryData" as middleman function?
EDIT: Added my Router.map to specify:
App.Router.map(function(){
this.resource('cres', function() {
this.route('showResult');
});
this.resource('about');
this.resource('posts', function() {
//child route posted inside the parent
this.resource('post', { path: ':post_id'});
});
});
Like always, thank you for any helping comments!
try:
App.CresShowResultController = Ember.ArrayController.extend({
queryParams: ['county'],
county: null,
actions: {
displayQueryData: function(){
this.transitionTo({queryParams: {county: 'someCounty'}});
}.bind(this),
},
});
I think that "this" in the displayQueryData function is a window reference.
In the case you reference above, just updating the queryParam itself will have the effect you are looking for.
In the route, you can define if these queryParams will replace the url, or if it will refresh the model.
Use the transitionToRoute, when you wish to switch to a different route, or different model.
App.CresShowResultController = Ember.ArrayController.extend({
queryParams: ['county'],
county: null,
actions: {
displayQueryData: function(){
this.set('county', 'someCounty');
// this.transitionTo({queryParams: {county: 'someCounty'}});
},
},
});
If I have in the router map:
this.resource('detail', { path: '/detail/:type' }, function() {
...
});
And I retrive the currentPanth in my Ember Application code:
currentPath: '',
ApplicationController : Ember.Controller.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
console.log('currentPath',App.currentPath);
}.observes('currentPath')
}),
When I navigate in my app, I get the route names by console, but when It is "detail" I get "detail.index". How can I get the type?
you only have access to the params in the route, ie. when you are defining your model:
App.Router.map(function() {
this.resource('photo', { path: '/photos/:photo_id' });
});
App.PhotoRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON('/photos/'+params.photo_id);
}
});
Or you can also use paramsFor, also in the route only.
Depending on what you are trying to acomplish maybe query params suit better
I'm not sure what is the elegant way to pass server variables in to my Model.
For example, i have an id of user that has to be implemented on my Model. But seems like Backbone with require are not able to do that.
My two options are:
Get a json file with Ajax.
Add the variable on my index.php as a global.
Someone know if exists a other way. Native on the clases?
Trying to make work the example of backbonetutorials. I am not able to throw a callback when the method fetch().
$(document).ready(function() {
var Timer = Backbone.Model.extend({
urlRoot : 'timeserver/',
defaults: {
name: '',
email: ''
}
});
var timer = new Timer({id:1});
timer.fetch({
success: function(data) {
alert('success')
},
fail: function(model, response) {
alert('fail');
},
sync: function(data) {
alert('sync')
}
});
});
The ajax request it has been threw. But does not work at all. Because any alert its dispatched.
var UserModel = Backbone.Model.extend({
urlRoot: '/user',
defaults: {
name: '',
email: ''
}
});
// Here we have set the `id` of the model
var user = new Usermodel({id: 1});
// The fetch below will perform GET /user/1
// The server should return the id, name and email from the database
user.fetch({
success: function (user) {
console.log(user);
}
})
The server will reply with a json object then you can leave the rendering part for your backbone. Based on a template for the user.
You may also want to check these out: http://backbonetutorials.com/
I'm working on an EmberJS frontend to my Spring backend API and the first thing I needed to tackle was the "login" screen. I've been reading tutorials across the web and whatever I can find on Ember, however, the problem seems that there are just too many different versions of the same tutorials and different ways of doing the same thing. It's been confusing me, but, after a lot of toiling away, I think I may have finally managed to get my login screen to work (partly).
It authenticates nicely using ajax and I can see in the dev console that everything is working well.
Before I proceed any further, I first wanted to have people's opinions about the ember code I have written thus far and if it is within "best practices" for an ember application. Also, if there are any better ways to go about solving the same problem.
Secondly, I have no idea how to switch to a different view on a successful login. I admit I'm still not familiar with the concepts of ember, but the tutorials online just keep confusing me.
I'd really appreciate any help here. I'm a fast learner and I think that I can take things from here depending on the answers.
Here's the code from my app.js file:
App = Ember.Application.create();
//------------------------------------------------------------
// Hold the details of the logged-in user
//------------------------------------------------------------
App.LoggedInDetails = Ember.Object.create({
Fullname: null,
CompanyName: null,
TokenID: null,
setDetails: function(fullname, companyname, tokenid)
{
this.Fullname = fullname;
this.CompanyName = companyname;
this.TokenID = tokenid;
},
clearDetails: function()
{
this.Fullname = null;
this.CompanyName = null;
this.TokenID = null;
}
});
//------------------------------------------------------------
App.ApplicationView = Ember.View.extend({
templateName: 'logged-out'
});
App.ApplicationController = Ember.Controller.extend({
isError: false,
errorMessage: null,
authenticate: function()
{
var email = this.get('email');
var password = this.get('password');
var url = 'https://api.example.com/security/authenticateUser.json';
$.ajax({
url: url,
type: 'POST',
dataType:'json',
data: {email: email, passwd: password},
crossDomain: true,
context: this,
success: this.authenticateCompleted
});
console.log('Url: ' + url);
},
authenticateCompleted: function(data)
{
// Login was a success!
if(data.status === 'OK')
{
console.log('status: ' + data.status);
console.log('fullName: ' + data.fullName);
console.log('tokenId: ' + data.tokenId);
console.log('companyName: ' + data.companyName);
// Populate the LoggedInDetails object
App.LoggedInDetails.setDetails(data.fullName, data.companyName, data.tokenId);
this.set('isError', false);
}
else
{
App.LoggedInDetails.clearDetails();
this.set('errorMessage', 'Invalid Email/Password Combination');
this.set('isError', true);
console.log(data.status);
console.log(data.description);
}
}
});
//------------------------------------------------------------
App.Router = Ember.Router.extend({
enableLogging: true,
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet({
viewClass: App.ApplicationView,
controller: router.get('applicationController')
});
}
})
})
});
//------------------------------------------------------------
App.LoggedInView = Ember.View.extend({
templateName: 'logged-in'
});
App.LoggedInController = Ember.Controller.extend({
Fullname: App.LoggedInDetails.get("Fullname")
});
//------------------------------------------------------------
App.initialize();
My problem stems from switching from the "logged-out" default view to the "logged-in" view, which is the actual UI of my application.
Many thanks for any help.
Simplest approach: add an isLoggedIn property to ApplicationController and set it to true when the user authenticates. Make ApplicationView render a template that uses {{#if isLoggedIn}} ... {{else}} ... {{/if}} to switch between the logged in view and the non-logged in view.