how to sort backbone models/views? - javascript

What is the basic way to keep models any views in order using Backbone? I have some idea but it's not totally clear.
I want to keep them in order by a field called "created_at". I know there is the ability to provier a comparator function in the collection but I'm not sure how it works.
I also want this order in the collection to be reflected by the views at all times (in a list). I'm not exactly sure where I tie into the model though. I'm guessing i look for change in an index attribute and then update a to match?
Thanks very much for any help or explanation!

When you define your collection, you also define comparators.
I did it this way recently:
comparators: {
id: function(animal) {
return Number(animal.get("id"));
},
d_id: function(animal) {
return -Number(animal.get("id")); // descending
},
name: function(animal) {
return animal.get("name");
},
d_name: function(animal) {
return String.fromCharCode.apply(String, _.map(animal.get("name").split(""), function (c) {
return 0xffff - c.charCodeAt();
})
);
},
}
these I defined within my collection code.
Then, in rendering my collection views, I just did this
(this was within my view that renders the whole collection, in initialize():
this.collection = new MyCollection();
this.collection.comparator = Collection.comparators[// here I put 'id' or 'd_id' etc. ];
this.collection.sort();
Since this code is in your views's initialize, you can define your comparator
when you initialize your view, and pass it a name of a comparator like this:
var directory = new pageView("d_id");
and than thru initialize(comparator_id) you could pass this to your code in initialize:
this.collection = new MyCollection();
this.collection.comparator = Collection.comparators[comparator_id];
this.collection.sort();
And then I can use the collection in rendering and re-rendering the view/page
Edited:
Here is Backbone's collection.comparator documentation,
And right below it is an explanation of sort()
Basically, comparator can be a property of a model or a function that returns a property,
Or a negative property, if it's numeric, for descending order,
or a string or its reverse value for descending order
like in the example I gave you here.
So, comparator returns a property like "id" or "name", or "-id" , "-name" etc etc.
(for string you can't just make it "negative", you need to apply more complex function,
as I wrote.)

Related

Ember - Using controller functions in other controller functions

I am trying to create a page that shows a list of products with filtering and multiple ways of sorting the list. Rather than repeating the filtering code for each sort, I wanted to start each of the sorting methods with a call to the filtering method. I can currently filter by name:
filterText: '',
filteredResults: function() {
var filterText = this.get('filterText');
var regexp = new RegExp(filterText,'gi');
var causes = this.get('model.products').filter(function(name){
return name.get('name').match(regexp);
});
return causes;
}.property('filterText'),
However I now want to use this function in the sort methods. I've tried using Ember.computed.sort :
priceAscSorting: ['price'],
sortPriceAsc: Ember.computed.sort('filteredResults', 'priceAscSorting'),
but it seems like it treats the first input as null if I stick it in a template. If I try calling the filter method inside the sorting method:
priceAscSorting: ['views'],
sortPriceAsc: function() {
var products = filteredResults();
}
Ember throws a compiler error saying 'filteredResults' is not defined. How can I access my filter methods to use in my sorting methods?
Your code is correct, it will take filteredResults computed property and return sorted result in ascending order by using price key.
priceAscSorting: ['price'],
sortPriceAsc: Ember.computed.sort('filteredResults', 'priceAscSorting'),
but it seems like it treats the first input as null if I stick it in a
template
that means you need to check filteredResults computed property

Iterate over a backbone collection based on properties of models in an elegant way

I have a backbone collection and based on an attribute of models inside collection I iterate over the collection and show it in the UI.
The logic is, if the model has property isNewCar as true, I'll first show them all in UI, followed by a separator then i'll show all models having property isNewCar as false.
this.cars.forEach(function (car, index)
{
if(car.isNewCar()){ //IF A NEW CAR
//some logic.
//Attach current view in DOM with this model's properties
}
});
//Here, Attach some separator in DOM
this.cars.forEach(function (car, index)
{
if(!car.isNewCar()){ //IF 'NOT' A NEW CAR
//some logic.
//Attach current view in DOM with this model's properties
}
});
This looks messy and I understand its not so elegant, can someone suggest a better way to replace above code with some elegant solution?
I would suggest have a common rendering logic to render the items in a separate function say renderCars() and filter the collection as below
function filterCars(isNew) {
var isNewCar = isNew
return function(car) {
return (car.isNewCar() === isNewCar);
}
}
renderCars (this.cars.filter(filterCars(true)) );
renderCars (this.cars.filter(filterCars(false)) );
All we have done above is created a helper function filterCars that takes a boolean to decide if we need new cars or not. This helper function returns a function that is used to filter the cars .
Backbone filter uses the underscore filter that returns a new array of the filtered results. I am passing this to the common render function.
I did not test this but this should help clear out some repeated code.

Sorting a backbone collection alphabetically

I have a backbone collection that is pulling in a bunch of template names for people to use and I would like to sort them alphabetically so their easier to find. I am very unsure of how to do this though.
I have my backbone collection
this.templates = new Backbone.Collection();
and then I'm sorting through the templates to figure out where to add what.
var Names = this.model.collection.models.map(function(model){
return (model.attributes.Name) ? model.attributes.Name : 'Template';
});
Names.forEach(function(name) {
_this.templates.add(api.collections[(_this.templateType)].where({Name : name, ShowInToolBox : true}));
//adding a bunch of conditionals to add cretin forms to modules that are outside the scope
}
Is it possible to alphabetize these?
I've tried adding .sortBy("Name") to the backbone collection, but it just stopped my code from running.
Backbone offers the comparator property for sorting. You can pass the name of the property that the collection should be sorted on into the constructor:
this.templates = new Backbone.Collection([], { comparator: 'Name' })
Every time the collection changes, it will be re-sorted by the property name in the comparator. If you're doing something more complicated, you can define the comparator as a function. If you go this route, then I would recommend extending Backbone.Collection for clarity:
var Templates = Backbone.Collection.extend({
comparator: function(template1, template2){
return template1.get('someValue') - template2.get('someValue')
}
})
var templates = new Templates()
Backbone collections can be sorted with the comparator function.
If you define a comparator, it will be used to maintain the collection in sorted order. This means that as models are added, they are inserted at the correct index in collection.models. A comparator can be defined as a sortBy (pass a function that takes a single argument), as a sort (pass a comparator function that expects two arguments), or as a string indicating the attribute to sort by.

AngularJS - complex filtering based on categories etc

I've done some google-fu but all I can find about AngularJS filters is simple examples about simple filters (mostly on a single field's value).
What I'm after thoguh is somewhat more complex, and I kinda look for help on how to tackle my situation.
Imagine you have an array of the following JSON objects:
{
"id":"1",
"title":"Title",
"categories":[
{"id":"14","name":"DIY"}
],
"topics":[
{"id":"12","name":"Junk Food"}
]
},
{
"id":"2",
"title":"Title 2",
"categories":[
{"id":"4","name":"Test"},
{"id":"14","name":"DIY"},
],
"topics":[
{"id":"2","name":"Food"}
]
}
[...]
so basically each object can have ANY number of "categories" and / or "topics".
Now, my goal is to create a frontend interface that allows me to cumulatively apply various kinds of filters to those JSON objects.
For example, I'd like to say: show only the entries that have category.id = 14 AND topic.id = 2 [etc] and still support deep-linking for the filtered results.
So here's where I'm stuck:
1) what's the best way to use routes for this (ie how would you structure the URLs to support ANY number of filter (based on different values)
2) how should i keep track of the filters added? (ie, how many and which filters have been selected by the user)
Looking at the documentation for the AngularJS filters I'll obviously use the 2nd example for the filtering parameter:
Object: A pattern object can be used to filter specific properties on objects contained by array. For example {name:"M", phone:"1"} predicate will return an array of items which have property name containing "M" and property phone containing "1". A special property name $ can be used (as in {$:"text"}) to accept a match against any property of the object. That's equivalent to the simple substring match with a string as described above.
But I'm not so sure on how to make sure i'm checking the right field (ie topic.id for topics vs category.id for categories)...
Simply put, I'd love to see an example for such a less-trivial filtering scenario.
I think you need something like this instead. See his 'other simple alternative'. I do complex filtering in a service that's injected into my controller, and expose the filtered list on my $scope to the View. I only use Angular filters for relatively simple tasks.
Re: the question about how to expose this on the URL, you'll need some way of representing those filters as strings, and can use $location and $routeParams to populate them into your controller.
This can work if you write a custom filter:
var module = angular.module('app', []);
module.filter("property", ["$filter", function($filter){
var parseString = function(input){
return input.split(".");
}
function getValue(element, propertyArray) {
var value = element;
angular.forEach(propertyArray, function(property) {
value = value[property];
});
return value;
}
return function (array, propertyString, target) {
var properties = parseString(propertyString);
return $filter('filter')(array, function(item){
return getValue(item, properties) == target;
});
}
}]);
HTML part can look like this:
<ul>
<li ng-repeat="data in items | property:'categories.id':<id_of_a_category_we_want>">
{{ data }}
</li>
</ul>
Credit: OnOFF-Switch blog

