Backbone.js: Dynamic model/collection url based on user input - backbone.js

I'm total new to Backbone so please pointing me the right way to do it. Here is what I have:
A Backbone login View with 3 inputs: serverIP, username, password. I am doing all the validation and send jquery ajax() request to my backend host based on the serverIP that the user has to enter earlier.
My backend is js PHP using Slim restful framework, check user, password like usual basic stuff.
On the callback of successful ajax() call, I want to set the urlRoot for latter use of all models and collections as I'm using Slim for all database interactions and this PHP file located on the server.
I tried to set it on the global app variable like this in app.js:
var app = {
api_url: '',
views: {},
models: {},
routers: {},
utils: {},
adapters: {}
};
In the callback of login view I set:
app.api_url = "http://"+serverIP;
And try to use app.api_url in backbone model url but it's apparently undefined.
May be this is not the correct approach I'm trying and I messed up with the variable scope? Then how can I set model.url from the view? Please, any suggestions are much appreciated.
Thanks,
Hungnd
EDIT: Ok, I will try to elaborate my problem again:
Here is the login function in my LoginView.js, basically it take user inputs and send to my model to interact with the server, if success navigate to home view:
var user = new app.models.Login();
var userDetails = {
serverIP: $('#serverIP').val(),
username: $('#username').val(),
password: $('#password').val()
};
user.save(userDetails, {
success: function(data) {
/* update the view now */
if(data.error) { // If there is an error, show the error messages
}
else { // If not, send them back to the home page
app.router = new app.routers.AppRouter();
app.router.navigate('home',true);
}
},
error: function() {
/* handle the error code here */
}
Here is my LoginModel.js, get the serverIP from user input on login form and send to the server to process
app.models.Login = Backbone.Model.extend({
urlRoot: function(){
var serverIP = this.get('serverIP');
return "http://"+serverIP+"/api/login";
},
defaults: {
'serverIP': '',
'username': '',
'password': '',
}
});
Now, after successful login, navigate to HomeView.js, on initialize it calls to EmployeeCollection, so far so good
initialize: function () {
//Search result
this.searchResults = new app.models.EmployeeCollection();
this.searchResults.fetch({data: {name: ''}});
this.searchresultsView = new app.views.EmployeeListView({model: this.searchResults});
}
Here is my EmployeeModel.js where I have the problem, I dont know how to access the serverIP variable.
app.models.Employee = Backbone.Model.extend({
urlRoot:"api/employees",
//urlRoot: app.api_url+"/api/employees",
initialize:function () {
this.reports = new app.models.EmployeeCollection();
this.reports.url = app.api_url+'/api/employees/' + this.id + '/reports';
}
});
app.models.EmployeeCollection = Backbone.Collection.extend({
model: app.models.Employee,
//url: "api/employees",
url: function() {
//How to get serverIP?
},
});

All models in backbone already have an url property which will be used to fetch data. In your case you could define it as a function to generate url dynamically.
Here is an example :
//we are inside the definition of the loginModel
data: {
serverIP : null,
username : null,
password : null
},
url: function() {
var url = "rootUrl",
data = this.get("data");
return function() {
return url + '?' + $.param(data);
};
}
url is then defined as a closure, and object being references in javascript, the url generated will use the current values in the data object.

Related

AngularJS factory. I need to call a method in a factory passing in a parameter then I need to retrieve the result from a different controller

I am new to AngularJS. I am building an email front-end as a college project.
I have an inbox view that retrieves emails from a json file. It works as expected by making this call: $scope.emails = InboxService.query();.
When the user clicks on an email they are redirected to a new page where I want to view the email, also from a json file (for testing only).
The controller:
app.controller('InboxController', function ($scope, $location, InboxService, EmailService) {
//Make call to email by id
$scope.viewEmail = function(emailId)
{
//DOES NOT WORK
EmailService.find({id: emailId});
$location.path('inbox/email/' + emailId);
};
//Make call to inbox
$scope.emails = InboxService.query();
});
When the user clicks on an email I want to use the id to retrieve another json file and pass it to a separate controller for a new page.
This is my EmailController:
app.controller('EmailController', function ($scope, InboxService, EmailService) {
$scope.emails = {};
//DOES NOT WORK
EmailService.getEmail(function(response){
$scope.emails.email = response;
});
});
This is the email service: DOES NOT WORK
app.factory('EmailService', function ($resource) {
var thisEmail = {};
thisEmail.find = function () {
thisEmail = $resource('json/message/:id.json', {}, {
get: {method: 'GET', params: {id: '#id'}}
})
},
thisEmail.getEmail = function () {
return thisEmail;
};
return thisEmail;
});
The service does not do what i want. I want to retrieve a json file using an id, then be able to access that file in the EmailController.
Try:
app.factory('EmailService', ['$resource',function ($resource) {
return $resource('json/message/:id.json', {}, {
get: {method: 'GET', params: {id: '#id'}}
});
}]);
In your controller:
EmailService.get({id:emailId});
You should use the built in state provider https://docs.angularjs.org/api/ngRoute/service/$route
or https://github.com/angular-ui/ui-router/wiki instead of directly working with the location hash.

actions in $resource in angularjs

I have a webapi back end with the following method in the ProductController :
[HttpGet]
[Route("api/product/FindName")]
public Product FindName(string name){
return name & "Hello "
}
I am trying to make use of $resource in the frontend.
var resource = $resource('api/product/:id', {});
resource.query() will return all items which are exposed in the server side using the GetALL() method. This works fine .
What exactly is the {action} in the $resource does ? I have seen examples for the POST, but what if is set
var resource = $resource('api/product/:id', {}, { FindName: { method: 'GET', params: { name: 'phone' } } });
will this call the method FindName in the backend ? or what exactly it does, I mean the parameter if I set the 'GET' in method.
I am calling as
resource.FindName({ name: 'phone' }, function () {
});
But the backend is not getting fired . i see the call that is being requested to the server from fiddler is
Demo/api/product?name=phone
The resource declaration is incorrect. It should be
var resource = $resource('api/product/:id', {}, { FindName: { method: 'GET', params: { id: 'phone' } } });
This defaults the id placeholder to value phone.
For invocation now you can do
resource.FindName({}, function () { //gets api/product/phone
});
or override id part
resource.FindName({id:'tablet'}, function () { //gets api/product/tablet
});
Resource has a built in GET function that should be able to be used without the need to define the extra FindName action that has been added to $resource.
If you changed the route on your webapi to be
[HttpGet]
[Route("api/product/{name}")]
public Product FindName(string name){
return name & "Hello "
}
Then you could use resource like this to get data back from this route.
var resource = $resource('api/product/:id', {}, {});
resource.get({ id: 'phone' }, function () {
});
If you wanted the name params to match on both you could change :id to :name and in the resource.get change id to name also.
I hope this helps.

Adding a PATCH method to Backbone collections

I have a web application that is using a Django/Tastypie backend with a Backbone/Marionette frontend. I'd like to use Tastypie's bulk operations to create multiple objects via my API using a PATCH request to a list endpoint.
My understanding is that Backbone doesn't support this. What is the best way to add this to Backbone? I assume I'll need to add a save method to Backbone's collection object and extend the Backbone sync method.
From http://backbonejs.org/
If instead, you'd only like the changed attributes to be sent to the server, call
model.save(attrs, {patch: true}). You'll get an HTTP PATCH request to the server
with just the passed-in attributes.
Fiddle Sending patch request on backbone collection sync:
$(function() {
Backbone.$ = $;
var User = Backbone.Model.extend({
urlRoot: "/testUrl",
isNew : function () { return false; },
defaults: {
name: 'John Doe',
age: 25
}
});
var user1 = new User();
var user2 = new User();
var user3 = new User();
var user4 = new User();
var UserCollection = Backbone.Collection.extend({
model: User,
url: "/testUrl"
});
var userCollection = new UserCollection([ user1, user2, user3]);
// update
user1.set('name','Jane Doe');
user4.set('name','Another User');
// delete
userCollection.remove(user2);
// add
userCollection.add(user4);
userCollection.sync('patch', userCollection , { error: function () {
console.log(userCollection); } });
});

Backbone PUT is sending data www-form-urlencoded instead of application/json

I'm pretty new to backbone and how it works and inherited a bunch of code but I can't solve this at all:
I have a user model:
var User = Backbone.Model.extend({
idAttribute: 'username',
defaults: {
username: "",
email: "",
roles : [],
password: ""
}
});
var Users = Backbone.Collection.extend({
model: User,
initialize: function(args, options) {
if (options && options.dialog) {
this.dialog = options.dialog;
}
},
parse: function(response) {
if (this.dialog) {
this.dialog.populate(response);
}
return response;
},
url: function() {
var segment = AdminUrl + "/users";
return segment;
}
});
Then elsewhere in my view I'm doing:
user.save({username: $newtarget.val()},null);
or
user.save();
The PUT is fired to the correct url but every time its triggered it sends the data
Content-Type application/x-www-form-urlencoded; charset=UTF-8
but my Jersey endpoint accepts application/json
Everywhere I read people are struggling to put urlencoded data but my problem is the otherway around!
Parameters are being send as url params:
username=admin&email=&password=admin&roles%5B%5D=ROLE_USER&roles%5B%5D=ROLE_ADMIN&id=1
===EDIT===
If I force the content type and data:
user.save({}, {data: JSON.stringify(user.attributes),contentType: "application/json"});
The put works fine, which is bizarre.
Backbone.emulateJSON = false;
is true for some reason
From the docs
Turn on emulateJSON to support legacy servers that can’t deal with
direct application/json requests … will encode the body as
application/x-www-form-urlencoded instead and will send the model in a
form param named model.
http://backbonejs.org/docs/backbone.html

Backbone.js router triggering on put, not on post

So I have been working with basic Backbone.js to create a simple demo page that is a basic User Manager. I run the same function whether I am creating (POST) or updating (PUT) a user's information. Both the POST and PUT requests are successful (I.E. calling my "success" function) however only the PUT command will trigger the router, the POST request does not trigger the same line of code while it DOES trigger the success function.
Below is my events code. The saveUser function takes care of both the PUT and POST requests but the router in success is only fired from a successful PUT request, not from a successful POST request (even though both are successful in updating the database).
events: {
'submit .edit-user-form': 'saveUser',
'click .delete': 'deleteUser'
},
saveUser: function (ev) {
var userDetails = $(ev.currentTarget).serializeObject();
var user = new User();
reply = user.save(userDetails, {
success: function (){
router.navigate('', {trigger: true});
},
error: function(){
$("#editError").toggle();
$("#editError").html("Error:<br/>"+reply.status+" : "+reply.statusText+"<hr/>");
}
});
return false;
},
deleteUser: function (ev){
$.ajaxSetup({
headers: {
'method':"DeleteUser"
}
});
reply = this.user.destroy({
success: function (){
router.navigate('', {trigger: true});
},
error: function(){
$("#editError").toggle();
$("#editError").html("Error:<br/>"+reply.status+" : "+reply.statusText+"<hr/>");
}
})
return false;
}
Here is the router code:
var Router = Backbone.Router.extend({
routes:{
'new': 'editUser',
'edit/:id': 'editUser',
'': 'home'
}
});
var router = new Router();
//ROUTES
router.on('route:home', function(){
alert("1");
userList.render(),
editUser.render({});
});
router.on('route:editUser', function(id){
userList.render();
editUser.render({id: id});
});
//NECESSARY
Backbone.history.start();`
Any help would be appreciated!
Thanks!
The problem is that when you're creating a new user, you're already at the url "", and router.navigate(newUrl) only triggers if newUrl is different than the current page url.
See https://github.com/documentcloud/backbone/issues/652.
To fix, change
router.navigate('', {trigger: true});
To
Backbone.history.fragment = null; // Forces the current URL to be dirty
router.navigate('', {trigger: true}); // Now, Backbone thinks the new URL is different
When you post, the current url is "http://gocella.com/backbone/#", after you post you navigate to "http://gocella.com/backbone/#" which doesn't change the url, which won't fire the event.
Router only triggers the event when the url changes, it works from update because when you hit edit your url changes to "http://gocella.com/backbone/#/edit/1", and then the trigger changes back to "http://gocella.com/backbone/#"
I think the approach of using the router for this problem is just overall incorrect.
You should do something more along the lines of:
when you hit save, create a new model or update the existing model with the form values, then use collection.create(modelFromForm) or, if you use model.save, then on the success callback, add it to the collection

Resources