jQuery object .sort() not sorting - javascript

I'm trying to sort a list by it's value but I can't figure out why it doesn't work once I changed it.
The nothing is sorted once I change the a and b part to ipArray[a/b.value].
I can confirm that all the option values exists in the ipArray as I'm able to get those values by using selectList.each(function(){
alert( ipArray[$(this).val()].ipAdd)
)
ipArray is an array that has objects of this format
{
ipAdd : "",
network : ""
}
id = id of the entry
eg. ipArray[id] = { ip : "1.2.3.4", network: "test network"};
// Example
var id = $("#list");
var selectList = $("option", id );
selectList.sort(function (a, b) {
a = a.value;
b = b.value;
// Doesn't work. Would like to sort by network name then by ip address
//a = ipArray[a.value].ipAdd;
//b = ipArray[b.value].ipAdd;
return a - b;
});
id.html(selectList);
Edit:
Created a fiddle https://jsfiddle.net/bme4rv6o/12/
With the expected output

You are sorting using a property value which doesn't even exists in the object
selectList.sort(function (a, b) {
var _network = a.network.localCompare(b.network);
var _ip = a.ip.localCompare(b.ip);
// Will first check network, if equal will then check IP
return (_network==0?_ip:_network);
});

First, note that a.value is a string. Parse it to int and subtract 1 from it to then use the result as the index.
Finally, use localeCompare to sort strings, instead of subtracting.
Fiddle: https://jsfiddle.net/bme4rv6o/13/
Also, you'll probably need to add more logic to sort function to handle a tie (EG, when both a and b are equal, return an IP comparison (which would also need proper parse for each token separated by the dots)).

Simply one line of code:
It evaluates the first part and if the network is the same, then the other part is evaluated.
selectList.sort(function (a, b) {
return a.network.localeCompare(b.network) || a.ipAdd.localeCompare(b.ipAdd);
});
The algorithm is working fine, see below. But your selectList does contain an object, whicht is not sortable like you would like.
var ipArray = [];
function add(ip, network) {
ipArray.push({ ip: ip, network: network });
}
add("1.1.1.1", "A");
add("2.2.2.2", "B");
add("3.3.3.3", "D");
add("4.4.4.4", "C");
add("5.5.5.5", "A");
ipArray.sort(function (a, b) {
return a.network.localeCompare(b.network) || a.ip.localeCompare(b.ip);
});
document.write('<pre>' + JSON.stringify(ipArray, 0, 4) + '</pre>');

Related

Dynamically create array of objects, from array of objects, sorted by an object property

