I'm using backbone to make a simple POST to my API. However, I need to add additional details to the json post on save about my user. What is the best way and how?
user : {pk:1, name:test}
var ParticipantView = Backbone.View.extend({
el: '#participant-panel',
events: {
'submit #participant-form': 'saveParticipant'
},// end of events
saveParticipant: function (ev) {
var participantDetails = $(ev.currentTarget).serializeObject();
var participant = new Participant();
participant.save(participantDetails, {
success: function (participant) {
alert("created")
},
error: function (model, response) {
console.log('error', model, response);
}
});// end of participant save function
return false; // make sure form does not submit
}// end of save participants
});// end of participant view
just add it to your participantDetails variable like this:
var participantDetails = $(ev.currentTarget).serializeObject();
var userinfo = {pk: 1, name: "test"};
participantDetails.user = userinfo;
If you want to add properties to the main object do:
var participantDetails = $(ev.currentTarget).serializeObject();
participantDetails.pk = 1;
participantDetails.name = "test";
Related
I have just recently be tasked with creating an SPA. So, I created a new project and selected SPA and found that it loaded all the files I needed including this knockout.js.
I am new to knockout.js so I watched a few videos and I get the idea, but the SPA project just doesn't seem to compute to me as it isn't a Single Page Application because you have to go to a new URL to login, register, authorise, manage account, etc (you get the idea).
So, looking at the code for the index page I can see a view model for the homeView. It looks like this:
function HomeViewModel(app, dataModel) {
var self = this;
self.myHometown = ko.observable("");
Sammy(function () {
this.get('#home', function () {
// Make a call to the protected Web API by passing in a Bearer Authorization Header
$.ajax({
method: 'get',
url: app.dataModel.userInfoUrl,
contentType: "application/json; charset=utf-8",
headers: {
'Authorization': 'Bearer ' + app.dataModel.getAccessToken()
},
success: function (data) {
self.myHometown('Your Hometown is : ' + data.hometown);
}
});
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});
return self;
}
app.addViewModel({
name: "Home",
bindingMemberName: "home",
factory: HomeViewModel
});
and the HTML looks like this:
<!-- ko with: home -->
<!-- removed HTML to make it concise -->
<!-- /ko -->
now, from the look of this (correct me if I am wrong) the with handle states that if there is a variable called home, then display it (I assume this is what the bindingMembername is).
So, seeing that I can guess that if I added another partial page and included it. I could created a view model like this:
function DrawViewModel(app, dataModel) {
var self = this;
Sammy(function () {
this.get('#draw', function () {
app.home = null;
});
});
return self;
}
app.addViewModel({
name: "Draw",
bindingMemberName: "draw",
factory: DrawViewModel
});
so, in theory because this sets the app.home to null whenever anyone navigates to #draw, then the home partial will not be displayed, similarly I could added app.draw = null to the sammy route for the homeViewModel to hide the draw partial.
My issue with this, is that it will get massively complicated the more viewModels I create. So, is there something I am missing? Is there an easier way of doing this?
My ultimate goal is to move all the pages to be SPA (including the login/register pages).
Cheers in advance,
/r3plica
Well, after a bit of messing around I found out how to do this.
Basically I rewrote the AddView method and made it look like this:
// Other operations
self.addViewModel = function (options) {
var viewItem = new options.factory(self, dataModel),
navigator;
// Add view to AppViewModel.Views enum (for example, app.Views.Home).
self.Views[options.name] = viewItem;
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (self.view() !== viewItem) {
return null;
}
return new options.factory(self, dataModel);
});
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
};
are you can see, if check to see if the current held view is different to the one I am adding. If it is, then I return null (which is how I get it to hide any views I am not using).
To answer my question further, I needed a way of working out how to direct to the login page if the user was not logged in.
Again in app.viewmodel.js I added a few observable properties:
// UI state
self.user = ko.observable(null);
self.loggedIn = ko.computed(function () {
return self.user() !== null;
});
and in my new login.viewmodel.js I added this function:
// Operations
self.login = function () {
self.loggingIn(true);
dataModel.login({
grant_type: "password",
username: self.userName(),
password: self.password()
}).done(function (data) {
if (data.userName && data.access_token) {
app.navigateToLoggedIn(data.userName, data.access_token, self.rememberMe());
} else {
//self.errors.push("An unknown error occurred.");
}
}).fail(function (jqXHR, textStatus, error) {
dataModel.displayError(jqXHR);
}).always(function () {
self.loggingIn(false);
});
};
the important bit here is the app.naviateToLoggedIn method. This is located in the app.viewmodel.js and looks like this:
// UI operations
self.navigateToLoggedIn = function (userName, accessToken, persistent) {
if (accessToken) {
dataModel.setAccessToken(accessToken, persistent)
}
self.user(new UserViewModel(self, userName, dataModel));
self.navigateToHome();
};
the userViewModel is dead simple:
function UserViewModel(app, name, dataModel) {
var self = this;
// Data
self.name = ko.observable(name);
// Operations
self.logOff = function () {
dataModel.logout().done(function () {
app.navigateToLoggedOff();
}).fail(function (jqHXR) {
dataModel.displayError(jqHXR);
});
};
return self;
}
and finally, to get our initial load right, in the home.viewmodel.js js file, I have this sammy declaration:
Sammy(function () {
this.get('#home', function () {
if (app.loggedIn()) {
app.navigateToHome();
} else {
window.location.hash = "login";
}
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});
So my Backbone.js code is getting the JSON... I am trying to simply console out the models in the success callback of the fetch method but I just get back [r,r,r] instead of [object,object,object]. Pulling my hair out...
var Person = Backbone.Model.extend();
var PersonCollection = Backbone.Collection.extend({
model : Person,
url: 'js/names.json',
parse: function(data) {
console.log(data); // <--- this will return what I am looking for
return data;
}
});
var PersonView = Backbone.View.extend({
initialize: function(){
var self = this;
self.collection = new PersonCollection();
self.collection.fetch({success: function() {
console.log(self.collection.models); // <-- how do I get it here?
}});
}
});
var newView = new PersonView();
JSON
[
{ "name": "Linda", "birthYear": 1947},
{ "name": "Kat", "birthYear": 1977},
{ "name": "Jen", "birthYear": 1989}
]
edit: I want the same thing after the fetch I get when I console.log the data in a custom parse method in the collection. See comments in code above
You are getting confused. The models in the collection are Backbone models which wrap around your records, not anything directly resembling your JSON you are giving to the collection. If you want that, consider console.log(JSON.stringify(self.collection)).
Looking here suggests that your callback function will get passed the items, e.g.
users.fetch({
success: function (users) {
var template = _.template($('#user-list-template').html(), {users: users.models});
that.$el.html(template);
}
})
So maybe a tweak to your callback will help...
I tried this code, adding to toJSON now log the same object that parse function logged. If you are not getting same output there could be some thing wrong with other part of the code.
var Person = Backbone.Model.extend();
var PersonCollection = Backbone.Collection.extend({
model : Person,
url: 'js/names.json',
parse: function(data) {
console.log(data); // <--- this return array of name objects
return data;
}
});
var PersonView = Backbone.View.extend({
initialize: function(){
var self = this;
self.collection = new PersonCollection();
self.collection.fetch({success: function() {
console.log(self.collection.toJSON()); // <-- even this return array of name objects
}});
}
});
var newView = new PersonView();
I am having an issue where my collection property (in this case Parameters Collection) in my model is not being included in the JSON string created by the JSON.stringify function. Is there any reason why this might be happening? It basically just excludes it and adds the rest of the variables to the JSON string.
Here is the event:
EventAggregator.on('toggleFacet', function (facets) {
var facets = SearchOptionsUtil.getCheckedFacets(facets);
var sortOptions = SearchOptionsUtil.getSortOptions();
var searchOptions = new SearchOptionsModel();
for(var facet in facets){
var id = facet;
var value = facets[facet];
searchOptions.parameters.add(new ParameterModel({id: id, values: value.split(',')}));
}
var criteria = $.extend(facets, sortOptions);
location.hash = UriUtil.getUriHash(criteria);
RequestUtil.requestSearchResults(searchOptions);
});
Here is the fetch:
requestSearchResults: function (searchOptions) {
//fetch the results
var performSearchModel = new PerformSearchModel();
var searchOptionsJson = JSON.stringify(searchOptions);
performSearchModel.fetch({
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({searchOptionsJson: searchOptionsJson}),
success: function (response) {
console.log("Inside success");
console.log(response);
},
error: function (errorResponse) {
console.log("Inside Failure")
console.log(errorResponse.responseText)
}
}) //have to wait for the fetch to complete
.complete(function () {
//show our regions
App.facetsRegion.show(new FacetListView({collection: performSearchModel.facets}));
App.resultsRegion.show(new ResultListView({collection: performSearchModel.results}));
//perform search fetch complete
});
}
and here is the model:
var SearchOptionsModel = Backbone.Model.extend({
defaults: {
parameters: ParameterCollection,
currentItemId: '{EE8AA76E-0A3E-437B-84D8-AD7FCBAF2928}',
sortBy: 0,
sortDirection: 'asc',
resultsPerPage: 10
},
initialize: function () {
this.parameters = new ParameterCollection();
//fetch calls an on change event.
this.on("change", this.fetchCollections);
},
url: function () {
return '/Services/Search/SearchService.asmx/SearchOptions';
},
parse: function (response) {
var data = JSON.parse(response.d);
return data;
},
fetchCollections: function () {
//when we call fetch for the model we want to fill its collections
this.parameters.set(
_(this.get("parameters")).map(function (parameter) {
return new ParameterModel(parameter);
})
);
}
});
UPDATE**
So I changed the way I create and add the parameters collection in the SearchOptionsModel and the JSON object is being formed correctly. I changed it from this:
var searchOptions = new SearchOptionsModel();
for(var facet in facets){
var id = facet;
var value = facets[facet];
searchOptions.parameters.add(new ParameterModel({id: id, values: value.split(',')}));
}
To this:
var parameters = new ParameterCollection();
//loop through all of the variables in this object
for(var facet in facets){
var id = facet;
var value = facets[facet];
parameters.add(new ParameterModel({id: id, values: value.split(',')}));
}
var searchOptions = new SearchOptionsModel({parameters: parameters});
Now the parameters are filled under the attributes in the model and I see an empty parameters variable on the searchOptions object (which was being filled before instead). Why is there a parameters variable set in the SearchOptionsModel if I am not explicitly creating it? Is it because the parameters default is set to a collection?
To convert a Backbone model to JSON, you must use the toJSON method:
model.toJSON();
Check doc here: http://backbonejs.org/#Model-toJSON
What I'm trying to do is when the page is loaded it will show the user a list of all their "contacts". There is a fair bit of code so I put it all HERE and below is just the load method.
$(window).load(function () {
var Contacts = StackMob.Model.extend({ schemaName: 'contacts' });
var myContacts = new Contacts();
var q = new StackMob.Collection.Query();
q.orderAsc('firstname'); //sort by firstname in ascending order
myContacts.query(q, {
success: function (model) {
console.log(model.toJSON());
for (var i = 0; i < model.length; i++) {
var data = ({
FirstName: model[i].attributes.firstname,
LastName: model[i].attributes.lastname,
Pno: model[i].attributes.phoneno,
Emails: model[i].attributes.email,
objIdel: model[i].contacts_id,
objIdeit: model[i].contacts_id
});
var template = Handlebars.compile($('#template').html());
var html = template(model);
$("#contacts").append(template(data));
}
},
error: function (model, response) {
console.debug(response);
}
});
});
console.log(model.toJSON()); shows what I would expect but It doesn't seem to be getting into the for loop at all.
EDIT: If i get rid of the loop and use the code below I get only one contact with no values in the inputs
var data = ({
FirstName: model.attributes.firstname,
LastName: model.attributes.lastname,
Pno: model.attributes.phoneno,
Emails: model.attributes.email,
objIdel: model.contacts_id,
objIdeit: model.contacts_id
});
EDIT: I was able to get the firstname of a contact using console.log(results.attributes[0]["firstname"]); the problem is I cant figure out why its not going into the loop.
I tested the code without the loop and it made a handlebars template of the first contact that worked as planed, but I cant figure out why it wont loop through them all.
Link to a more up to date version of the code
How about trying this ... I iterate over the jsonData to get each object. Not sure if handlebars expects a JSON object or the JSON string, so I output each to the console.log
var Contact = StackMob.Model.extend({ schemaName: 'todo' });
var Contacts = StackMob.Collection.extend({ model: Contact });
var q = new StackMob.Collection.Query();
q.orderAsc('name'); //sort by firstname in ascending order
var myContacts = new Contacts();
myContacts.query(q, {
success: function (data) {
jsonData = data.toJSON();
for (var i = 0; i < data.length; i++) {
var obj = jsonData[i];
console.log(obj);
console.log(JSON.stringify(obj));
}
},
error: function (model, response) {
console.debug(response);
}
});
I have a view with a property which i want to update its value when i make a fetch request.
define(['underscore','backbone','models/taskCollection'],
function( _,Backbone,TaskCollection) {
var UserTasksView = Backbone.View.extend({
el:"#user-task-list",
cur_task: undefined,
initialize: function() {
this.collection = new TaskCollection;
this.model = this.collection._model;
_.bindAll(this,'render');
this.collection.bind('reset',this.render);
},
view_task: function( event ) {
var el = $(event.currentTarget);
var task_id = el.attr('data-taskid');
var row = el.parents('td').parents('tr.task-row');
row.addClass("active");
el.hide();
el.next('a').show();
var task = this.collection.fetch({
data: {id:task_id},
silent:true,
success:this._task_fetch_success
});
this._show_task_detail();
event.preventDefault();
},
_task_fetch_success: function(response,status,xhr) {
this.cur_task = JSON.stringify(status);
return status;
},
/**
* Displays the details of a task
**/
_show_task_detail: function() {
var main = $('.app-content');
var detail_view = $('.app-extra');
var task_detail_view = $("#task-detail-view");
//Reduce task list view width
main.animate({
"width":"50%"
},2000);
//Display app extra bar
detail_view.show();
//show task detail view
detail_view.children('active-page').hide().removeClass('active-page').addClass('inactive-page');
task_detail_view.show().removeClass('inactive-page').addClass('active-page');
console.log(this.cur_task);
var template = ich.task_detail(this.cur_task)
$('div.task-details').html(template);
}
The ajax request trigger by the fetch is successful and success callback executes, but when i try to log "cur_task" property, it shows up as undefined;
What am i doing wrong
You have a couple problems that start right here:
var task = this.collection.fetch({
data: {id:task_id},
silent:true,
success:this._task_fetch_success
});
and here:
_task_fetch_success: function(response,status,xhr) {
this.cur_task = JSON.stringify(status);
return status;
}
First of all, the success callback is not a jQuery success callback and doesn't receive the usual jQuery parameters; from the fine manual:
The options hash takes success and error callbacks which will be passed (collection, response) as arguments.
so your _task_fetch_success function is called as f(collection, response) not as f(response, status, xhr) as you're expecting; this is why you have to treat the status parameter as JSON: the status is actually the response.
Your next problem is that this isn't what you think it is inside your _task_fetch_success function. Backbone's fetch just calls success as a plain old function:
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp, options); // <--------------- Right here
collection.trigger('sync', collection, resp, options);
};
That means that this will be window, not your view. The easiest way to work around this problem is to add _task_fetch_success to your _.bindAll list in initialize:
initialize: function() {
//...
_.bindAll(this, 'render', '_task_fetch_success');
//...
}