How to find which event has fired on the collection - javascript

I had a collection called approvals on which I had an event on sync and delete event types of collection to renderRows. check below code I need to reset the current collection based on the approval collection delete event.
this.approvals.on("sync delete", this.renderRows, this);
function renderRows(model, e, event ) {
//some code
if (event.type == "delete") {
this.collection.reset();
}
}
But I am getting the event as undefined. Can you please let me know how to get the event.type for collections

You also have this option:
this.listenTo(this.approvals, 'sync', _.partial(this.renderData, 'sync'));
this.listenTo(this.approvals, 'delete', _.partial(this.renderData, 'delete'));
and renderData (or however you want to call it) gets one extra param which you're passing with the _.partial (curry)
renderData: function(eventName, collection, resp, options) {}
this is the method signature: http://backbonejs.org/docs/backbone.html#section-133
collection.trigger('sync', collection, resp, options); delete looks the same
Looks like this as a basic example: (cant do delete but i can trigger change, just wait 5 seconds)
var Model1 = Backbone.Model.extend({
url: 'http://jsonplaceholder.typicode.com/posts/1'
});
var View1 = Backbone.View.extend({
template: _.template('<%= eventName %> - <%= body %>'),
initialize: function() {
// render something as soon as possible
this.render();
this.model = new Model1();
this.listenTo(this.model, 'sync', _.partial(this.renderData, 'sync'));
this.listenTo(this.model, 'change', _.partial(this.renderData, 'change'));
this.model.fetch();
// to test it
setTimeout(_.bind(function(){this.model.set('body', 'it was changed')}, this), 5000);
},
// this is the normal sync/change function signature only with one extra param `eventName`
// which is being `curry`'ed in
renderData: function(eventName, model, resp, options) {
this.$el.html(this.template({
'eventName': eventName,
'body': model.get('body')
}));
return this;
},
render: function() {
this.$el.html('nothing to see here');
return this;
}
});
new View1({el: $('body').append($('<div>'))});
Run it here: http://jsfiddle.net/tLaLykk8/

The event name isn't passed through unless it was specifically passed as an arg e.g. a trigger('sync', 'sync'). So you can either inspect the arguments (because they are different depending on the event in this case I think) - but that's a bad idea because they could change and it makes your code fragile. Your best bet is to simply split it out:
this.listenTo(this.approvals, "sync", this.onApprovalSync);
this.listenTo(this.approvals, "delete", this.onApprovalDelete);
onApprovalSync: function() {
this.renderRows();
}
onApprovalDelete: function() {
this.collection.reset();
this.renderRows();
}