I'm trying to match and group objects, based on a property on each object, and put them in their own array that I can use to sort later for some selection criteria. The sort method isn't an option for me, because I need to sort for 4 different values of the property.
How can I dynamically create separate arrays for the objects who have a matching property?
For example, I can do this if I know that the form.RatingNumber will be 1, 2, 3, or 4:
var ratingNumOne = [],
ratingNumTwo,
ratingNumThree,
ratingNumFour;
forms.forEach(function(form) {
if (form.RatingNumber === 1){
ratingNumOne.push(form);
} else if (form.RatingNumber === 2){
ratingNumTwo.push(form)
} //and so on...
});
The problem is that the form.RatingNumber property could be any number, so hard-coding 1,2,3,4 will not work.
How can I group the forms dynamically, by each RatingNumber?
try to use reduce function, something like this:
forms.reduce((result, form) => {
result[form.RatingNumber] = result[form.RatingNumber] || []
result[form.RatingNumber].push(form)
}
,{})
the result would be object, with each of the keys is the rating number and the values is the forms with this rating number.
that would be dynamic for any count of rating number
You could use an object and take form.RatingNumber as key.
If you have zero based values without gaps, you could use an array instead of an object.
var ratingNumOne = [],
ratingNumTwo = [],
ratingNumThree = [],
ratingNumFour = [],
ratings = { 1: ratingNumOne, 2: ratingNumTwo, 3: ratingNumThree, 4: ratingNumFour };
// usage
ratings[form.RatingNumber].push(form);
try this its a work arround:
forms.forEach(form => {
if (!window['ratingNumber' + form.RatingNumber]) window['ratingNumber' + form.RatingNumber] = [];
window['ratingNumber' + form.RatingNumber].push(form);
});
this will create the variables automaticly. In the end it will look like this:
ratingNumber1 = [form, form, form];
ratingNumber2 = [form, form];
ratingNumber100 = [form];
but to notice ratingNumber3 (for example) could also be undefined.
Just to have it said, your solution makes no sense but this version works at least.
It does not matter what numbers you are getting with RatingNumber, just use it as index. The result will be an object with the RatingNumber as indexes and an array of object that have that RatingNumber as value.
//example input
var forms = [{RatingNumber:5 }, {RatingNumber:6}, {RatingNumber:78}, {RatingNumber:6}];
var results = {};
$.each(forms, function(i, form){
if(!results[form.RatingNumber])
results[form.RatingNumber]=[];
results[form.RatingNumber].push(form);
});
console.log(results);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
HIH
// Example input data
let forms = [{RatingNumber: 1}, {RatingNumber: 4}, {RatingNumber: 2}, {RatingNumber: 1}],
result = [];
forms.forEach(form => {
result[form.RatingNumber]
? result[form.RatingNumber].push(form)
: result[form.RatingNumber] = [form];
});
// Now `result` have all information. Next can do something else..
let getResult = index => {
let res = result[index] || [];
// Write your code here. For example VVVVV
console.log(`Rating ${index}: ${res.length} count`)
console.log(res)
}
getResult(1)
getResult(2)
getResult(3)
getResult(4)
Try to create an object with the "RatingNumber" as property:
rating = {};
forms.forEach(function(form) {
if( !rating[form.RatingNumber] ){
rating[form.RatingNumber] = []
}
rating[form.RatingNumber].push( form )
})

The key value of one object is changing on update of other similar type of object in javascript

I am using two different array object initialPermissionArr[item.id] and newPermissionArr[roleId] in two different functions
function(item){
vm.initialPermissionArr[item.id] = item.permissions.map(function (permission) {
permission.status = true;
return permission;
});
}
staticArr[item.id] = item.permissions.map(function (permission) {
permission.status = true;
return permission;
});
newpermissionArr[item.id] = vm.initialPermissionArr[item.id];
Below function updates the array, if same object is found it updates the status and if new object is found it pushes to newPermissionArr
function onChanageOfPermission(roleId,item) {
var flag = false ;
for (var key in newpermissionArr[roleId]){
if(newPermissionArr[roleId][key].id == item.id){
flag = true;
newPermissionArr[roleId][key].status = item.status;
break;
}
}
if (!flag) {
newPermissionArr[roleId].push(item);
}
}
So when newPermissionArr[roleId][key].status = item.status; is updated it also update the status in the initialPermissionArr[item.id] also.
And initial declaration is
var newPermissionArr = [];
var staticArr = [];
where for eg item is
{
roleId : 1,
permissions : [{"name": "A", "id" : 1},{ "name" : "B", "id" : 2 }]
}
I need initial object Array to remain same and at the end i need to compare the initial array with the modified array and need to find the reference however on updating the status it updates in both array. How to avoid this ?
The arrays reference the same object. To modify just one of them, you should use slice() function for clone the array:
newpermissionArr[item.id] = vm.initialPermissionArr[item.id].slice();
This is happening because of following line of code
newpermissionArr[item.id] = vm.initialPermissionArr[item.id];
Here object is passed by reference, so whenever newpermission is updated intialpermission will also be updated.
To fix this just copy the intialPermissionArr to newPermissionArr.
Unfortunately,plain javascript does not have any function like angular.copy. So you will have to do this in following way-
newrPermissionArr[item.id] = JSON.parse(JSON.stringify(vm.intialPermissionArr[item.id]));
this should fix your problem.
When you assign something to a and b and it is a pointer in memory to object c. Then as soon as you change it to c2 both a and b will get c2 from that point as they were just pointers to same location.

Backbone.js filter

