Backbone.js Why is my default model present in a fetched collection? - backbone.js

I'm sure I'm missing something very basic. I'm setting up a collection of fetched objects
collection.fetch({reset: true})
based on a model that contains a 'defaults' property.
However, when I view the fetched collection in the console I have an extra model in it, which is set with the default attributes. Why is this happening? More importantly, how do I prevent it?
var diningApp = diningApp || {};
(function(){
"use strict";
diningApp.MenuItem = Backbone.Model.extend({
defaults: {
service_unit: null,
course: null,
formal_name: null,
meal: null,
portion_size: null,
service_unit_id: null
}
});
var MenuCollection = Backbone.Collection.extend({
model: diningApp.MenuItem,
url: '/api/dining/get_menus',
parse: function(response){
return response.menu_items;
}
});
diningApp.menuCollection = new MenuCollection();
diningApp.menuCollection.fetch({reset: true});
})();
Here is a portion of the JSON response from the server:
{
"status": "ok",
"menu_items": [
{
"service_unit": "Faculty House",
"course": "Entrees",
"formal_name": "Local CF Fried Eggs GF",
"meal": "BREAKFAST",
"portion_size": "1 Egg",
"service_unit_id": 0
},
{
"service_unit": "Faculty House",
"course": "Entrees",
"formal_name": "CageFree Scrambled Eggs GF",
"meal": "BREAKFAST",
"portion_size": "2 eggs",
"service_unit_id": 0
}]
}
And here's the resulting collection in the console:

If you dig a bit into Backone's source code to check what happens when you reset a collection, you'll end looking at Collection.set. The lines of interest to your problem are :
// Turn bare objects into model references,
// and prevent invalid models from being added.
for (i = 0, l = models.length; i < l; i++) {
attrs = models[i] || {};
// ...
This means that each falsy (false, null, etc.) item in the array is converted to an empty object before being cast into a model and receiving default values.
Either
modify your server response to remove falsy values
or alter your parse method to clean up you array
parse: function(response){
return _.compact(response.menu_items);
}

Related

Elasticsearch query parse failure

Why does this ES multi-match query return a 400 error (bad request)?
"query": {
"multi_match": {
"query": searchTerms,
"fields": ["content", "title"],
"operator": "and"
}
},
size: 100,
from: 0,
highlight: {
fields: {
"title": {number_of_fragments: 0},
"content": {number_of_fragments: 10,fragment_size: 300}
}
}
}
I'm using this query in conjunction with AngularJS UI Bootstrap Typeahead code like this
uib-typeahead="query as query._source.ymme for query in getSuggestions($viewValue)" typeahead-on-select="search($item)"
This is my search() function
$scope.search = function() {
console.log($scope.searchTerms);
$scope.currentPage = 0;
$scope.results.documents = [];
$scope.isSearching = true;
return searchService.search($scope.searchTerms, $scope.currentPage).then(function(es_return) {
var totalItems = es_return.hits.total;
var totalTime = es_return.took;
var numPages = Math.ceil(es_return.hits.total / $scope.itemsPerPage);
$scope.results.pagination = [];
for (var i = 1; i <= 100; i++) {
if(totalItems > 0)
$scope.results.totalItems = totalItems;
$scope.results.queryTime = totalTime;
$scope.results.pagination = searchService.formatResults(es_return.hits.hits);
$scope.results.documents = $scope.results.pagination.slice($scope.currentPage, $scope.itemsPerPage);
}
}
),
function(error){
console.log('ERROR: ', error.message);
$scope.isSearching = false;
}
};
I'm not quite sure what is wrong? I'm thinking it has something to do with $scope, but I'm not sure. The query works when I use it Sense plugin for ES and it also works if I just type in a search term instead of selecting it from the autocomplete dropdown.
If it is $scope, what am I missing?
UPDATE
All shards failed for phase: [query_fetch]
org.elasticsearch.search.SearchParseException: [hugetestindex][0]: from[-1],size[-1]: Parse Failure [Failed to parse source [{"query":{"multi_match":{"query":{"_index":"hugetestindex","_type":"doc","_id":"57","_score":3.877801,"_source":{"ymme":"bourne supremacy"}},"fields":["content","title"],"operator":"and"}},"size":100,"from":0,"highlight":{"fields":{"title":{"number_of_fragments":0},"content":{"number_of_fragments":10,"fragment_size":300}}}}]]
UPDATE 2 Object {_index: "hugetestindex", _type: "doc", _id: "56", _score: 2.5276248, _source: Object}
I think that is the problem, instead of a search terms, its receiving "Object"....?
UPDATE 3So basically it goes like this,
[Object, Object, Object, Object, Object]
0: Object
_id: "229"
_index: "hugetestindex"
_score: 3.3071127
_source: Object
ymme: "bourne supremacy"
__proto__: Object
_type: "doc"
__proto__:
Object1:
Object2:
Object3:
Object4:
Object
length: 5
__proto__: Array[0]
where "bourne supremacy" is the value of the ymme field in the _source Object, the array at the top with the 5 objects is the es return, es_return.hits.hits - this last hits, is the array.
The way you deconstruct your object is by doing something like the following:
object.data.hits.hits._source.field_name;
The above is only a notation to get the value of a single field, you might need to do a loop for each of those values so maybe something like:
$scope.list = []
for hit in object.data.hits.hits {
$scope.list.push(hit._source.field);
}
console.log(list);
Then from your HTML you want to use this list by doing an ng-repeat with it or something similar to get each of the terms in the list.
<div ng-repeat="term in list">{{term}}</div>
If you can update your question with how your object looks and what data you want from it, I can update this answer to match it exactly.
UPDATE
To match your data structure, I'm assuming you want to extract each of the ymme values from those objects. You need to do the following:
<div ng-repeat="object in es_return.hits.hits">
{{object._source.ymme}}
</div>
Just make sure "es_return" is a $scope variable so you can access it as above or just do:
$scope.es_return = es_return;
In your Angular code

