0

Schema

var my_array = [ 
{
    "title": "a",
    "pinned": {
        "status": "true",
        "order": "1"
    }
},
{
    "title": "d",
    "pinned": {
        "status": "false",
        "order": "n/a"
    }
},
{
    "title": "c",
    "pinned": {
        "status": "true",
        "order": "0"
    }
},
{
    "title": "b",
    "pinned": {
        "status": "false",
        "order": "n/a"
    }
}
];

Desired Behaviour

Sort objects by title alphabetically,
unless they have a pinned status of true,
in which case move this "subset" of items to the beginning of the array,
sorted by their pinned.order value.

An example scenario would be a forum which had posts sorted by date, but also had sticky posts at the top, which were sorted by a defined order.

The original schema would, therefore, be displayed as:

[ 
{// i am at the top, because i have a pinned status of true and order of 0  
    "title": "c",
    "pinned": {
        "status": "true",
        "order": "0"
    }
},
{// i am second from the top, because i have a pinned status of true and order of 1  
    "title": "a",
    "pinned": {
        "status": "true",
        "order": "1"
    }
},
{// i follow in alphabetical order 
    "title": "b",
    "pinned": {
        "status": "false",
        "order": "n/a"
    }
},
{// i follow in alphabetical order 
    "title": "d",
    "pinned": {
        "status": "false",
        "order": "n/a"
    }
}
]

What I've Tried

my_array.sort(function(a, b) {
    return a.pinned.order.localeCompare(b.pinned.order) || a.title.localeCompare(b.title);
});

based on this answer:

https://stackoverflow.com/a/45741804

I also tried...

I thought about creating two separate arrays based on the value of pinned.status, sorting them separately, and then recombining them (as shown below), but I'm wondering if there is something more elegant?

var my_array = [ 
{
    "title": "a",
    "pinned": {
        "status": "true",
        "order": "1"
    }
},
{
    "title": "d",
    "pinned": {
        "status": "false",
        "order": "n/a"
    }
},
{
    "title": "c",
    "pinned": {
        "status": "true",
        "order": "0"
    }
},
{
    "title": "b",
    "pinned": {
        "status": "false",
        "order": "n/a"
    }
}
];


var my_subset = [];

for (var i = 0; i < my_array.length; i++) {

    if (my_array[i].pinned.status === "true") {
        // add to subset
        my_subset.push(my_array[i]);
        // remove from original array
        my_array.splice(i, 1);
    }

}

// sort "pruned" original array alphabetically
my_array.sort(function(a, b) {
    return a.title.localeCompare(b.title);
});

// sort subset array by pinned.order
my_subset.sort(function(a, b) {
    return a.pinned.order.localeCompare(b.pinned.order, undefined, { numeric: true });
});

// prepend subset to original array
var new_array = my_subset.concat(my_array);

// array is sorted as desired
console.log(new_array);
Saeed
  • 2,169
  • 2
  • 13
  • 29