I'm trying to get a filter function to work properly. The function takes an array as param. The array containing all the filter params as ints. like for example: [2,36,11]. So the ints represent different filter options. Then i match this array elements with another int that comes as a variable. like 2 for example. so if 2 is in this array the filter should go on and do its thing.
The problem is that this function only works as long as the ints in the array not are higher than 9. So if one of the filter option is 12 it won't work. But lets say the array has an int wich is 1 and you choose the filer on filter option 12 it will accept that as a match and render wathever should be rendered, since indexOf takes it as a match. How can i solve this quite weird behaviour?
filter: function(f) {
var filter = this.collection.filter(function(o){
var accept = false;
$(f).each(function(i,val){
if(_.indexOf(o.get('tags'), val) >-1){
accept = true;
}
})
return accept;
});
var filtered = new PeopleCollection(filter);
new PeopleView({
el: this.$('.peoplelist'),
collection: filtered
});
}
So as tags is a string with the numbers you can split the sting on , and then do a straight comparison on each element against the val.
filter: function(f) {
var filter = this.collection.filter(function(o) {
var accept = false;
$(f).each(function(i, val) {
//only run if accept is false
if (!accept) {
//if tags is a string of numbers spereated by commas
//turn that string into array and test each one against the val
_.forEach(o.get('tags').split(","), function(tag) {
if (accept) {
return;
}
//use parseInt to ensure both are numbers
if (parseInt(tag) === parseInt(val)) {
accept = true;
}
});
}
})
return accept;
});
var filtered = new PeopleCollection(filter);
new PeopleView({
el: this.$('.peoplelist'),
collection: filtered
});
}
here is a quick fiddle using the same base concept, just removed the view part so open your console to see the results of each filter, http://jsfiddle.net/leighking2/gmtvt12p/
This happens because o.get('tags') is returning a String, as you mentioned in a comment.
Your use of _.indexOf(value, item) will work on any value that has a length property and can be accessed with array notation (e.g. value[0]). You can check it by looking at underscore's source code for indexOf function.
The String type of JavaScript fits this signature. You are actually executing your function over each character of the String:
'1,2,3'.length // 5
'1,2,3'[0] // '1'
'1,2,3'[1] // ','
This is why when you have the string "12" it will match either "1" or "2".
To convert your string to an array of numbers, you can do something like this:
'1,2,3'.split(',').map(function(x) { return parseInt(x, 10); }); // [1, 2, 3]
After this conversion, you can use _.indexOf as you expect.
So, try this:
filter: function(f) {
var filter = this.collection.filter(function(o){
var accept = false;
$(f).each(function(i,val){
var tags = o.get('tags').split(',').map(function(x) { // <-
return parseInt(x, 10); // <-
}); // <-
if(_.indexOf(tags, parseInt(val, 10)) >-1){ // <-
accept = true;
}
})
return accept;
});
var filtered = new PeopleCollection(filter);
new PeopleView({
el: this.$('.peoplelist'),
collection: filtered
});
}

Advanced AngularJS live Search

I have the following data
persons = [
{
"age":20,
"parameter1":94,
"name":"Foobarin"
},
{
"age":33,
"parameter1":49,
"name":"Johan"
}
]
I want to create an advanced live search which recognizes patterns. An examples could be "foo a20 p94" which would get me the first object.
a20 - Search where age is 20
p94 - Search where parameter1 is 94
and then if there is any other text which does not have any set prefix then test that against the name value.
All the values (except name which is case-insensitive) is of type integer. I want to limit the prefixes to predefined such as a, p and not age20.
The data-sets is around 400.
I've created a basic live search which searches all variables in the object, but now I do not know where to continue. Any ideas?
It's not foolproof but as a first-pass this is what I'd start with, I'll try to talk through it in pseudo.
First declare a propertyMatrix, a simple object-map which can point "prefixes" to the actual property names which exist within person. The searchPersons function accepts a single string (query) value and consists of two main parts:
The query string is split on whitespace characters into an array of "tokens". Each token is also an array of exactly 2 length, containing each token name and token value. At this stage it attempts find the predetermined prefix - if no such entry exists the token name: name is assumed.
A filter is then applied to the persons array. For each person we iterate over the tokens array and make an appropriate comparison, if any single check fails we return false (thus excluding the person from the results).
var propertyMatrix = {
'a': 'age',
'p': 'parameter1'
},
searchPersons = function(query){
var tokens = query.split(/\s+/).map(function(t){
t = t.toLowerCase();
var i = t.match(/\d+$/), p;
if(i && i.length) {
p = t.substring(0, t.indexOf(i));
if(p in propertyMatrix)
return [propertyMatrix[p], parseInt(i, 10)];
}
return ['name', t];
}),
result = persons.filter(function(p){
for(var i=0, l=tokens.length; i<l; i++){
var token = tokens[i][0], value = tokens[i][1];
if(!(token in p)) return false;
if(token === 'name'){
if(p.name.toLowerCase().indexOf(value)<0) return false;
} else if(p[token] !== value) return false;
}
return true;
});
return result;
};
fiddle

