Backbone collection.reset adds one incorrect model - javascript

I have a collection which is filtered through a search box. When a search/filter is performed, I get a result which is a collection. This collection is correct and contains the expected models from the search. When I use collection.reset(result) it adds a backbone collection which contains one model which has nothing to do with the result collection, nor does it contain anything but the standard collection backbone stuff.
The Collection
var Products = Backbone.Collection.extend({
model: Product,
url : '',
search : function(letters){
if(letters == "") return this;
var pattern = new RegExp(letters,"gi");
return new Products((this.filter(function(data) {
return pattern.test(data.get("title"));
})));
}
});
From the view:
search : function (ev){
var results = products.search($("#search").val());
console.log("result");
console.log(results);
this.collection.reset(results);
console.log("altered collection");
console.log(this.collection);
}
And an image showing what it contains at the different points of logging:
Again the filtered collection (after result) is 100% correct and what I expect it to be. The state of this.collection prior reset, is also correct. The only thing I do is this.collection.reset(result); and log this.collection again. I listen to the 'reset' event, and when it fires there is obviously no models to render and I get an error.

it looks like results is a Products collection. collection.reset() expects an array of models, not a collection.
Try this.collection.reset(results.models);

Related

How to match an integer from one collection with it's identical in another collection, creating a many to many relationship.

