I have a collection of objects which will be updated in bulk (not necessarily all at the same time, but more than one).
Therefore I need to send updates to the server in bulk (i.e. not one request for each updated object), and I need to do partial updates (i.e. only update objects which have changed).
Problems I have come across:
There is no save() method on a Collection (strange considering it has a fetch() method)
There is no built in tracking of models that have changed in a Collection since the last sync with the server.
Is there a clean, elegant way to achieve this?
I have researched and tried a few things, but all solutions are rather kludgy and over-complicated - which is unacceptable for a framework which is meant to simplify these sort of things.
Surely there is a decent way to do such a common thing in Backbone. Or should I use another framework e.g. Angular JS?
You can just build an array of the JSON representation of the models. Something like this.
Backbone.Collection.prototype.save = function()
{
var data = [];
for (var n = 0; n < this.length; ++n)
{
data.push(this.models[n].toJSON());
}
// save data through jQuery
console.log(data);
};
var col = new Backbone.Collection();
col.add({status: true});
col.add({status: false});
col.save();
You can add some model.hasChanged() check to prevent unnecessary savings.
Just to help anyone who finds this question, this is what I ended up using (based on #joconja's answer):
Backbone.Collection.prototype.saveChanges = function() {
var data = [];
for (var i = 0; i < this.length; ++i) {
if (this.models[i].hasChanged()) {
var changes = this.models[i].changedAttributes();
changes.id = this.models[i].get('id');
data.push(changes);
this.models[i].changed = {};
}
}
Backbone.ajax({
url: this.url,
method: 'PUT',
contentType: 'application/json',
data: JSON.stringify(data)
});
};
Related
Currently, I have a table named Appointments- on appointments, I have a Relation of Clients.
In searching the parse documentation, I haven't found a ton of help on how to eagerly fetch all of the child collection of Clients when retrieving the Appointments. I have attempted a standard query, which looked like this:
var Appointment = Parse.Object.extend("Appointment");
var query = new Parse.Query(Appointment);
query.equalTo("User",Parse.User.current());
query.include('Rate'); // a pointer object
query.find().then(function(appointments){
let appointmentItems =[];
for(var i=0; i < appointments.length;i++){
var appt = appointments[i];
var clientRelation = appt.relation('Client');
clientRelation.query().find().then(function(clients){
appointmentItems.push(
{
objectId: appt.id,
startDate : appt.get("Start"),
endDate: appt.get("End"),
clients: clients, //should be a Parse object collection
rate : appt.get("Rate"),
type: appt.get("Type"),
notes : appt.get("Notes"),
scheduledDate: appt.get("ScheduledDate"),
confirmed:appt.get("Confirmed"),
parseAppointment:appt
}
);//add to appointmentitems
}); //query.find
}
});
This does not return a correct Clients collection-
I then switched over to attempt to do this in cloud code- as I was assuming the issue was on my side for whatever reason, I thought I'd create a function that did the same thing, only on their server to reduce the amount of network calls.
Here is what that function was defined as:
Parse.Cloud.define("GetAllAppointmentsWithClients",function(request,response){
var Appointment = Parse.Object.extend("Appointment");
var query = new Parse.Query(Appointment);
query.equalTo("User", request.user);
query.include('Rate');
query.find().then(function(appointments){
//for each appointment, get all client items
var apptItems = appointments.map(function(appointment){
var ClientRelation = appointment.get("Clients");
console.log(ClientRelation);
return {
objectId: appointment.id,
startDate : appointment.get("Start"),
endDate: appointment.get("End"),
clients: ClientRelation.query().find(),
rate : appointment.get("Rate"),
type: appointment.get("Type"),
notes : appointment.get("Notes"),
scheduledDate: appointment.get("ScheduledDate"),
confirmed:appointment.get("Confirmed"),
parseAppointment:appointment
};
});
console.log('apptItems Count is ' + apptItems.length);
response.success(apptItems);
})
});
and the resulting "Clients" returned look nothing like the actual object class:
clients: {_rejected: false, _rejectedCallbacks: [], _resolved: false, _resolvedCallbacks: []}
When I browse the data, I see the related objects just fine. The fact that Parse cannot eagerly fetch relational queries within the same call seems a bit odd coming from other data providers, but at this point I'd take the overhead of additional calls if the data was retrieved properly.
Any help would be beneficial, thank you.
Well, in your Cloud code example - ClientRelation.query().find() will return a Parse.Promise. So the output clients: {_rejected: false, _rejectedCallbacks: [], _resolved: false, _resolvedCallbacks: []} makes sense - that's what a promise looks like in console. The ClientRelation.query().find() will be an async call so your response.success(apptItems) is going to be happen before you're done anyway.
Your first example as far as I can see looks good though. What do you see as your clients response if you just output it like the following? Are you sure you're getting an array of Parse.Objects? Are you getting an empty []? (Meaning, do the objects with client relations you're querying actually have clients added?)
clientRelation.query().find().then(function(clients){
console.log(clients); // Check what you're actually getting here.
});
Also, one more helpful thing. Are you going to have more than 100 clients in any given appointment object? Parse.Relation is really meant for very large related collection of other objects. If you know that your appointments aren't going to have more than 100 (rule of thumb) related objects - a much easier way of doing this is to store your client objects in an Array within your Appointment objects.
With a Parse.Relation, you can't get around having to make that second query to get that related collection (client or cloud). But with a datatype Array you could do the following.
var query = new Parse.Query(Appointment);
query.equalTo("User", request.user);
query.include('Rate');
query.include('Clients'); // Assumes Client column is now an Array of Client Parse.Objects
query.find().then(function(appointments){
// You'll find Client Parse.Objects already nested and provided for you in the appointments.
console.log(appointments[0].get('Clients'));
});
I ended up solving this using "Promises in Series"
the final code looked something like this:
var Appointment = Parse.Object.extend("Appointment");
var query = new Parse.Query(Appointment);
query.equalTo("User",Parse.User.current());
query.include('Rate');
var appointmentItems = [];
query.find().then(function(appointments){
var promise = Parse.Promise.as();
_.each(appointments,function(appointment){
promise = promise.then(function(){
var clientRelation = appointment.relation('Clients');
return clientRelation.query().find().then(function(clients){
appointmentItems.push(
{
//...object details
}
);
})
});
});
return promise;
}).then(function(result){
// return/use appointmentItems with the sub-collection of clients that were fetched within the subquery.
});
You can apparently do this in parallel, but that was really not needed for me, as the query I'm using seems to return instantaniously. I got rid of the cloud code- as it didnt seem to provide any performance boost. I will say, the fact that you cannot debug cloud code seems truly limiting and I wasted a bit of time waiting for console.log statements to show themselves on the log of the cloud code panel- overall the Parse.Promise object was the key to getting this to work properly.
I have created a very basic backbone application, but I need to perform a slightly complex check which I am having trouble doing.
My code is below. What I'm doing here is creating a list of participants in a chat. In reality, I will be sending this list in through a javascript function.
Participant = Backbone.Model.extend({});
Participants = Backbone.Collection.extend({
model: Participant
});
ParticipantView = Backbone.Marionette.ItemView.extend({
template: "#participant-template",
tagName: 'div',
className: 'call-participant',
initialize: function () {
this.$el.prop("id", this.model.get("chatid") + "-" + this.model.get("participantName"));
},
});
ParticipantsView = Backbone.Marionette.CompositeView.extend({
template: "#participants-template",
tagName: 'div',
itemView: ParticipantView,
appendHtml: function(collectionView, itemView) {
collectionView.$el.append(itemView.el);
}
});
MyApp.addInitializer(function(options)) {
var participantsView = new ParticipantsView({
collection: options.participantNames
});
MyApp.participantContainer.show(participantsView);
var participantsModel = new Participants();
};
$(document).ready(function() {
MyApp.start({participantsModel: participantsModel});
})
The trouble I am having is that when people leave/join the chat, the message is being resent with a new participant list (e.g. some participantName values won't be there).
So to the question: In backbone.marionette, how and where do I tell it to compare the existing models to the new list of models (for a give chatid), and if the model is no longer in the list, remove the model, and if there's something in the list that isn't a model, add it.
You will see I am building my ID from the the chatid AND the participantName (note that the name is unique, it is actually the JID I'm sending without the #server). The reason the ID is formatted like this is that I will have around 5-10 lists on one page, so I don't want to start updating the wrong chats.
Thank you. Please ask if you require additional information.
jsFiddle: http://jsfiddle.net/966pG/175/
Kind Regards,
Gary Shergill
EDIT: I am aware of get and set, but in all honesty have no idea how to use them. I've tried reading about them "http://backbonejs.org/#Model-get".
EDIT: Live scenario below. I have a javascript function which listens for pubsub events, and when it receives the right one it creates an array of participant objects:
var participants = [];
$(iq).find('participants').each(function() {
var participantsNodes = this.childNodes;
for (var i = 0; i < participantsNodes.length; i++) {
var participantAttr = participantsNodes[i].attributes
var participant = participantAttr[0].nodeValue;
participants.push({"participantName": participant, "chatid": chatid});
}
});
var chatid = $(iq).find('chatid').text();
...
participantsModel.add(new Participants({
chatid : chatid,
participantArray : participants
}))
Based on conversation in the comments
Backbone's Collection.set function takes care of this for you. You can add a new set of participants and it will recognize which ones are new, which ones are old and will fire add and remove events appropriately.
You need to add an array of Participants though, you can't just add an array of names. You can handle that with map pretty easily though.
//array of names from sonewhere
arr = getNamesFromServer()
arrOfParticipants = arr.map(function(name) {
id = calculateChatID(name,/*whatever else you need */)
return new Participant(name:name,chatID: id);
}
participantNames.set(arrOfParticipants)
Exactly how you keep track of your participants and where you pull in updates is up to you.
Of course if you can get the information formatted into the correct format on the server, (an array of json objects matching Participant), you should just use Backbone's built in sync functions, which will handle all of this for you. Then, once you've set up a RESTful URL that matches your client side naming scheme, or overriden the url property on your model/collection, you can just call participantNames.fetch().
I am creating a commercial API for the first time for responsive webpages/web applications (mobile devices).
I am new and, sadly, working alone as well as new to Javascript (long complicated story).
I was just wondering if someone from the industry could offer their professional opinion on the following format of a "get" call:
var getSample = function(params) {
//Returns Object
return $.ajax({
url: URL + 'downloadQuadrat.php',
type: 'GET',
data: { 'projectID': params.pid, 'quadratID': params.qid },
dataType: dataType
});
}
Function call:
var printList = function(lid,options,get) {
var list = $("ul#"+lid);
var promise = get(options);
promise.promise().then(
function(response) {
var items = response;
list.empty();
$.each(items, function(item,details) {
var ul = $('<ul/>');
ul.attr('id', lid+'_'+details.ID);
var li = $('<li/>')
.text(details.ID)
.appendTo(list);
ul.appendTo(list);
$.each(details,function(key,value) {
var li = $('<li/>')
.text(key+': '+value)
.appendTo(ul);
});
});
}
);
}
Any input or guidance will be hugely appreciated.
I'm not a professional from the industry, per se, but there's a few things that I think would improve your code:
Format it according to common conventions. It's hard to see what your code is doing without proper indentation.
Just use $("#"+lid) instead of $("ul#"+lid). The ul at the beginning does not add any disambiguation because id attributes must be unique, and it just make it take longer to parse.
Ditch localstorage in this case. It's not supported on all browsers, and as far as I can tell, you don't need it here. Just directly use the data returned from the response.
Here is how I would change your code:
var printList = function(lid, options, get) {
var promise = get(options);
var list = $("#" + lid);
promise.success(function(response) {
var data = response;
list.empty();
$.each(data, function(item, details) {
var ul = $('<ul/>').attr('id', lid + '_' + details.ID);
var li = $('<li/>').text(details.ID).appendTo(list);
ul.appendTo(list);
$.each(details, function(key, value) {
var li = $('<li/>').text(key + ': ' + value).appendTo(ul);
});
});
});
}
EDIT: The edited version of your code looks fine to me, except for the minor ul# thing.
Some more suggestions to make your API a tad more professional looking:
1 - Namespacing
Use a namespace to keep your code packaged neatly in it's own space where it won't conflict with other function definitions on the page. Something like this to start with:
window.MyNamespace = {};
MyNamespace.get = function(qid, pid) {
//things
};
MyNamespace.anotherFunction = function() {
//other stuff
}
If your code starts getting bigger you can wrap the whole lot in a closure. Also you could define it all as class and then instantiate it once to make your code neater and allow you to store instance variables and call this.anotherFunction(). I can provide examples of those too if you like.
2 - API method signatures
Another thing I prefer to see is explicit arguments to functions rather than function get(params) style code. Making parameters explicit makes your code easier to read and understand and discourages ad-hoc hacks which is especially important when writing an API. It's a case of just because you can doesn't mean you should.
3 - Config
Try to move things like IDs and URLs into variables to start with to make your code a bit easier to reuse and work with.
Generally, Javascript developers are famous for looking at your code before they look at your API docs so anything you can do to make the API function names and argument names more expressive and self-documenting will help them.
I've been able to hack around the ember-data RESTAdapter and make it work for my django web app using the djangorestframework. It's clearly a different flavor of REST from what Rails has implemented.
I'm extending the original DS.RESTAdapter to work with the django approach and I'm curious how I can take the "record" that they typically turn into JSON and instead make a basic query dict of "foo=bar&baz="
Here is what i've done so far that does work -I'd just like a less hard coded approach
DS.RESTAdapter = DS.Adapter.extend({
bulkCommit: false,
createRecord: function(store, type, record) {
var root = this.rootForType(type);
//var data = {};
//data[root] = record.toJSON();
var data = 'username=%#'.fmt(record.get('username'))
...
If you define your models like so:
MyApp.MyModel = DS.Model.extend({
property: DS.attr('string'),
....
});
MyApp.MyModel.reopenClass({
url: '/path?ids=%#
});
Then in your adapter:
find: function(store, type, id) {
var url = type.url;
url = url.fmt(id);
....
})
I have a collection which holds some of the users. Some information that is needed is how many total there are, how many pages, etc. How do I pass these back to the client? Or do they have to come from a separate view in which case I will need more than one ajax call? I'd like to have the collection fetch() and also receive some of this "meta data". What's a good way for handling this?
Generally, you need to handle this in the collection class' parse method. Its responsibility is to take the response and return back an array of model attributes. However, you could do more than that if you wished if you didn't mind the parse method having this additional responsibility.
UserList = Backbone.Collection.extend({
model: User,
url: '/users',
parse: function(data) {
if (!data) {
this.registered_users = 0;
return [];
}
this.registered_users = data.registered_users;
var users = _(data.users).map(
function(user_data) {
var user = {};
user['name'] = user_data.name;
return user;
}
);
return users;
}
});
So in the trivial example above, presume the server returns a response which contains a count of registered users and and an array of user attributes. You would both parse through and return the user attributes and you would pick off the registered user count and just set it as a variable on the model.
The parse method would get called as part of a fetch. So no need to modify the fetch, just use the built-in hook method that you have.
Purists would say that you are giving the parse method a secondary responsibility which might surprise some people (e.g. returning something and modifying model state). However, I think this is okay.
One way to do this is to override the Collection::fetch() method so that it parses this metadata out of the response. You could have your backend return a response like this:
{
"collection": [
{ ... model 1 ... },
{ ... model 2 ... },
...
],
"total_rows": 98765,
"pages": 43
}
In your fetch method which overrides the original Backbone.Collection::fetch() method, you can handle each property of the object separately. Here's you could do the override with a slightly modified fetch method:
_.extend(Backbone.Collection.prototype, {
fetch : function(options) {
options || (options = {});
var collection = this;
var success = options.success;
options.success = function(resp) {
// Capture metadata
if (resp.total_rows) collection.total_rows = resp.total_rows;
if (resp.pages) collection.pages = resp.pages;
// Capture actual model data
collection[options.add ? 'add' : 'refresh'](
collection.parse(resp.collection), options);
// Call success callback if necessary
if (success) success(collection, resp);
};
options.error = wrapError(options.error, collection, options);
(this.sync || Backbone.sync).call(this, 'read', this, options);
return this;
});
Note that this approach using _.extend will affect all your classes which extend Backbone.Collection.
This way, you don't have to make 2 separate calls to the backend.
I would bootstrap the information at pagecreation. Write the information into the html document when the server creates the site. Like that you don't have to have an ajax call at all. I do that with the whole collection in ordner not to first load the page and then wait for the ajax call to return the information needed.
Code example with Python:
Line 64: https://github.com/ichbinadrian/maps/blob/master/python/main.py <- from here
Line 43: https://github.com/ichbinadrian/maps/blob/master/templates/index.html <- into here