jquery JSON array as localStorage item

Little change of my code and it's partially works :)
var db = {
hotels: JSON.parse(localStorage.getItem('table') || "[]"),
get objects() {return this.hotels},
set objects(obj) {
obj = this.hotels;
localStorage.setItem('table', JSON.stringify(obj))
}
}
jQuery(function(){
var count = localStorage.getItem('count');
if(!count) {
count = 0;
}
function Add(item){
var client = {
ID : jQuery(item).find("#txtID").val(),
Name : jQuery(item).find("#txtName").val(),
Photo : jQuery(item).find("#txtPhone").val(),
Link : jQuery(item).find("#txtEmail").val()
};
db.objects = db.objects.push(client);
count = count+1;
localStorage.setItem('count',count);
jQuery('.panel2 a span').text('('+ localStorage.getItem('count') +')');
jQuery(item).find('.add_rem').hide();
jQuery(item).find('.remove').show();
jQuery("#tblList .empty").hide();
jQuery("#tblList").find('li:gt(0)').remove();
jQuery.each(db.objects,function(i,element) {
jQuery("#tblList").append("<li class='added"+db.objects[i].ID+"'>"+
"<img src='../../images/general/delete.gif' alt='Delete"+i+"' class='delete'/>" +
"<a href='"+db.objects[i].Link+"' title='"+db.objects[i].Name+"'>"+
" <img src='"+db.objects[i].Photo+"' alt='"+db.objects[i].Name+"'>" +
" <span>"+db.objects[i].Name+"</span>" +
" </a>" +
"</li>");
})
return true;
}
function Delete(item){
jQuery(item).prev('.add_rem').show();
jQuery(item).find('.remove').hide();
jQuery(item).find('.remove').removeAttr('alt');
}
function List(){
if(count > 0) {
jQuery("#tblList .empty").hide();
jQuery('.panel2 a span').text('('+ localStorage.getItem('count') +')');
}
for(var i= 0; i<= count; i++) {
var cli = JSON.parse(db.hotels);
if(cli[i] != null){
jQuery("#"+cli[i].ID).find('.add_rem').hide();
jQuery("#"+cli[i].ID).find('.remove').show();
jQuery("#"+cli[i].ID).find('.remove').attr('alt','Delete'+i);
jQuery("#tblList").append("<li class='added"+cli[i].ID+"'>"+
"<img src='../../images/general/delete.gif' alt='Delete"+i+"' class='delete'/>" +
"<a href='"+cli[i].Link+"' title='"+cli[i].Name+"'>"+
" <img src='"+cli[i].Photo+"' alt='"+cli[i].Name+"'>" +
" <span>"+cli[i].Name+"</span>" +
" </a>" +
"</li>");
}
}
}
jQuery("#frmCadastre").bind('submit',function(e){
e.preventDefault()
return Add(this);
});
List();
jQuery(".remove, .delete").bind("click", function(e){
e.preventDefault();
Delete(this);
List();
});
})
now my question is how to push element to array after page refresh this is located in function Add()
array looks like this
"["{"ID":"1","Name":"test","photo":"/link/to/photo.jpg"}"]"
and if i add another element before page refresh it works great
"["{"ID":"0","Name":"test0","photo":"/link/to/photo0.jpg"}","{"ID":"1","Name":"test1","photo":"/link/to/photo1.jpg"}"]"
but if i Reload page and try to add an element Firebug is throwing:
`TypeError: db.objects.push is not a function
db.objects = db.objects.push(client);`
We will start to clear out what happens with HTML5 Local Storage. Local storage is a place in your disk defined by every browser that supports it. These places may be different for every browser. They hold tuples with keys and values, both strings. If you want to save a whole object to the disk, you must serialize it. That means you have to transform it into an array of data, in our case chars -> string. In javascript the most common object serialize function is JSON.stringify. Its input is a valid JSON object, which in our case is an array and it will make it into a string using the literals you use to initialize an object like {x:5}. JSON.stringify([{x:5},{x:6},{x:7}]) will have the following output: "[{x:5},{x:6},{x:7}]". And to reconstruct your object from a string you use JSON.parse(x) where x is a valid json string. You want now to have an array of objects, the first thing you'll think of is to serialize your array you have as a var in your program and add a special key you remember to store it into your disk. Each browser has seperate localStorage for every site that is hosted by a server.
An example that stores a value bound to a key in localstorage is this:
localStorage.setItem('x','5');
localStorage['x'] = 5;
localStorage.x = 5;
all of them do the same thing, and their speed is in descending order. Now you have at Chrome->resources->localstorage:
+-------------------+
| Key | Value |
+---------+---------+
| x | "5" |
+---------+---------+
When you make your first visit to the page, you have nothing in localStorage, so you must have some initial values. Trying to get a value by:
return localStorage.getItem('x');
return localStorage['x'];
return localStorage.x;
will give you undefined. There is a nice operator made in javascript and is the ||.
null || 5 //returns 5
undefined || 3.14 //returns 3.14
'' || 6 //returns 6
[] || {} //returns []
If the left operand "exists", return it, else return the right one. This makes things faster with localStorage.getItem('x') || 5 so if a tuple with key x exists it will return the value of the item with the specified key, else it will return the 5, which is our initial value.
Let's get back to the localStorage again. Remember the tuples are saved into the disk, which is vastly slower to access than things in ram. If I want to read the value of an item in the localStorage let say in a loop, several times, should I read it directly from the disk, or should I read it once from the disk and save it into ram to access it faster? You surely know what makes more sense...So I must have a variable that is the clone of the one in the localStorage. Let's say I name it private_var. It must have an initial value which will be:
var private_array = JSON.parse(localStorage.getItem('array')) || [];
When you want to change your array in localstorage (e.g pushed an item) you use:
private_array.push(item)
localStorage.setItem('array', JSON.stringify(private_array))
Your localstorage will be like:
+---------+-----------------------------------------+
| Key | Value |
+---------+-----------------------------------------+
| array | [{"name":"george", "surname":"bush"}] |
+---------+-----------------------------------------+
To make things faster in terms of code production, not program speed you can define setters and getters.
var obj = {
temp: 5,
get x( ) { return this.temp },
set x(value) { this.temp = value }
}
we have an object named obj, a member temp and a setter and a getter function, just like in some java code. You can check obj.temp === 5. These special operators allow us to write
obj.x = obj.x + 6;
and it will be executed as something like this:
obj.set_x(obj.get_x() + 6);
So let's say you have an interface named db (database, similar to what a locastorage is) and a "private member" which is clearly not. You can see an implementation at http://ejohn.org/blog/javascript-getters-and-setters/ with __define(G/S)etter__ which has real private member, but this one is surely faster to write and more readable.
var db = {
cl_arr: JSON.parse(localStorage.getItem('array')) || [],
get clients( ) { return this.cl_arr },
set clients(v) {
localStorage.setItem('array', JSON.stringify(this.cl_arr));
if(v.constructor === Array) { this.cl_arr = v }
}
}
so when I execute:
db.clients.filter(function(client) { return client.money > 1000 });
this will be executed
db.get_clients().filter...;
And when I try to change the array, I will write this
db.clients = db.clients.push(client);
Even if the push method can change the array, only the get function will be triggered so only the "private" cl_arr variable will change, to trigger the setter which updates our localStorage too I have to write the db.clients = ...

Categories