Updating a view in backbone when a collection is updated - javascript

I have a web app that I am building, I have form input that allows you to enter a name, on entering this name, I want to update a list with that inputted name, my problem is however that if add one name and then another the previos name that is outputted to the view, is overwritten (but if I refresh the page I get the full list). Here is my code,
GroupModalHeaderView.prototype.render = function() {
this.$el.empty();
if (this.model.isNew()) {
this.$el.append(this.template({
m: this.model.toJSON()
}));
return this.edit();
} else {
this.$el.append(this.template({
m: this.model.toJSON()
}));
this.$el.find(".modal-header-menu").show();
return this.$el.find(".icon-button-close-modal").show();
}
};
GroupModalHeaderView.prototype.save = function(e) {
var $collection, $this;
if (e) {
e.preventDefault();
}
$this = this;
if (this.$("#group-name").val() !== "") {
$collection = this.collection;
if (this.model.isNew()) {
this.collection.push(this.model);
}
return this.model.save({
name: this.$("#group-name").val(),
async: false,
wait: true
}, {
success: function() {
return $this.cancel();
}
});
}
};
GroupListView.prototype.events = {
"click .list-header-add": "add",
"click .list-header-expander": "showHide",
"keyup #search-query": "keyup"
};
GroupListView.prototype.initialize = function() {
//console.log("fired");
this.collection.on("change", this.renderList, this);
this.collection.on("reset", this.render, this);
return this.renderList();
};
GroupListView.prototype.renderList = function(collection) {
var responsiveHeight = $("body").height() - 400;
if($("#people-network-requests").is(":visible")) {
this.$el.find("#people-groups-list").height($("#people-people-list").height()-250+"px");
} else {
this.$el.find("#people-groups-list").height($("#people-people-list").height()+"px");
}
var $collection, $this;
if (!collection) {
collection = this.collection;
}
this.$el.find(".list-items").empty();
$this = this.$el.find("#people-groups-list");
this.$el.find(".list-items").removeClass("list-items-loading").empty();
$collection = collection;
if ($collection.length < 1) {
/*this.$el.find("#people-groups-inner").hide();
$(".activity-no-show").remove();
return this.$el.find("#people-groups-inner").append('<div class="activity-no-show">\
<p>To add a new group, click the + in the top right hand corner to get started.</p>\
</div>');*/
} else {
this.collection.each(function(item) {
var displayView;
displayView = new app.GroupListDisplayView({
model: item,
collection: $collection
});
console.log($this);
return $this.append(displayView.render());
});
return this;
}
};
return GroupListView;
})(app.BaseView);
GroupListDisplayView.prototype.render = function() {
//console.log(this.$el);
//alert("1");
var $body;
this.$el.html(this.template({
m: this.model.toJSON()
}));
$body = this.$el.find(".card-body");
$text = $body.text();
$.each(this.model.get("people"), function(i, person) {
var personTile;
this.person = new app.Person({
id: person.id,
avatar: person.avatar,
first_name: person.first_name,
last_name: person.last_name
});
personTile = new app.PersonTileView({
model: this.person
});
if(person.id) {
$body.append(personTile.render()).find(".instruction").remove();
}
});
return this.$el.attr("id", "group-card-" + this.model.id);
};
GroupListView.prototype.keyup = function() {
this.filtered = $collection.searchName(this.$el.find("#search-query").val());
//console.log(this.filtered);
return this.renderList(this.filtered);
};

