I have a UI with a form that has dropdowns andcheckboxes in order to filter the results returned from the server.
I wonder if I have to create a Model in order to keep the current settings available or just get the vals with jquery and pass them to the Collections fetch.
What would be better?
That dependes how much you want to invest in design, or you just can live with a more pragmatic approach.
If you run for the design solution we can say that there is not a Collection of results what we are playing with here. It is a FilterSearch model, and every time we are sending the filter params to the server what we are doing is creating an instance of FilterSearch. Let's say:
POST http://myapp.com/filter_searchs?field1=value1&field2=value2
The server will take this request and will return the array of results. Let's say:
{
"field1": "value1",
"field2": "value2",
"results": [ "result1", "result2" ]
}
The Backbone FilterSearch model will be updated with the info responded by the server:
var myFilterSearch = App.FilterSearch.new({ field1: "value1", field2: "value2" });
myFilterSearch.save({ wait:true });
And from there we can create our results Collection:
var myResults = App.Results.new( myFilterSearch.get( "results" ) );
(All code is simplified and not tested)
Another solution can be to store the filter fields into the Collection it self.
App.Results = Backbone.Collection.extend({
url: function() {
return "/results?field1=" + this.field1 + "&field2=" + this.field2 );
}
});
var myResults = App.Results.new({ field1: "value1", field2: "value2" });
myResults.fetch();
This approach is interesting because you can update the results just modifying the filter fields in the Collection and call to .fetch(), all the references to the Collection will remain.
(All code is simplified and not tested)
For filtering collection using backbone the best approach is to fetch the collection and the return a subset filtered collection this will also make you code more reusable
To make the filter you should have a filtered function
var yourCollection = Backbone.Collection.extend ({
filtered : function () {
I suggest to use UnderScore filter which will return true for valid and false for invalid where true is what you are looking for. use this.models to get the current collection models use model.get( '' ) to get the element you want to check for
for example you may have a check box which shows a specific category
var results = _.filter( this.models, function ( model ) {
if ( item.get('category') === 'y' )
return true ;
return false ;
});
You may keep filtering the result with all you dropdown and checkboxes
Then use underscore map your results and transform it to JSON like
results = _.map( results, function( model ) { return model.toJSON() } );
Finally returning a new backbone collection with only results
return new Backbone.Collection( results ) ;
Optionally if you don't want to keep all the data in the collection but just the filtered one you could reset the collection and skip the above return like
this.reset( results ) ;
View : Here your ui data requested you should call the filtered function after passing the ui for values using jquery
Related
I want to fire fetch method on Backbone Collection which would pass an Id parameter similar to what happens in Model.fetch(id)
E.g.
var someFoo= new Foo({id: '1234'});// Where Foo is a Backbone Model
someFoo.fetch();
My Backbone collection:-
var tasks = backbone.Collection.extend({
model: taskModel,
url: '/MyController/GetTasks',
initialize: function () {
return this;
}
});
In my View when I try to fetch data:-
var _dummyId = 10; //
// Tried approach 1 && It calls an api without any `id` parameter, so I get 500 (Internal Server Error).
this.collection.fetch(_dummyId);
// Tried approach 2 && which fires API call passing Id, but just after that
// I am getting error as below:- Uncaught TypeError: object is not a function
this.collection.fetch({
data: {
id: _dummyId
}
});
Found it very late : To cut short the above story I want something like Get /collection/id in backbone.
Thank you for your answers, finally I got the solution from Backbone.js collection options.
Apologies that I couldn't explain the question properly while for same requirement others have done brilliantly and smartly.
Solution : I can have something like :-
var Messages = Backbone.Collection.extend({
initialize: function(models, options) {
this.id = options.id;
},
url: function() {
return '/messages/' + this.id;
},
model: Message,
});
var collection = new Messages([], { id: 2 });
collection.fetch();
Thanks to nrabinowitz. Link to the Answer
As mentioned by Matt Ball, the question doesn't make sense: either you call fetch() on a Collection to retrieve all the Models from the Server, or you call fetch() on a Model with an ID to retrieve only this one.
Now, if for some reason you'd need to pass extra parameters to a Collection.fetch() (such as paging information), you could always add a 'data' key in your options object, and it may happen that one of this key be an id (+add option to add this fetched model rather than replace the collection with just one model)... but that would be a very round-about way of fetching a model. The expected way is to create a new Model with the id and fetch it:
this.collection = new taskCollection();
newTask = this.collection.add({id: 15002});
newTask.fetch();
In your code however, I don't see where the ID is coming from, so I am wondering what did you expect to be in the 'ID' parameter that you wanted the collection.fetch() to send?
I have a complex model served from my back end, which has a bunch of regular attributes, some nested models, and a couple of collections.
My page has two tables, one for invalid items, and one for valid items. The items in question are from one of the nested collections. Let's call it baseModel.documentCollection, implementing DocumentsCollection.
I don't want any filtration code in my Marionette.CompositeViews, so what I've done is the following (note, duplicated for the 'valid' case):
var invalidDocsCollection = new DocumentsCollection(
baseModel.documentCollection.filter(function(item) {
return !item.isValidItem();
})
);
var invalidTableView = new BookIn.PendingBookInRequestItemsCollectionView({
collection: app.collections.invalidDocsCollection
});
layout.invalidDocsRegion.show(invalidTableView);
This is fine for actually populating two tables independently, from one base collection. But I'm not getting the whole event pipeline down to the base collection, obviously. This means when a document's validity is changed, there's no neat way of it shifting to the other collection, therefore the other view.
What I'm after is a nice way of having a base collection that I can have filter collections sit on top of. Any suggestions?
I fleshed out my previous attempt and have come up with an extension to Backbone.Collection that does what I need.
collections.FilteredCollection = Backbone.Collection.extend({
initialize: function(items, options) {
if (_.isUndefined(options.baseCollection))
throw "No base collection to watch";
if (!_.isFunction(options.filterFunc)) {
throw "No filter to apply";
}
_.extend(this, options);
this.listenTo(this.baseCollection, 'all', this.reraise);
},
reraise: function (event) {
this.reset(this.baseCollection.filter(this.filterFunc), { silent: true });
var args = [].slice.call(arguments, 1);
this.trigger(event, args);
}
});
The one small issue I have with this is that I have to manually apply filterFunc to the baseCollection, then pass that in as the items parameter when instantiating a FilteredCollection, but that's something I can live with.
The below code is what I'm using to instantiate. Note that there's another almost-exact copy which is for the collection of ONLY VALID items, but any filters can be applied.
var allDocs = theModel.get('Documents');
var invalidOptions = {
baseCollection: allDocs,
filterFunc: function(item) {
return !item.isValidItem();
}
};
var invalidDocs = allDocs.filter(invalidOptions.filterFunc);
var invalidDocsCollection = new collections.FilteredCollection(
invalidDocs, invalidOptions
);
My model is structured like this:
model = {
distance: 12.05,
widget: {
id: 1,
creationDate: '12/01/2012'
}
}
How do I set the idAttribute of the model to be the id on the widget property? Is there a syntax to do this?
How about rearranging/flattening your model to make id a top-level property? Override parse and you won't need to set the idAttribute:
var YourModel = Backbone.Model.extend({
parse: function (response) {
var distance = response.distance;
response = response.widget;
response.distance = distance;
return response;
}
});
Now id will be automatically picked up by Backbone as the id. If you need to persist your data back to your datastore, you'll need to overwrite methods necessary to transform the data back. If possible, it would be a better solution if your model came structured with id already at a top level.
I have a collection self.models. I also have an array of an object which contains the fields and filters I wish to apply to my collection called filterArr. An example of this would be:
[{field: "Account", filter: "123"}, {field: "Owner", filter: "Bob"}]
The question is, I'm not sure quite how I'd iterate through each of my models to return only those models to which this filterArr applies too, I know it has to be something like this, but this is hard-coded:
self.models = _.filter(self.models, function (model) {
model = model.toJSON();
return model.Account === "123" && model.Owner === "Bob";
});
First of all, underscore's filter returns an Array, so what you're doing effectively here is substituting your collection with a filtered array. Something like this would be more appropriate:
this.filtered = _.filter(this.models, ...);
Backbone Collection implements most of underscore's useful functions. So the solution above is far from optimal (in fact it doesn't work at all the way you want it to), instead do something like this:
this.filtered = this.models.filter(function() {...});
The best way to get and set model fields by name are by far the get and set functions of Backbone Model, so why not use them? Model.toJSON() works, but you're just copying the attributes-hash unnecessarily around.
this.filterObj = { // Why not make it an object instead of array of objects
"Account": "123",
"Owner": "Bob"
};
this.filtered = this.models.filter(function(model) {
// use the for in construct to loop the object
for (filter in filterObj) {
// if the model doesn't pass a filter check, then return false
if (model.get(filter) !== filterObj[filter]) return false;
}
// the model passed all checks, return true
return true;
});
Hope this helps!
Basically you need to iterate over model's attributes and compare their keys and values to filter's attributes.
self.models = _.filter(self.models, function (model) {
var fits = true; // does this model "fit" the filter?
model = model.toJSON();
_.each(model, function(modelVal, modelKey) {
_.each(filterArr, function(filter) {
if (modelKey === filter.field && modelVal !== filter.filter) {
fits = false
}
}
})
return fits
})
However, with a bit of underscore magic there's a trickier way. I'm not sure if it's better in terms of performance, but it surely looks better to my eye.
// change a bit the way filter is described:
var filter = {Account: '123', Owner: 'Bob'},
// save an array of filter keys (Account, Owner)
filterKeys = _.keys(filter),
// and an array of its values (123, Bob)
filterVals = _.values(filter)
self.models = _.filter(self.models, function (model) {
// pick a subset of model which has the same keys as filter
var filteredSubset = _.pick(model.attributes, filterKeys),
// pick values of this subset
subsetValues = _.values(filteredSubset)
// this values have to be equal to filter's values
// (use .join() to turn array to string before comparison due to references)
return filteredVals.join() === subsetValues.join()
})
Notice that in the latter case all models have to have all the keys declared in filter.
If I were you and I were looking for a most robust way, I would rewrite the first example, but would have changed _.each to standard for loops and return false as soon as first 'non-fit' value is met.
So I already got this working using a loop but is wondering is there is a cleaner way to do this.
Basically i got a model that got an array of ids from another model, currently I loop every id and add manually model to a new collection using this filter on the model collection.
getOneById : function(id){
return this.filter(function(data) {
return data.get("id") == id;
});
},
Is there a way to directly return a list doing something like
getAllById : function(arrayIds){
return _(this.filter(function(data) {
??????? return data.get("id") == eachID;
}));
},
Thanks!
you could reduce the looping by checking to see if the id of your object is at an index > -1 in the array:
function(arrayIds){
var models = _.select(collection, function(model){
return (_.indexOf(arrayIds, model.id) > -1);
});
return models;
}
this requires the inclusion of underscore.js in your code, but since you're already using backbone, you already have that.
My solution:
Having collection (Backbone.Collection) and arrayIds
var collection2 = new Backbone.Collection();
collection2.add(collection.models.filter(function(model){
return arrayIds.indexOf(model.id) !== -1;
}));
Four lines :D
console.assert(collection2.length === arrayIds.length) //OK