I am working on a backbone application at the moment, and I wanting to order my collection based on various attributes, I have sorting working based on cost, duration, start and finish date (timestamps) but I am struggling to sort the collection based on the name attribute, here is my collection code,
var ProjectCollection = Backbone.Collection.extend({
url: '/projects',
model: app.Project,
sort_key: "finish_date",
sort_order: 1,
parent_filter: false,
initialize: function() {
var pm = this.get('projectmanager');
this.set("project_manager", new app.User(pm));
var sp = this.get('salesperson');
this.set("sales_person", new app.User(sp));
},
comparator: function (item1, item2) {
return (item1.get(this.sort_key) - item2.get(this.sort_key)) * 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();
},
});
Is there are way to sort alphabetically on a string?
I have added the new comparator function, and this is output I get when ordering via name in an ascending fashion,
Android Application
Hello World I am a new instance of a project
Java Application
Simon's Project
Some new project
Symfony App,
Windows Application
iPhone Application
As you can see iPhone application should not be last in the list.
i changed your comprator function to support strings along with numerics
the problem in your comprator function is that it subtracting strings which yields NaN
i changed it to be like
comparator: function (item1, item2) {
var sortValue = (item1.get(this.sort_key) > item2.get(this.sort_key)) ? 1 : -1;
return sortValue * this.sort_order;
},
this will just compare, get a value 1 for a > b and then change it for desc sort like you did
check this jsFiddle
EDIT:
to support case insensitive ordering, comparator function should be modified to handle strings separately
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;
}
jsFiddle updated
Related
I'm stuck in developing a filter for a list.
My list displays in the item an icon depending on two entries from the actual data record.
new sap.ui.core.Icon({
src : {
parts : [
{ path : "Model>date" },
{ path : "Model>inventory" }
],
formatter : function(date, inventory) {
if (!inventory) {
return "sap-icon://decline";
}
if (date != "31.12.9999") {
return "sap-icon://decline";
}
return "sap-icon://accept";
}
}
})
Now i will implement a filter for this information:
a) show all entries with the decline-icon
b) show all entries with the accept-icon
My problem is that the filter is accepting only one path-element.
var dateFilter =new sap.ui.model.Filter({
path : "date",
test : function(date) {
//pseudo code
if(#filter-true)
show entries with accept;
else
show entries with decline;
}
});
aFilters.push(dateFilter)
But actual i've no idea for implementing this filter.
Can someone help me please?
PS: I know that my grammar is not the best ;)
You can combine multiple filters that rely on different paths.
var oDateFilter = new sap.ui.model.Filter({
path : "date",
test : function(sDate) {
if(sDate === "31.12.9999"){
return true;
}
return false;
}
});
The code above creates a filter that operates using the "date" property of your model.
var oInventoryFilter = new sap.ui.model.Filter({
path : "inventory",
test : function(bInventory) {
if(bInventory !== true){
return false;
}
return true;
}
});
The code above creates a filter using the "inventory" property. Now, we are combining it via the following code:
var oCombinedFilter = new sap.ui.model.Filter({
filters: [oDateFilter, oInventoryFilter],
and: true
});
This filter returns true for all elements that got the inventory property set to true (make sure that the parameter is a boolean!) and the date set to "31.12.9999".
For more information see sap.ui.model.Filter.
In my app I've got 2 functions to work with localStorage.
When I add the first and second items, it works properly, but when it is the third item, it gives an error.
Here are the functions:
w.getLocalStorage = function() {
var c = localStorage.getItem('cities');
var arr = [];
arr.push(c);
return c ? arr : [];
}
w.setLocalStorage = function(data, googleData, cities, name) {
if (data) {
city.name = data.name;
city.coord.lat = data.coord.lat;
city.coord.lon = data.coord.lon;
cities.push(JSON.stringify(city));
// console.log(city);
localStorage.setItem("cities", cities);
} else if (googleData) {
city.name = name;
city.coord.lat = googleData.results[0].geometry.location.lat;
city.coord.lon = googleData.results[0].geometry.location.lng;
console.log('cities', cities);
cities.push(JSON.stringify(city));
// console.log(cities, city);
localStorage.setItem("cities", cities);
}
}
Here is what it returns for the first 2 items:
Array[1]
0 : "{"name":"Pushcha-Voditsa","coord":{"lat":50.45,"lon":30.5}}"
1 : "{"name":"Kyiv","coord":{"lat":50.4501,"lon":30.5234}}"
Here is what when the third items is added:
Array[1]
0 : "{"name":"Pushcha-Voditsa","coord":{"lat":50.45,"lon":30.5}}, {"name":"Kyiv","coord":{"lat":50.4501,"lon":30.5234}}"
1 : "{"name":"Kyiv","coord":{"lat":50.4501,"lon":30.5234}}"
How can I fix this?
As you can only store string in localStorage, to persist object convert them in stringified format using JSON.stringify() method and on retrieval use JSON.parse() to parses the JSON string to construct the JavaScript value or object.
Here are the code snippet, which require attention. You should persist stringified cities data
cities.push(city);
localStorage.setItem("cities", JSON.stringify(cities));
While retrieval, parse it JavaScript object
var cities = localStorage.getItem('cities');
var c = cities ? JSON.parse(cities) || [];
I am new to backbone js. I am trying to create an application that displays certain data and allows users to filter them with certain attributes and narrow down results. My fetched data has three fields : Organisation name, Organisation type and Country. Users can apply filter on Organisation type and Country by selecting them from drop down list. The Organisation type and Country could be multiple. I used jQuery's select2 to allow users to select multiple organisation types and countries. The Organisation name is filtered on each keystroke on the input field. I managed to create a filter that filters the record based on all three attributes separately. Here's my collection along with my filter function:
var OrganisationCollection = Backbone.PageableCollection.extend({
model: Organisation,
state: {pageSize: 20},
mode: "client",
queryParams: {
currentPage: "current_page",
pageSize: "page_size"
},
filterBy: function(organisationName, organisationType, countryName){
var matchingModels;
var filterFunction = function(model) {
var check = (model.get('name').indexOf(organisationName)>-1);
return check;
}
collectionToFilter = this;
matchingModels = filteredCollection(collectionToFilter, filterFunction);
function filteredCollection(original, filterFn) {
var filtered;
// Instantiate new collection
filtered = new original.constructor();
// Remove events associated with original
filtered._callbacks = {};
filtered.filterItems = function(filter) {
var items;
items = original.filter(filter);
filtered._currentFilter = filterFn;
return filtered.reset(items);
};
// Refilter when original collection is modified
original.on('reset change destroy', function() {
return filtered.filterItems(filtered._currentFilter);
});
return filtered.filterItems(filterFn);
};
collectionToFilter = new OrganisationCollection(matchingModels);
return collectionToFilter;
}
});
Now, this filter function only filters the collection based on organisation name. If I were to filter my collection based on organisation type, my filterBy function would be:
filterBy: function(organisationName, organisationType, countryName){
var matchingModels;
var filterFunction = function(model) {
var check = typeof organisationType=='string' ? (model.get('type').indexOf(organisationType)>-1) : (_.indexOf(organisationType, model.get('type')) > -1);
return check;
}
collectionToFilter = this;
matchingModels = filteredCollection(collectionToFilter, filterFunction); // filterFunction would be same as above
collectionToFilter = new OrganisationCollection(matchingModels);
return collectionToFilter;
}
My filterBy country function would be similar to above.
Now my problem is that these three functions work fine separately. But I am unable to merge these three functions to narrow down the results. i.e. when a user filters with countries USA and Australia, types corporate and multilateral, only those organisations from countries USA and Australia having types corporate and multilateral are supposed to show. And again, among those results, if the user types 'A' in Organisation name input field, only those organisations from previous results starting with alphabet 'A' are supposed to show. I have no idea how to implement that.
Here's my function that triggers the filter:
$(document).ready(function(){
var country = $("#filter-country").val();
var countryName = country ? country : '';
var type = $("#filter-org-type").val();
var orgType = type ? type : '';
var orgName = '';
$("#filter").on('input',function(e){
var orgFilter = $('#filter').val();
orgName = orgFilter ? orgFilter : '';
orgCollection.trigger("filter-by-organisation",orgName, orgType, countryName);
});
// Users can select countries from select field #filter-country and types from select field #filter-org-type and click on button #apply-filters to trigger filter function
$(document).on('click', '#apply-filters', function(){
orgCollection.trigger("filter-by-organisation",orgName, orgType, countryName);
});
});
Please help me with this. Thanks
I might not have understood your problem very well, but you could simply do something like this:
var OrganisationCollection = Backbone.PageableCollection.extend({
model: Organisation,
filterBy: function(organisationName, organisationType, countryName) {
var organisationNameResults = this.filter(function(model) {
// your logic for filtering based on organisationName
});
var organisationTypenResults = _.filter(organisationNameResults, function(model) {
// your logic for filtering based on organisationType
});
var countryNameResults = _.filter(organisationTypenResults, function(model) {
// your logic for filtering based on countryName
});
this.reset(countryNameResults);
}
});
It can be refactored, this is just to give you an idea
I am wanting to filter a backbone collection so returned to me are are only models that match or are close too some search parameters,
My models structure looks like this,
{
email: "name#domain.com",
first_name: "Name",
surname: "LastName",
handle: "NameLastName",
initials: "NL"
}
The model above is "User" model, it can be in the "UsersCollection", what I am hoping is achievable, is to search the "UsersCollection" for models based on the email address, first name, last name and handle, at the moment, I can only search based on one attribute, below is the search function from the "UsersCollection",
search : function(letters){
if(letters == "") return this;
var pattern = new RegExp(letters,"gi");
return _(this.filter(function(data) {
return pattern.test(data.get("first_name"));
}));
}
The letters parameter comes from the view, and they the value of a text input. Now this works, but it only creates matches based on the first_name, how could I match across multiple attributes?
now it will return the models that match either of the attribute.
var UsersCollection = Backbone.Collection.extend({
model: User,
myFilter: function( email, first_name, surname , handle) {
filtered = this.filter(function(user) {
return user.get("email") === email || user.get("first_name") === first_name ||
user.get("surname") === surname || user.get("handle") == handle ;
});
return new UsersCollection(filtered);
}
});
var filterdMe = UsersCollection.myFilter("ada#ff.d","something1","something2","something3");
I do something similar as well. I map an input attribute to a model key and pass an object to search that has the attributes the user is searching for. The search function is sort of like this: Here is a minimal jsbin demo.
var Collection = Backbone.Collection.extend({
model:yourModel,
search:function(find) {
return this.filter(function(model) {
var json = model.toJSON();
for (var k in find) {
var re = new RegExp(find[k],"i");
if (json[k].search(re) === -1) return false;
}
return true;
});
}
});
The html code looks like:
<input name="first_name"/>
<input name="last_name"/>
<input name="email"/>
The view code would be (more or less):
var View = Backbone.View.extend({
template:_.template($('#view-template').html()),
events: {
'keyup input' : 'search'
},
search:function() {
var find = {};
this.$('input').each(function() {
var $element = $(this),
modelKey = $element.attr('name'),
modelVal = $element.val();
find[modelKey] = modelVal;
});
var results = this.collection.search(find);
/* do something with results */
}
});
This seems to work decently if you don't have a large data-set. You could also use indexOf and toLowerCase if you didn't want to use a RegExp object. Maybe this will get you started down some path.
I know this is old. I solved the same thing today and thought I should post the code I'm using. I found a lot of this from another stackoverflow question. I can't find it now, sorry!
This will make a new array from a backbone collection. If you want another backbone collection you can do new BackboneCollection(search_results);.
var search_results = this.collection.filter(function(model) {
return _.some(model.attributes, function(val, attr) {
if(val !== null && val !== undefined)
{
if(!_.isString(val))
//if a model's attribute is a number, we make it a string.
{
val = val.toString();
}
//convert both options to uppercase for better searching ;)
val = val.toUpperCase();
q = q.toUpperCase(); //q is the search term, I grabbed it using JQuery
var array = q.split(" ");
return ~val.indexOf(q);
}
else
{
return false;
}
});
});
Note: This will not work for multiple words. If you were searching cars Volkswagen would work but not 1988 Volkswagen unless one of the model's attributes was "1988 Volkswagen" but they would more likely be two different attributes (year and make).
I need to sort individual items in groups.
The Listview api lets me to create sorted groups or sort whole list.
I need to sort item in the respective groups.
Is it possible?
I was struggling with the same issue and neither found a useful example, nor a hint. Then I started to combine SortedListProjection and GroupedSortedListProjection, and by that, finally made it work.
My use case was to have groups in alphabetical, ascending order, but within a group, have the items ordered by a timestamp, descending.
Here is how I set it up in the JavaScript file:
var list = new WinJS.Binding.List(); // list gets populated later in the code
var sortedList = list.createSorted(compareItems);
var groupedList = list.createGrouped(getGroupKey, getGroupData, compareGroups);
WinJS.Namespace.define("my.stuff", {
sortedList: sortedList,
groupedList: groupedList
});
The important thing was to keep the item sorting in sync with the grouping. Therefore the item sorting function is calling grouping functions.
Below are all four functions used for sorting and grouping:
compareItems = function (leftItem, rightItem) {
let leftKey = getGroupKey(leftItem);
let rightKey = getGroupKey(rightItem);
let compare = compareGroups(leftKey, rightKey);
if (compare == 0) { // if keys are equal compare timestamps
return leftItem.timestamp < rightItem.timestamp ? 1
: leftItem.timestamp > rightItem.timestamp ? -1 : 0;
}
return compare;
};
getGroupKey = function (item) {
return item.name + item.category;
};
getGroupData = function (item) {
return {
name: item.name + " (" + item.category + ")"
};
};
compareGroups = function (leftKey, rightKey) {
return leftKey.toUpperCase().localeCompare(rightKey);
};
Finally, the combined declaration of both lists for the ListView:
<div id="dataDeliveriesList" class="hidden"
data-win-control="WinJS.UI.ListView"
data-win-options="{
itemDataSource: my.stuff.sortedList.dataSource,
itemTemplate: select('#itemTemplate'),
groupDataSource: my.stuff.groupedList.groups.dataSource,
groupHeaderTemplate: select('#groupHeaderTemplate'),
layout: {
type: WinJS.UI.ListLayout
}
}">
</div>
It's possible. ObservableCollection doesn't provide a sorting option, and therefore you'll have to create a new collection that's sorted on a selected property. See sample code below. For other sorting options, read the blog post here and another stackoverflow thread.
// TODO: Create an appropriate data model for your problem domain to replace the sample data
var group = SampleDataSource.GetGroup((String)navigationParameter);
this.DefaultViewModel["Group"] = group;
//this.DefaultViewModel["Items"] = group.Items;
// Sort items by creating a new collection
ObservableCollection<SampleDataItem> grpSorted = new ObservableCollection<SampleDataItem>(
group.Items.OrderBy(grp => grp.Title));
this.DefaultViewModel["Items"] = grpSorted;