this.collection.on("add", this.addDisplayView, this);
Then create a function addDisplayView that accepts the model for the view. You will need to refactor the this.collection.each(function(item)... part of your code to use the addDisplayView function.
GroupListView.prototype.addDisplayView = function(model){
var displayView = new app.GroupListDisplayView({
model: model,
collection: this.collection
});
// use this.$, as it is already mapped to the context of the view
return this.$("#people-groups-list").append(displayView.render());
}
You should also change this.collection.push(this.model); to this.collection.add(this.model);
addcollection.add(models, [options])
Add a model (or an array of models) to the collection, firing an "add" event. If a model property
is defined, you may also pass raw attributes objects, and have them be
vivified as instances of the model. Pass {at: index} to splice the
model into the collection at the specified index. If you're adding
models to the collection that are already in the collection, they'll
be ignored, unless you pass {merge: true}, in which case their
attributes will be merged into the corresponding models, firing any
appropriate "change" events.
http://documentcloud.github.io/backbone/#Collection-add

Related

Filtering the collection doesn't work

I have a collection of models with boolean value interviewed and I want to filter them so that my view would display either models with this property set to true or models with this property set to false. In my collection I have the following methods:
var ResumeCollection = Backbone.Collection.extend({
filterActive: function () {
var active = this.where({interviewed: false});
return active;
},
filterInterviewed: function () {
var interviewed = this.where({interviewed: true});
return interviewed;
}
});
and in my view I have the following:
var ResumeList = Backbone.View.extend({
events: {
'click #active': 'showActive',
'click #interviewed': 'showInterviewed'
},
initialize: function () {
this.collection = new ResumeCollection();
},
render: function () {
var self = this;
this.$el.html( $('#filter') );
_.each(this.collection.toArray(), function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
},
showActive: function () {
this.collection.filterActive();
this.render();
},
showInterviewed: function () {
this.collection.filterInterviewed();
this.render();
}
});
But any time I click #active or #interviewed buttons, it happens nothing and the models with required properties aren't rendered. I've already tried to manage that with reset method or returning a new collection instance with required models, but that's not a solution, because when I succesfully filter the initial collection, it returns me a new collection with the models I need (e.g. models where interviewed: true), and I can't filter it more -- it returns just an empty collection.
I just can't get how can I filter this one collection in the way I need.
You're returning a successfully filtered collection, and then not doing anything with them.
showActive: function () {
this.collection.filterActive();//returns a value you're not using
this.render();
},
showInterviewed: function () {
this.collection.filterInterviewed();//returns a value you're not using
this.render();
}
I suggest adding an optional parameter to your render method that represents the filtered collection. If the parameter is defined, use it. If not, use the unfiltered collection.
Borrowing some of #Simo's code to return a new collection.
filterActive: function () {
var active = this.where({interviewed: false});
return new ResumeCollection(active);
},
filterInterviewed: function () {
var interviewed = this.where({interviewed: true});
return new ResumeCollection(interviewed);
},
render: function (filtered) {
var self = this;
var data = filtered ? filtered.toArray() : this.collection.toArray();
this.$el.html( $('#filter') );
_.each(data , function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
},
showActive: function () {
var filtered = this.collection.filterActive();
this.render(filtered);
},
showInterviewed: function () {
var filtered = this.collection.filterInterviewed();
this.render(filtered);
}
Your issue is that you are not returning the filtered collection.
This should work:
filterActive: function () {
var active = this.filter(function(item) {
return item.get('interviewed') === false;
});
return new ResumeCollection(active);
},
filterInterviewed: function () {
var interviewed = this.filter(function(item) {
return item.get('interviewed') === true;
});
return new ResumeCollection(interviewed);
},
i would suggest you to modify you render function to accept a argument which will be array of models.
Now when rendering full collection you can call render as
render(this.collection.models) // reference to list of models
also if you filter out the collection then the filter function most probably be returning the subset of models from collection. Which you can again pass to render function
this.render(this.showActive()) // showActive returns subset of models from collection
This way your render function becomes modular.. which accepts array and render then on page..
Now for Filtering out Collection you can use filter , where methods exposed by underscore .. Remember to capture the return and pass it along to render Function..

Implement search effectively in Backbone.js

I am trying to perform a search on my current collection and if the results aren't retrieved i am trying to query my search api
Collection:
var Backbone = require('backbone'),
_ = require('underscore'),
Urls = require('../../libs/urls'),
services = require('../../libs/services'),
skuListModel = require('../../models/sku/SkuListModel');
var SkuListCollection= Backbone.Collection.extend({
model: skuListModel,
sync: function (method, model, options) {
options = _.defaults({}, options, {
readUrl: Urls.sku.list
});
return services.sync.call(model, method, model, options);
}
});
View
searchData: function (e) {
var self = this;
var models = this.skuCollection.filter(function (item) {
return item.get("sku_code").indexOf(e.target.value) > -1
});
console.log(models);
if (models != null) {
self.skuCollection.set(models);
}
else {
self.skuCollection.fetch({
data: {
search_string: e.target.value
}
}).then(function (response) {
console.log(response);
//self.skuCollection.add(self.skuSearchCollection.toJSON(), { silent: true });
});
}
}
My question effectively is how do i modify my current collection to store the retrieved results and if my solution seems effective.
Move your filtering logic to the collection
Use promises to unify your response : an immediately resolved deferred if you find models, the xhr object if you have to fetch the data
Customize the behavior of fetch via the set options, e.g {remove: false} to keep the existing models
These points lead to a collection definition :
var SkuListCollection = Backbone.Collection.extend({
skus: function(code) {
var self = this;
var filtered = function() {
return self.filter(function (item) {
return item.get("sku_code").indexOf(code) !== -1;
});
};
var models = filtered();
if (models.length) {
// models found : define a promise and resolve it
var dfd = $.Deferred();
dfd.resolve(models);
return dfd.promise();
} else {
// models missing: fetch and add them
return this.fetch({
remove: false,
data: {
search_string: code
}
}).then(filtered);
}
}
});
Your view would then be rewired as :
searchData: function (e) {
this.skuCollection.skus(e.target.value).then(function(models) {
// do what you have to do with the filtered models
});
}
And a demo http://jsfiddle.net/nikoshr/84342xer/1/

Backbone model when created already has attributes

In my my application I do something like this to create a new model,
this.model = new App.Models.Organisation;
The code for the model looks like this,
'use strict'
App.Models.Organisation = Backbone.Model.extend({
urlRoot: "http://" + App.API_ROOT + "/organisations",
defaults: {
//members : new App.Collections.Users
},
initialize: function() {
//Gets
var members = this.get('users');
var projects = this.get('projects');
var teams = this.get('teams');
var clients = this.get('clients');
console.log(members);
console.log(projects);
console.log(teams);
console.log(clients);
//Sets
if(members != undefined) {
this.set('members', App App.Collections.Users(members));
} else {
this.set('members', App App.Collections.Users);
}
if(projects != undefined) {
this.set('projects', new App.Collections.Projects(projects));
} else {
this.set('projects', new App.Collections.Projects);
}
if(teams != undefined) {
this.set('teams', new App.Collections.Teams(teams));
} else {
this.set('teams', new App.Collections.Teams);
}
if(clients != undefined) {
this.set('clients', new App.Collections.Clients(clients));
} else {
this.set('clients', new App.Collections.Clients);
}
},
validate: function() {
}
});
However when log the new model where I expect to see empty attributes I get the following:
Why would teams and projects have a value when the model is newly created?
The teams collections looks like this,
'use strict'
App.Collections.Teams = Backbone.Collection.extend({
url: 'http://' + Pops.API_ROOT + '/teams',
model: Pops.Models.Team,
initialize: function() {
var members = this.get('members');
this.set('members', new App.Collections.Users(members));
},
search: function(filterValue) {
var matcher = new RegExp(filterValue);
var found_models = this.filter(function(model) {
return matcher.test(model.get('name'));
});
return found_models;
},
});
and the projects collection like this,
App.Collections.Projects = Backbone.Collection.extend({
url: 'http://' + App.API_ROOT + '/project',
model: App.Models.Project,
sort_key: "name",
sort_order: 1,
parent_filter: false,
filters: [1,2,3],
initialize:function() {
var pm = this.get('projectmanager');
this.set('project_manager', new App.Models.User(pm));
var sp = this.get('salesperson');
this.set('sales_person', new App.Models.User(sp));
this.sortByField('created_at', 'desc');
},
comparator: function (item1, item2) {
var val1 = item1.get(this.sort_key);
var val2 = item2.get(this.sort_key);
if (typeof (val1) === "string") {
val1 = val1.toLowerCase();
val2 = val2.toString().toLowerCase();
}
var sortValue = val1 > val2 ? 1 : -1;
return sortValue * this.sort_order;
},
sortByField: function(fieldName, orderType) {
this.sort_key = fieldName;
this.sort_order = orderType == "desc" ? -1 : 1;
console.log(this.sort_order);
this.sort();
},
sortStatus: function( filters ) {
this.filters = filters;
this.each(function(project){
project.set('visible', _.contains(filters, parseInt(project.get('status'))));
});
},
myProjects: function() {
this.each(function(project){
if(project.get('user_id') == '1' && project.get('organisation_id') == null) {
project.set('visible', true);
} else {
project.set('visible', false);
}
}, this);
},
status: function( status ) {
if(this.parent_filter == false) {
//Filter all projects on the dashboard
this.each(function(project){
project.get('visible', true);
project.set('visible', project.get('status') == String(status) );
});
} else {
//Filter only projects that are currently visible
this.each(function(project) {
if(project.get('visible')) {
project.set('visible', project.get('status') == String(status) );
}
});
}
},
otherProjects: function() {
this.each(function(project){
if(project.get('organisation_id') != null) {
project.set('visible', true);
} else {
project.set('visible', false);
}
}, this);
},
sortBy: function(filterBy, orderBy) {
this.sortByField(filterBy, orderBy);
this.sort();
},
search: function(filterValue) {
var matcher = new RegExp(filterValue);
var found_models = this.filter(function(model) {
return matcher.test(model.get('name'));
});
return found_models;
},
});
I see what's going on now, in your teams collection initialize method you have this line:
this.set('members', new App.Collections.Users(members));`
So this is calling set on a collection which is different from calling set on an individual model.
On a collection set treats the first element as an array of models. You are passing 'members' as the first parameter and this adding a model to the collection with every character in the string as one attribute of that model
On a model, set expects either an attributes hash to be passed or 2 parameters attribute name and value to be passed, and will set the model attributes accordingly.
Basically you cannot treat the collection as an individual model.
If you want to keep a reference to the members from the teams collection, why not keeping a reference like this.members = new App.Collections.Users(members) that you can access from other places in the teams collection?

backbone - cannot loop through a collection

I am having trouble looping through a collection that helps populate a view with data. When I try to loop through the collection and get the following error,
Uncaught TypeError: Object # has no method 'each'
I have absolutley no idea why I am getting this error, other than the object obviosly doesn't have that method, however I only get this error when I run the drop function see code below.
Here is by backbone code,
GroupListView.prototype.keyup = function() {
this.filtered = this.collection.searchName(this.$el.find("#search-query").val());
return this.renderList(this.filtered);
};
GroupListView.prototype.renderList = function(collection) {
var $collection, $this;
console.log(this.$el);
console.log(collection);
if (!collection) {
collection = this.collection;
console.log(collection);
}
this.$el.find(".list-items").empty();
$this = this.$el.find("#people-groups-list");
$collection = collection;
if ($collection.length < 1) {
this.$el.find("#people-groups-list").hide();
return this.$el.find("#people-groups-list").after('<div class="activity-no-show">\
<p>To add a new group, click the + in the top right hand corner to get started.</p>\
</div>');
} else {
this.$el.find("#people-groups-list").show();
collection.each(function(item) {
//console.log(item);
var displayView;
displayView = new app.GroupListDisplayView({
model: item,
collection: $collection
});
return $this.append(displayView.render());
});
return this;
}
};
GroupListDisplayView.prototype.render = function() {
var $body;
//console.log(this.model.toJSON());
this.$el.html(this.template({
m: this.model.toJSON()
}));
$body = this.$el.find(".card-body");
$.each(this.model.get("people"), function(i, person) {
var personTile;
this.person = new app.Person({
id: person.id,
avatar: person.avatar,
first_name: person.first_name,
last_name: person.last_name
});
console.log(this.person);
personTile = new app.PersonTileView({
model: this.person
});
return $body.html(personTile.render());
});
return this.$el.attr("id", "group-card-" + this.model.id);
};
GroupListDisplayView.prototype.drop = function(e) {
var $collection, $model, person_id, request;
e.preventDefault();
$collection = this.collection;
person_id = e.originalEvent.dataTransfer.getData('Text');
request = new app.PersonGroupAdd;
$model = this.model;
return request.save({
async: true,
wait: true,
person_id: person_id,
group_id: this.model.get("id")
}, {
success: function(d) {
return $model.fetch({
async: true,
wait: true
});
}
});
};
GroupListView.prototype.initialize = function() {
this.collection.on("change", this.renderList, this);
this.collection.on("reset", this.render, this);
return this.renderList();
};
Try this instead, place this function with in your collection
parse: function (data) {
data.forEach(function (item) {
displayView = new app.GroupListDisplayView({
model: item,
collection: $collection
});
});
}
Hope this helps.
I'm not sure what the drop function has to do with it, but I see renderList being passed the results of searchName during keyup. What's likely happening is that searchName is returning a regular array, instead of a wrapped object that would have the each method (i.e. a Backbone.Collection, jQuery collection or Underscore wrapped object).
Instead of calling collection.each, use jQuery or Underscore:
$.each(collection, function(i, item) { ... });
or
_.each(collection, function(item, i, list) { ... });

Why are my Backbone Models nested strangely within a Collection, requiring drilling down to access methods/properties?

I've got a Collection and a Model, both using attributes/options to augment them with additional capabilities. Here's the Model (LoadRouteGroup):
return Backbone.Model.extend({
initialize: function () {
console.log(this);
},
fetchf: function () {
console.log("FETCH");
}
});
And the Collection (LoadRouteGroups):
return Backbone.Collection.extend({
constructUrl: function(options) {
if (options.groupingType === "facility") {
// TODO: new endpoint: /api/v1/loadroutes?grouping=facility
this.url = clawConfig.endpoints.webApiRootUrl + "/api/loads/facilities";
}
else {
this.url = clawConfig.endpoints.webApiRootUrl + "/api/v1/loadroutes";
}
},
initialize: function (models, options) {
options || (options = {});
this.constructUrl(options);
console.log(this);
}
});
They're instantiated as such:
var loadRouteGroup = new LoadRouteGroup({
entityType: "facility"
});
// WORKS
loadRouteGroup.fetchf();
// assign groupingType option to collection to denote which URL to use
var loadRouteGroups = new LoadRouteGroups({
model: loadRouteGroup
}, {
groupingType: "facility"
});
var firstGroup = loadRouteGroups.at(0);
// DOESN'T WORK
firstGroup.fetchf();
// WORKS
firstGroup.attributes.model.fetchf();
I would expect that call to firstGroup.fetchf() to work... but it doesn't. Instead, I have to weirdly drill down and use firstGroup.attributes.model.fetchf() in order to access the method.
What's going on here? This would seem straightforward to me, but I can't for the life of me figure out what's wrong with the relationship between my Collection and Model.
The collection definition should include the model type:
return Backbone.Collection.extend({
// ....
model: LoadRouteGroup
});
When initializing the collection, pass in an array of models:
var loadRouteGroup = new LoadRouteGroup({
entityType: "facility"
});
var loadRouteGroups = new LoadRouteGroups([loadRouteGroup], {
groupingType: "facility"
});
Specify the model when you extend the collection instead of when you instantiate.

Categories

Resources