From what I understood you want to have a common handler for sync and remove events and wants to identify which event triggered the handler.
Since the signature of the callbacks of these events are different:
remove: (model, collection, options)
and sync: (model_or_collection, resp, options)
We can achieve this by checking the type of arguments passed to the handler as shown below:
var View = Backbone.View.extend({
initialize: function() {
this.listenTo(this.collection, 'sync', this.dosomething);
this.listenTo(this.collection, 'remove', this.dosomething);
this.render();
},
events: {
'click #remove': 'triggerRemove',
'click #sync': 'triggerSync',
},
render: function() {
this.$el.html('<button id="remove">Trigger remove</button><button id="sync">trigger sync</button>').appendTo('body');
return this;
},
triggerRemove: function() {
var model = this.collection.add({
name: 'test'
});
this.collection.remove(model);
},
triggerSync: function() {
this.collection.trigger('sync');
},
dosomething: function(model) {
if (arguments[1] instanceof Backbone.Collection) //second argument is a collection
console.log('remove triggered')
else
console.log('sync triggered')
}
});
var view = new View({
collection: new Backbone.Collection([{
name: 'hi'
}, {
name: 'hellow'
}])
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.3/backbone-min.js"></script>

Related

Backbone.js multiple views, one collection, one fetch

I am trying to generate multiple views using one collection and one fetch every 5 seconds.
Below is a working example, but both views are refreshed when fetched.
I could splice the response into multiple urls, but i want to minimize the aumount of requests.
My current problem is that i dont want all views to re-render every 5 seconds when the collection is re-fetched, only the associated view that changed.
I have tried creating multiple models inside the collection and adding the correct object in the parse function without any luck.
Response:
{
"json-1": {
"sub_1": "3",
"sub_2": [],
},
"json-2": {
"sub_1": [],
"sub_2": "1",
},
}
// Client
const APICollection = Backbone.Collection.extend({
initialize: (models, options) => {
this.id = options.id;
},
url: () => {
return 'https://url.url/' + this.id;
},
model: APIModel,
parse: (resp) => {
return resp;
},
});
const ViewOne = Backbone.View.extend({
initialize: function () {
this.collection.bind('sync', this.render, this);
this.update();
_.bindAll(this, 'update');
},
render: function (n, collection) {
// Render view
},
update: function () {
let self = this;
this.collection.fetch({
update: true, remove: false, success: function () {
setTimeout(self.update, 5000);
}
});
}
});
// Also updates when re-fetched
const ViewTwo = Backbone.View.extend({
initialize: function () {
this.collection.bind('sync', this.render, this);
},
render: function (n, collection) {
// Render function
}
});
let col = APICollection([], {id: 'someid'});
new ViewOne({collection: col, el: $("#one")});
new ViewTwo({collection: col, el: $("#two")});
**Update
To clarify: "only the associated view that changed". By this i mean that 'ViewOne' should only be re-rendered when 'json-1' has changed, and 'ViewTwo' shouldn't re-render. currently the full response is sent to both views.
When dealing with an API which returns an Object, not an array of Objects, the best approach is to use a Backbone.Model directly.
update: function () {
let self = this;
this.model.fetch({
update: true, remove: false, success: function () {
setTimeout(self.update, 5000);
}
});
}
The model is still fetched the same way as the collection, but the Views can listen to specific attributes on the model, instead of:
this.collection.bind('sync', this.render, this);
The following can be used:
this.model.bind('change:json-1', this.render, this);
Tip: Better to listenTo rather than bind, it is safer (see docs)
this.listenTo(this.model, 'change:json-1', this.render);

Backbone collection change event fires, but nothing changes in the view

I am running the following view:
app.OrganisationTab = Backbone.View.extend({
el : "#organisations",
template : _.template( $("#tpl-groups-list").html() ),
events : {
"click .js-edit-group" : "editGroup"
},
initialize: function() {
this.listenTo(this.collection, 'change', this.change);
var that = this;
this.collection.fetch({
success: function() {
that.render();
}
})
},
change: function() {
//this.$el.empty();
console.log("collection has changed");
},
render:function() {
this.$el.empty();
this.addAll();
return this;
},
addAll: function() {
this.collection.each(this.addOne, this);
},
addOne: function(model) {
var view = new app.GroupEntry({
model: model
});
this.$el.append(view.render().el);
},
editGroup: function(e) {
e.preventDefault();
var elm = $(e.currentTarget),
that = this;
$('#myModal').on('hidden.bs.modal', function () {
$('.modal-body').remove();
});
var organisation = this.collection.findWhere({ id : String(elm.data('groupid')) });
var members = organisation.get('users');
organisation.set('members', new app.UserCollection(members));
var projects = organisation.get('projects');
organisation.set('projects', new ProjectCollection(projects));
var orgForm = new app.createOrganisationForm({
model : organisation,
});
$('#myModal').modal({
backdrop: 'static',
keyboard: false
});
}
});
This view triggers a new view, and in that I can change a model save it (sends a PUT) and I can get in my console, collection has changed. If I console.log this collection I can see that the collection has changed. If I try and re-render the page all I see are the models as they were without the edits.
Why would this be happening, when clearly the collection is getting changes as it fires the events and I can see it when I log the collection?
After reading your comment:
No sorry on collection change I try to run render() which should empty
the container, and add all the models...but it seems to render the old
collection again.
You're getting this problem because you are overriding the success handler for the fetch call. That success callback is triggered before the models are placed in the collection. You need to listen to the sync event if you want render after the collection has been synchronized with the server (models are updated after fetch).
Update initialize to:
initialize: function() {
this.listenTo(this.collection, 'change', this.change);
this.listenTo(this.collection, 'sync', this.render);
this.collection.fetch();
},

BackboneJS Uncaught Error: A "url" property or function must be specified

I am getting this error . I am able to preform read, and remove functions using BackboneJs , but i am having error when i execute the add method any help will be appreciated.
JSfiddel path is http://jsfiddle.net/2wjdcgky/
BackboneJS Uncaught Error: A "url" property or function must be specified
$(function() {
Model
var modelContact = Backbone.Model.extend({
defaults: function() {
return {
Id: 0,
Name: "",
Address: ""
};
},
idAttribute: "Id"
});
ModelCollection
var contactCollection = Backbone.Collection.extend({
model: modelContact,
url: function() {
return 'api/Contact';
},
add: function(model) {
this.sync("create", model); // Error On create
},
remove: function(model) {
this.sync("delete", model); //Runs Fine
}
});
var contacts = new contactCollection;
View
var contactView = Backbone.View.extend({
tagName: "tr",
events: {
"click a.destroy": "clear"
},
template: _.template($("#newContacttemplate").html()),
initialize: function() {
this.model.on("change", this.render, this);
this.model.on('destroy', this.remove, this);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
clear: function(e) {
contacts.remove(this.model); // runs fine
}
});
Main View
var main = Backbone.View.extend({
el: $("#contactApp"),
events: {
"click #btnsave": "CreateNewContact"
},
initialize: function() {
this.Nameinput = this.$("#contactname");
this.Addressinput = this.$("#contactaddress");
contacts.on("add", this.AddContact, this);
contacts.on("reset", this.AddContacts, this);
contacts.fetch();
},
AddContact: function (contact) {
console.log("AddContact");
var view = new contactView({ model: contact });
this.$("#tblcontact tbody").append(view.render().el);
},
AddContacts: function () {
console.log("AddContacts");
contacts.each(this.AddContact);
},
CreateNewContact: function (e) {
console.log(e);
//Generate an error "BackboneJS Uncaught Error: A "url" property or function must be specified"
contacts.add({ Name: this.Nameinput.val(), Address: this.Addressinput.val() });
}
});
var m = new main;
});
Your JSFiddle was missing Backbone references and all.
Working update: http://jsfiddle.net/apt7hchL/2/
Much simpler code (no need to define those add and remove methods on the collection!). Also more common Javascript coding style conventions.
Please note I had to manually generate an "Id" attribute to allow creating more than one contact. As you are making Id = 0 by default, second model with same is not added, as Backbone sees a model with id=0 is already in the collection.
When you want to save, call the model.save() method. Don't call sync manually, you'll normally don't need to!
For the model to be saved to the database before being added to the collection, use:
createNewContact: function (e) {
e.preventDefault();
var self = this;
var newContact = new ContactModel({
Name: this.$("#name").val(),
Address: this.$("#address").val()
});
newContact.save({ success: function(model){
self.collection.add(model);
});
//clear form
this.$("#name").val("");
this.$("#address").val("");
}
Sync method tries to sync to a server setup to handle it, with CRUD abilities. If thats not what you're looking for, and you just want to display this information on the client side, instead of using sync, you should use Collection.add(model) and Collection.remove(model)

Backbone models overwriting each other

I've got a collection of Delivery models called DeliveryList. When I add or edit a Delivery, all attributes of the previously added or edited Delivery are overwritten by the attributes of the new one.
Curiously, if I reload the page after saving a model with this line of code:
// Hacky way to get around the models overwriting each other
location.reload();
The model will not be overwritten by newly created or edited models.
Any thoughts on why this is happening?
Here's the rest of my code:
var DeliveryView = Marionette.ItemView.extend({
initialize: function () {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
_.bindAll(this, "editDeliveryOption", "saveAllFields");
},
onRender: function() {
if (this.model.isNew()) {
this.editDeliveryOption();
this.$el.addClass("new");
}
},
template: "#delivery-item-template",
events: {
"click #removeThis": "removeDeliveryOption",
"click #editThis": "editDeliveryOption"
},
saveAllFields: function() {
var value = $("#optionName input").val();
this.model.save({ optionName: value });
var value = $("#shipToState option:selected").val();
this.model.save({ shipToState: value });
var value = $("#zipCodes input").val();
this.model.save({ zipCodes: value });
var value = $("#perOrderFee input").val();
this.model.save({ perOrderFee: value });
var value = $("#perItemFee input").val();
this.model.save({ perItemFee: value });
// After done editing, remove the view from the dom
this.editDeliveryForm.remove();
// Show the new option
this.$el.removeClass("new");
// Hacky way to get around the models overwriting each other
location.reload();
},
editDeliveryOption: function () {
this.editDeliveryForm = new Backbone.Form({
template: _.template($("#editDeliveryTemplate").html()),
model: this.model
}).render();
layout.editDelivery.show(this.editDeliveryForm);
$("#triggerEditDelivery").fancybox({
'afterClose': this.saveAllFields,
}).click();
// This button in Fancybox isn't working
$("#saveDelivery").click(function() {
this.saveAllFields;
});
},
removeDeliveryOption: function () {
this.model.destroy();
}
});
var DeliveriesView = Marionette.CompositeView.extend({
initialize: function () {
this.collection.fetch();
this.listenTo(this.collection, 'change', this.changThis);
},
changeThis: function () {
alert("it changed");
},
template: "#deliveries-view-template",
itemView: DeliveryView,
events: {
"click #addShipping": "addDeliveryOption",
},
addDeliveryOption: function() {
this.collection.create();
},
// Specify a jQuery selector to put the itemView instances in to
itemViewContainer: "#deliveries",
});
Thanks EmptyArsenal and mu is too short for pointing me in the right direction.
What ended up being the problem was the fancybox call:
$("#triggerEditDelivery").fancybox({
'afterClose': this.saveAllFields,
}).click();
Every time I added a new field, it kept binding a saveAllFields method call to #triggerEditDelivery. Therefore, every time I clicked #triggerEditDelivery for a new Delivery, it would save all them to the currently open one.
Here's my fix:
$("#triggerEditDelivery").fancybox({
helpers: {
overlay: { closeClick: false }
}
}).click();
$("#saveDelivery").click(this.saveAllFields);
$("#cancelDelivery").click(this.cancelDeliveryOption);

How do I fill my view with my new backbone collection data after a reset?

I have a model called TimesheetRow and a collection called TimesheetRows.
If I make a call to TimesheetRows.reset(newCollection); where newCollection is a collection of four TimesheetRow models in JSON format, four new model views appear on my page.
How can I fill these input and select fields with the values from the new collection? I've noticed that if I change a field on one of the four models, the collection itself is properly updated, it seems like a one-way bind (does that make sense?).
Here is some code, I apologize for the quantity.
var TimesheetRow = Backbone.Model.extend({
defaults: function () {
return {
MondayHours: 0,
TuesdayHours: 0,
WednesdayHours: 0,
ThursdayHours: 0,
FridayHours: 0,
SaturdayHours: 0,
SundayHours: 0,
JobNo_: null,
PhaseCode: null,
TaskCode: null,
StepCode: null,
WorkTypeCode: null,
Description: "",
};
},
clear: function () {
this.destroy();
}
});
var TimesheetRowList = Backbone.Collection.extend({
model: TimesheetRow,
});
var TimesheetRows = new TimesheetRowList;
var TimesheetRowView = Backbone.View.extend({
template: _.template($('script.timesheetTemplate').html()),
events: {,
"change input" : "changed",
"change select" : "changed"
},
render: function () {
Backbone.Model.bind(this);
this.$el.html(this.template(this.model.toJSON())).hide().slideDown();
return this;
},
initialize: function () {
_.bindAll(this, "changed");
//this.model.bind('change', this.render, this);
//this.model.bind('reset', this.render, this);
this.model.bind('destroy', this.remove, this);
},
changed: function (evt) {
var changed = evt.currentTarget;
var value = this.$("#"+changed.id).val();
var obj = "{\""+changed.id.replace("Timesheet", "") +"\":\""+value+"\"}";
var objInst = JSON.parse(obj);
this.model.set(objInst);
},
});
var TimesheetRowsFullView = Backbone.View.extend({
el: $("#timesheet-rows-container-view"),
events: {
"click #add-new-timesheet-row" : "createRow",
"click #submit-new-timesheet" : "submitTimesheet",
"click #request-sample-timesheet" : "requestTimesheet"
},
initialize: function () {
TimesheetRows.bind('add', this.addOne, this);
TimesheetRows.bind('reset', this.addAll, this);
TimesheetRows.bind('all', this.render, this);
},
addOne: function (timesheetrow) {
var view = new TimesheetRowView({ model: timesheetrow });
this.$("#timesheet-start-placeholder").prepend(view.render().el);
},
addAll: function () {
TimesheetRows.each(this.addOne);
},
render: function () {
//Is this the code I'm missing?
},
createRow: function () {
TimesheetRows.create();
},
requestTimesheet: function () {
//newCollection is retrieved from database in code that I've excluded.
TimesheetRows.reset(newCollection);
}
});
var TimesheetRowsFullViewVar = new TimesheetRowsFullView();
In my changed function, I include Timesheet prefix because my IDs on those fields are all prefixed with Timesheet.
I know for a fact that my new JSON collection object is correctly formatted.
The two lines that are commented out in the TimesheetRowView initialize function were giving me trouble when I would update fields. I'm not sure if they are required or not.
Questions:
When I "reset", my previous model views are still present, how would I get rid of them? Should I use JQuery?
I expect at some point I need to add the Timesheet prefix back on, for the models to find the right IDs. At which step or in which function do I do this? (This ties in to the "one-way bind" I mentioned.)
I've been following along slightly with the TODO application, and I didn't find any of the code in the render function to be necessary for my side, in the FullView. Is this where I'm missing code?
collection.reset() only removes the models from your collection and put other collection in there if you pass them as a param.
To have your views removed from DOM you should do it yourself.
A way to do it is to listen to the reset event it triggers when all is done and create a method that removes your views from the DOM.
Or
Just remove then in that requestTimesheet method of yours.

Categories

Resources