user1063287
  • 10,265
  • 25
  • 122
  • 218
  • Why did you save your status as a string instead of a boolean? – Eliya Cohen Sep 08 '18 at 09:30
  • have been 'back and forwarding' with sending parameters to node, querying database, then handling results in frontend - sending parameters converts them to string, so have just been using strings instead (rather than losing track of what has been/needs to be converted to boolean etc). – user1063287 Sep 08 '18 at 09:35
  • Can you use lodash? – Saeed Sep 08 '18 at 09:42
  • @Saeed - have never used `lodash`. – user1063287 Sep 08 '18 at 09:45
  • @EliyaCohen - i've confronted the problem and now storing numbers and booleans correctly with these two solutions that convert "stringified" numbers and booleans to their intended values: for numbers: https://stackoverflow.com/a/34502964, for booleans: https://stackoverflow.com/a/48929869 For reference, I've noticed it occurs 1) when sending parameters via jquery ajax, 2) when storing and retrieving boolean [cookies](https://github.com/js-cookie/js-cookie), 3) working with HTML data attributes The result from a database action, when sent back to the client, retains non-stringified values. – user1063287 Sep 09 '18 at 05:35

4 Answers4

2

First fix the data by making the numeric string a number and the boolean string a boolean:

for (const item of my_array) {
    item.pinned.status = JSON.parse(item.pinned.status);
    item.pinned.order = Number(item.pinned.order);
}

Now you won't have to compare them as strings. Otherwise your approach is basically fine, you just forgot the most significant indicator whether an item should go to the top or not: its pinned.status. Compare by that first, so that any pinned item comes before any not pinned item.

my_array.sort(function(a, b) {
    return -(a.pinned.status - b.pinned.status) // reverse: true before false
    || (a.pinned.status // equal to b.pinned.status
      ? a.pinned.order - b.pinned.order
      : a.title.localeCompare(b.title));
});

var my_array = [{
    "title": "a",
    "pinned": {
      "status": true,
      "order": 1
    }
  },
  {
    "title": "d",
    "pinned": {
      "status": false,
      "order": 0
    }
  },
  {
    "title": "c",
    "pinned": {
      "status": true,
      "order": 0
    }
  },
  {
    "title": "b",
    "pinned": {
      "status": false,
      "order": 0
    }
  }
];

my_array.sort(function(a, b) {
  return -(a.pinned.status - b.pinned.status) // reverse: true before false
    ||
    (a.pinned.status // equal to b.pinned.status
      ?
      a.pinned.order - b.pinned.order :
      a.title.localeCompare(b.title));
});

console.log(my_array);

You can also do

my_array.sort(function(a, b) {
    return -(a.pinned.status - b.pinned.status) // reverse: true before false
    || a.pinned.order - b.pinned.order
    || a.title.localeCompare(b.title);
});

as not-pinned items have the same order (NaN) but the former is more explicit.

user1063287
  • 10,265
  • 25
  • 122
  • 218
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

If you can use Lodash (utility library for Javascript) you can use orderBy or sortBy:

use Lodash in project:

<script src="lodash.js"></script>

use orderBy to sort:

_.orderBy(my_array, [function(e) { return e.pinned.status}, 'title'], ['asc', 'asc']);

more info on orderBy

Saeed
  • 2,169
  • 2
  • 13
  • 29
1

Just try this:

my_array.sort(function(a, b) {
    return a.title.localeCompare(b.title);
}).sort(function(a, b) {
    return a.pinned.order.localeCompare(b.pinned.order)
});
Ankit
  • 960
  • 6
  • 12
1

there you go:

var my_array = [ 
{
    "title": "a",
    "pinned": {
        "status": "true",
        "order": "1"
    }
},
{
    "title": "d",
    "pinned": {
        "status": "false",
        "order": "n/a"
    }
},
{
    "title": "c",
    "pinned": {
        "status": "true",
        "order": "0"
    }
},
{
    "title": "b",
    "pinned": {
        "status": "false",
        "order": "n/a"
    }
}
];

var trueArr = [];
var falseArr = [];
var titleArr = [];
var tempArr = []

for(var obj of my_array){
  if(obj.pinned.status == "true"){
    trueArr.push(obj);
  }else{
    falseArr.push(obj);
  }
}

function sortArr(arr){
  titleArr = [];
  tempArr = [];

  for(var obj of arr){
    titleArr.push(obj.title);
  }
  
  titleArr = titleArr.sort();
  
  for(var counter = 0; counter < arr.length; counter++){
    tempArr.push(null);
  }
  
  for(var obj of arr){
    tempArr[titleArr.indexOf(obj.title)] = obj;
  }
  for(counter = 0; counter < tempArr.length; counter++){
    arr[counter] = tempArr[counter];
  }
}

function addTrueFalseArr(arr){
  for (var obj of arr){
    my_array.push(obj)
  }
}

sortArr(trueArr);
my_array = [];
addTrueFalseArr(trueArr);
sortArr(falseArr);
addTrueFalseArr(falseArr);

extracts all objects where pinned = true and puts them in an array. Then it does same for objects where pinned = false. Then it sorts both arrays according to their titles (alphabetically), then sets original array to an empty array --> [], then appends the true items to the original array and, finally, appends the false items.

Saif Taher
  • 325
  • 1
  • 12