sort backbone collection based on model attributes

I have a backbone collection which is rendered in a table. I would like to make the table sortable based on certain attributes the collection has, like "task_status","task_group". I've being reading the backbone documentation about collection.comparator,nd collection.sort.
How can I get this done?
The comparator function is used to compare two models in the collection and it can compare them in any (consistent) way that it wants to. In particular, it can choose which model attribute to use so you could have something like this in your collection:
initialize: function() {
this.sort_key = 'id';
},
comparator: function(a, b) {
// Assuming that the sort_key values can be compared with '>' and '<',
// modifying this to account for extra processing on the sort_key model
// attributes is fairly straight forward.
a = a.get(this.sort_key);
b = b.get(this.sort_key);
return a > b ? 1
: a < b ? -1
: 0;
}
and then you just need some methods on the collection to change the sort_key and call sort:
sort_by_thing: function() {
this.sort_key = 'thing';
this.sort();
}
In older Backbones, calling sort will trigger a "reset" event whereas newer versions will trigger a "sort" event. To cover both cases you can listen to both events and re-render:
// in the view...
initialize: function() {
this.collection.on('reset sort', this.render, this);
}
Demo: http://jsfiddle.net/ambiguous/7y9CC/
You can also use listenTo instead of on to help you avoid zombies:
initialize: function() {
this.listenTo(this.collection, 'reset sort', this.render);
}
Demo: http://jsfiddle.net/ambiguous/nG6EJ/
#mu-is-too-short's answer is good, except there's an easier way to compare the field values:
The easiest way to sort the collection based on a field, is to provide a comparator function that returns the exact field's value you want to sort by. This kind of comparator causes Backbone to call sortBy function, instead of sort, which then does that complex comparison on it's own and you don't have to worry about the logic.
So in essence, you don't have to provide a complex comparator function, unless you have a more advanced need for determining the order.
var myCollection = Backbone.Collection.extend({
sort_key: 'id', // default sort key
comparator: function(item) {
return item.get(this.sort_key);
},
sortByField: function(fieldName) {
this.sort_key = fieldName;
this.sort();
}
});
After this you can just call the collection's sortByField -function with a string that represents the key that you want to sort by.
For example:
collection.sortByField('name');
Modified #my-is-too-short's demo: http://jsfiddle.net/NTez2/39/
#jylauril's answer helps out tremendously, but needed to modify the demo (perhaps slight changes in backbone since it was posted?)
Looks like you need to trigger a render after you've sorted.
$('#by-s').click(function() {
c.sortByField('s');
v.render();
});
Updated #my-is-too-short's demo: http://jsfiddle.net/NTez2/13/

Categories

Resources