This is supposed to be a simple many to many relationships in meteor but I must be missing something because i cannot get it to work.
I have a Collection called reblog and in it is an array of integers called descovered see image
I have a second collection called posts which is a collection of posts, and these posts have an id. take a look at the second image
I want to create a many to many relationships between the posts and the reblog collection. i.e, I want to match the integer
descovered: 9
from the reblog collection, with:
id: 9
from the posts collection so that I can display only the posts matched from the reblog collection. This of course will allow me to display the title of the post and other attributes.
This is my js
Template.reblogging.helpers({
descovered() {
var id = FlowRouter.getParam('_id');
//fetch the reblog collection contents
var rebloged = reblog.find().fetch();
//log below is showing that the fetch is successful because i can see the objects fetched in console
console.log(rebloged);
//create the relationship between the posts collection and the reblog collection
var reblogger = posts.find({
id: {
$in: rebloged
}
}).fetch();
//nothing is showing with the log below, so something is going wrong with the line above?
console.log(reblogger);
return reblogger
}
});
I must be missing something because this seems a pretty straightforward thing but it's not woring
And my HTML is like this
<template name="reblogging">
{{#each descovered }}
<ul class="">
<li>
<h5 class="">{{title.rendered}}</h5>
</li>
</ul>
{{/each}}
</template>
You don't need to convert to strings and parse, you can use .map() directly on the cursor to create an array of descovered values. Also since you are using Blaze you can just return a cursor instead of an array. I suspect you also meant to use your FlowRouter _id parameter in your first .find(). If you didn't then there's no need to get that param in your helper.
Template.reblogging.helpers({
descovered() {
const id = FlowRouter.getParam('_id');
const reblogArr = reblog.find(id).map(el => { return el.descovered });
return posts.find({ id: { $in: reblogArr } });
}
);
As it turns out, the matching was accurate, however, the data from reblog collection needed to be treated with REGEX to get rid of everything else apart from the values I needed, then turn them into an array, this is the final code that worked. leaving it here, Hopefully, it will help someone in the future.
Template.reblogging.helpers({
descovered() {
var id = FlowRouter.getParam('_id');
//fetch the reblog collection contents
var rebloged = reblog.find().fetch();
//log below is showing that the fetch is successful because i can see the objects fetched in console
console.log(rebloged);
//turn it into a string so i can extract only the ids
var reblogString = JSON.stringify(rebloged).replace(/"(.*?)"/g, '').replace(/:/g, '').replace(/{/g, '').replace(/}/g, '').replace(/,,/g, ',').replace(/^\[,+/g, '').replace(/\]+$/g, '');
//after have extracted what i needed, i make it into an array
var reblogArr = reblogString.split(',').map(function(item) {
return parseInt(item, 10);
});
//create the relationship between the posts collection and the reblog collection
var reblogger = posts.find({
id: {
$in: reblogArr
}
}).fetch();
//nothing is showing with the log below, so something is going wrong with the line above?
console.log(reblogger);
return reblogger
}
});

Backbone collection fetch imported incorrectly

I have a collection which is fetched from a REST endpoint, where it receives a JSON.
So to be completely clear:
var Products = Backbone.Collection.extend({
model: Product,
url : 'restendpoint',
customFilter: function(f){
var results = this.where(f);
return new TestCollection(results);
}
});
var products = new Products();
products.fetch();
If I log this, then I have the data. However, the length of the object (initial) is 0, but it has 6 models. I think this difference has something to do with what is wrong, without me knowing what is actually wrong.
Now, if I try to filter this:
products.customFilter({title: "MyTitle"});
That returns 0, even though I know there is one of that specific title.
Now the funky part. If I take the ENTIRE JSON and copy it, as in literally copy/paste it into the code like this:
var TestCollection = Backbone.Collection.extend({
customFilter: function(f){
var results = this.where(f);
return new TestCollection(results);
}
});
var testCollectionInstance = new TestCollection(COPY PASTED HUGE JSON DATA);
testCollectionInstance.customFilter({title: "MyTitle"});
Now that returns the 1 model which I was expecting. The difference when I log the two collections can be seen below. Is there some funky behaviour in the .fetch() I am unaware of?
Edit 2: It may also be of value that using the .fetch() I have no problems actually using the models in a view. It's only the filtering part which is funky.
Edit 3: Added the view. It may very well be that I just don't get the flow yet. Basically I had it all working when I only had to fetch() the data and send it to the view, however, the fetch was hardcoded into the render function, so this.fetch({success: send to template}); This may be wrong.
What I want to do is be able to filter the collection and send ANY collection to the render method and then render the template with that collection.
var ProductList = Backbone.View.extend({
el: '#page',
render: function(){
var that = this; /* save the reference to this for use in anonymous functions */
var template = _.template($('#product-list-template').html());
that.$el.html(template({ products: products.models }));
//before the fetch() call was here and then I rendered the template, however, I needed to get it out so I can update my collection and re-render with a new one (so it's not hard-coded to fetch so to speak)
},
events: {
'keyup #search' : 'search'
},
search : function (ev){
var letters = $("#search").val();
}
});
Edit: New image added to clearify the problem
It's a bit tricky, you need to understand how the console works.
Logging objects or arrays is not like logging primitive values like strings or numbers.
When you log an object to the console, you are logging the reference to that object in memory.
In the first log that object has no models but once the models are fetched the object gets updated (not what you have previously logged!) and now that same object has 6 models. It's the same object but the console prints the current value/properties.
To answer your question, IO is asynchronous. You need to wait for that objects to be fetched from the server. That's what events are for. fetch triggers a sync event. Model emits the sync when the fetch is completed.
So:
var Products = Backbone.Collection.extend({
model: Product,
url : 'restendpoint',
customFilter: function(f){
var results = this.where(f);
return new TestCollection(results);
}
});
var products = new Products();
products.fetch();
console.log(products.length); // 0
products.on('sync',function(){
console.log(products.length); // 6 or whatever
products.customFilter({title: 'MyTitle'});
})
It seems like a response to your ajax request hasn't been received yet by the time you run customFilter. You should be able to use the following to ensure that the request has finished.
var that = this;
this.fetch({
success: function () {
newCollection = that.customFilter({ title: 'foo' });
}
});

Backbone/Marionette - Checking if a model exists

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().

Reading all object values in StackMob

I've been using StackMob's fetch() function to retrieve all objects in my table, given by the fetch() method as a single model object. Each object has multiple attributes with corresponding values. I've then used JSON.stringify(model) to get this output:
{"0":{"attribute1":val1,"attribute2":"val2","attribute3":val3,"attribute4":"val4"},
"1":{"attribute1":val1,"attribute2":"val2","attribute3":val3,"attribute4":"val4"},
"2":{"attribute1":val1,"attribute2":"val2","attribute3":val3,"attribute4":"val4"}
...and so on.
How can I print out just each of those values?
StackMob has a get() function that can be used as such:
var a_book = new Book({ 'title': 'The Complete Calvin and Hobbes', 'author': 'Bill Watterson' });
console.debug(a_book.get('title')); //prints out "The Complete Calvin and Hobbes"
But I'm not sure how I would use it in my situation, where I'm retrieving all of the objects in my table, rather than creating a new object (as above).
since the stackmob js sdk is built on backbonejs you could declare a collection which can contain all the "rows" in your "table" and then iterate through the collection to perform whatever action you want to each item in your "table"
var MyModel = StackMob.Model.extend({
schemaName:'YOUR_SCHEMA_NAME'
});
var MyCollection= StackMob.Collection.extend({
model:MyModel
});
var myCollection = new MyCollection();
myCollection.fetch({async: true});
myCollection.each(function(item){
//do something with item, maybe print
});

How to send information to a newly instantiated Backbone collection?

I have an API that filters a set of objects based on the ID present in the URL, like so:
http://localhost:8000/api/v1/goal_update/?goal__id=12&format=json
I have a collection that does a GET request on the above URL. Here is the code for the collection:
var GoalUpdateList = Backbone.Collection.extend({
// Reference the Goal Update model
model: GoalUpdate,
// Do HTTP requests on this endpoint
url: function() {
return "http://localhost:8000/api/v1/goal_update/?goal__id=" + this.goal_id + "&format=json";
},
// Set the goal ID that the goal update list corresponds to
initialize: function(goal_id) {
this.goal_id = goal_id;
},
});
I want to pass in the ID when I create a new instance of the collection, so I created a view that has this code:
this.collection = new GoalUpdateList(this.model.get("id"));
It thinks that I am passing in model parameters, though. I am trying to pass in information that tells the collection what URL to use. So it runs the ID through a validate function and messes up the rest of the backbone code.
Backbone's collections expect model data as the first parameter, as you've already noted. But you can specify a second parameter as an object literal, like other Backbone objects. Just pass null or undefined as the first parameter, and then in your collection initialize method, get the options as the second parameter.
GoalUpdateList = Backbone.Collection.extend({
initialize: function(data, options){
this.goal_id = options.goal_id;
}
});
new GoalUpdateList(null, model.get("goal_id"));

Categories

Resources