Merge objects with different values using Angularjs or Underscore js

I'm trying to merge two objects into a single multidimensional object for use in Angularjs controller by the 'unique_id'. (Note I also have Underscore Js added in).
Object #1 example:
[
{ "unique_id": "001", "title": "Putting Green Challenge - Motion Depends on Force and Mass" },
{ "unique_id": "002", "title": "Molecules to Organisms: Frog Life Cycle" }
]
Object #2 example (has MANY more rows than object 1..):
[
{
"ab_id": "76153F02-29F3-11D8-95EA-951BF95D9AEF",
"unique_id": "001",
"title": "How Speed Relates to Energy",
"state": "NY",
"document_title": "Core Curriculum",
"grade_code": "K-4",
"grade_descr": "Elementary",
"state_id": "1.S2.3a",
"state_text": "Use appropriate \"inquiry and process skills\" to collect data"
},
{
"ab_id": "7980A762-29F3-11D8-BD14-861D7EA8D134",
"unique_id": "001",
"title": "How Speed Relates to Energy",
"state": "NY",
"document_title": "Core Curriculum",
"grade_code": "5-8",
"grade_descr": "Intermediate",
"state_id": "1.S3.2d",
"state_text": "formulate and defend explanations and conclusions as they relate to scientific phenomena"
}
]
My Controller:
abApp.controller("abEE", function(abService, $http, $scope, $q, _) {
var abApp = this;
$scope.abData = $http.get('/data/ab_activities.json', {
cache: false
});
$scope.eeData = $http.get('/activities/eedata', {
cache: false
});
$q.all([$scope.eeData, $scope.abData]).then(function(values) {
var val = ??? This is where I want to merge the objects into one big multidimensional object..
});
Here is the output of console.dir(values);
0 Object { data=[28], status=200, config={...}, more...}
1 Object { data=[743], status=200, config={...}, more...}
This is the desired output I'd like to try and get:
[
{ "unique_id": "001", "title": "Putting Green Challenge - Motion Depends on Force and Mass", "alignments": [{"ab_id": "76153F02-29F3-11D8-95EA-951BF95D9AEF","unique_id": "001","title": "How Speed Relates to Energy",...}, {"ab_id": "7980A762-29F3-11D8-BD14-861D7EA8D134", "unique_id": "001", "title": "How Speed Relates to Energy",...}]
]
Edit
after you updated the question, i created this plunker
hopes it's what you meant
To merge all objects by unique_id
var unions = {};
$q.all([$scope.eeData, $scope.abData]).then(function(values)
{
for (var i = 0; i< values.length; i++)
{
var value = values[i];
if (!unions[value.unique_id])
{
unions[value.unique_id] = {};
}
angular.extend(unions[value.unique_id], value);
}
});
// Do somthing with 'unions'
...
If you could switch to use lodash instead of underscore, it can be achieved like this:
var val = _.values(_.merge(_.indexBy(values[0].data, 'unique_id'), _.indexBy(values[1].data, 'unique_id')));
The underscore doesn't have _.merge(), you have to loop through each property without it.
I don't think angular or underscore have this kind of functionality. I would do something like the following pseudo-code:
tempObject = {}
for object in objectArray
if tempObject[object.unique_id] isnt undefined
tempObject[object.unique_id] = object
else angular.extend(tempObject[object.unique_id], object) // or the other way around depending on your preference
resultingArray = []
for object, key of tempObject
resultingArray.push(object)
You will have to run the for object in objectArray for both the returned arrays but that should work and is probably more efficient than most merge algorithms as at most it will loop through each returned arrays twice.

Merge local Backbone collection with server

I have a local Backbone collection:
var collection = new Backbone.Collection([ { greeting: "hi" }, { greeting: "bye" } ]);
I understand that when I run collection.fetch, Backbone will run collection.set on the results. I need to merge in the response from the server, however. Say the response is:
[ { id: "2", greeting: "hi", name: "Bob" } ]
I would like the resulting collection, after the merge, to be:
[ { id: "2", greeting: "hi", name: "Bob" }, { greeting: "bye" } ]
I understand Backbone already attempts to do some merging here, but if I set the example response above, no merge happens and a new model gets added instead. I assume this is because it merges by id, and here we do not have any ids (in the local collection). In this case, greeting is my unique identifier / key.
The reason I am trying to do this is because I have a local collection and I simply want to see what already exists from that collection (using the key greeting) and merge any findings in.
My solution:
feeds.fetch({
add: false,
remove: false,
merge: false,
data: params,
success: function (feeds, response) {
// Merge any matches
_.each(response.results, function (result) {
_.each(feeds.models, function (feed) {
// We have to `parse` the result before setting it, as Model#set does
// not automatically run `parse` (Collection#set does).
result = feed.parse(result)
if (feed.get('rssUrl') === result.rssUrl) feed.set(result)
})
})
cb(feeds)
}
})
You can tell backbone to use a different key for the id attribute on your model:
GreetingModel = Backbone.Model.extend({
idAttribute: "greeting"
});
GreetingCollection = Backbone.Collection.extend({
model: GreetingModel
});
http://backbonejs.org/#Model-idAttribute
Edit: I suppose you could use two separate collections for local and server side.
var localCollection = new Backbone.Collection([ { greeting: "hi" }, { greeting: "bye" } ]);
ServerCollection = Backbone.Collection.extend({
url: "/api/"
...
});
var serverCollection = new ServerCollection({});
serverCollection.on("reset", function() {
localCollection.each(function(localModel) {
var greeting = localModel.get("greeting");
serverModel = serverCollection.findWhere({greeting: greeting});
if(serverModel) {
localModel.set(serverModel.attributes);
}
});
});
serverCollection.fetch();

Getting models in nested collections in Backbonejs

I'm working in a collection that contains a model with collections of "itself". For example:
[{
id: 1
name: "John",
children: [
{
id: 32
name: "Peter",
children: []
},
{
id: 54
name: "Mary",
children: [
{
id:12,
name: "Kevin"
}
]
},
]
}]
Let say that I want to get the Kevin "user" by its Id. But all that I have is the "first collection". How can I do that?? And about setting a user within a collection? Another thing: Its possible to get all the Kevin "parents" from him? Like Mary and John?
Does anyone has come to a issue like that?
Thanks a LOT
Well I've made a recursive function on the User's Collection that seems to solved the problem for now ( the best of this is that I can use for retrieve a "deep" model and change it.). Something like that ( if someone has any suggestions, be free to give it a opinion ):
findUserById: function(id) {
var self = new Backbone.Collection(this.toJSON());
return thisCollection(id, this);
function thisCollection(id, collection, array) {
var innerSelf = collection || this;
var thisArray = array || [];
for(var i = innerSelf.models.length; i--;) {
if(innerSelf.models[i].get('id') == id) {
return [innerSelf.models[i]].concat([thisArray]);
}else {
if(innerSelf.models[i].get('children').length > 0) {
thisArray.push(innerSelf.models[i]);
return thisCollection(id, innerSelf.models[i].get('children'), thisArray);
}else {
innerSelf.remove(innerSelf.models[i]);
return thisCollection(id, self, []);
}
}
}
}
}
Basically I return an array with 2 items. The first is the record that I'm looking for and the second is an array with the parents of this user.
Underscore (which is a Backbone dependency, so you already have it) is great for this sort of thing; if you use its "map" function (which Backbone provides as a method on Collection) with its find function, you can do the following:
findPersonInPeopleCollection: function(nameWeAreLookingFor) {
function findChildren(person) {
if (!person.children) return [person];
var children = _.map(person.children, function(child) {
foundPeople.push(findChildren(child);
})
return _.flatten(children);
}
var allPeople = _.flatten(myCollectionOfPeople.map(findChildren));
return _(allPeople).find(function(person) {
return person.get('name') == nameWeAreLookingFor;
}
}
If you wanted to store the parents initially you could either add logic to your "Person" model class's initialize function, eg.
var Person = Backbone.Model.extend({
initialize: function() {
_.each(this.get('children'), function(child) {
child.parent = this;
}, this);
}
});
You could also do something similar by overriding your collection's add method, or adding an event handler to it that triggers after people get added.

backbone collection fetch subtitute $.ajax

I'm trying to understanding backbone code. I read this
http://addyosmani.com/blog/building-spas-jquerys-best-friends/
then I plan to change this
if (this._index === null){
$.ajax({
url: 'data/blog.json',
dataType: 'json',
data: {},
success: function(data) {
ws._data = data;
console.log(ws._data);
ws._blogs = new BlogCollection(data.rows);
console.log(ws._blogs);
ws._index = new IndexView({model: ws._blogs});
Backbone.history.loadUrl();
}
});
return this;
}
return this;
use collection fetch
if (this._index === null){
ws._data = new BlogCollection;
ws._data.fetch({success: function(data){console.log(data.models[0].attributes);}});
//ws._blogs = new BlogCollection(ws._data.rows);
//ws._index = new IndexView({model: ws._blogs});
Backbone.history.loadUrl();
}
with this collection
var BlogCollection = Backbone.Collection.extend({
model: Blog,
url : 'data/blog.json',
parse: function(response){
return response;
}
when I read the response from collection, it's give same value as using jquery ajax.
but when I use fetch in controller, why I have to access data.models[0].attributes); to get the same data return.
this is my json
{ "page":"1", "total": "5", "records": "25", "rows":
[
{
"title": "Classic Pop",
"author": "Michael Jackson Collection",
"image": "images/1.jpg",
"tags": ["xyz", "abc", "def"],
"slug" : "classic-pop",
"url": "http://www.localhost/news",
"intro": "hello test",
"content": "hello test, alfjldsajfldjsflja dljfldasjfa jfljdasfl jfldsjf jljdafl jl"
},
{
"title": "Modern Pop/R&B",
"author": "Bruno Mars and Others",
"image": "images/54.jpg",
"tags": ["test", "test2", "test3"],
"slug" : "modern-pop-rb",
"url": "http://www.localhost/news",
"intro": "hello test 2",
"content": "hello test, alfjldsajfldjsflja dljfldasjfa jfljdasfl jfldsjf jljdafl jl"
}
] }
how to make fetch works right??
When extending your Backbone Collection, you should define a parse function that returns an array of rows that represent the models contained in your collection.
In your case, it must be an array of data, with each object in the array representing your Blog model, so you need to return the rows property:
var BlogCollection = Backbone.Collection.extend({
model: Blog,
url : 'data/blog.json',
parse: function(response){
return response.rows;
}
});
Then if your model has a parse function, it will get the data for each object contained in the array, in case you need to do anything with the data before the model's attributes are set:
var Blog = Backbone.Model.extend({
//data will contain one of the items returned from the collection's 'parse' function.
parse: function(data){
return data;
}
});
This will ensure that the Backbone collection will properly create and populate the models represented in the collection with the data.
You will probably want to expose the other metadata (page, total, records) on the collection too, perhaps with a property that is also a Backbone.Model of page/total/records.

Resources