3

I have a list as follows:

<ul>
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
  <li>Fourth</li>
  <li>Fifth</li>
</ul>

What I want is to order the list based on different conditions. For example if the variable state == "foo", I want the Fourth li to appear first and the Second li to appear last like so:

<ul>
  <li>Fourth</li>
  <li>First</li>
  <li>Third</li>
  <li>Fifth</li>
  <li>Second</li>
</ul>

And if the state == "bar" I want the list to appear in some other way.

Also consider that there may be future conditions where state may be equal to "baz" or "foobar" each having their own order. So writing custom conditionals for each state is not an option.

My idea was to maintain an ordered array for each state and then render the list according to the array using something like Mustache or Handlebars. Is there any better solution to this which is also future proof?

Takit Isy
  • 9,688
  • 3
  • 23
  • 47
radiantshaw
  • 535
  • 1
  • 5
  • 18

5 Answers5

4

Here is how I'll do it:

  • Using an object to store the different orders,
  • Using a .forEach() to follow the selected order to re-order the lis.

New snippets

  • Using .map(), the on 'change' function can be reduced to a single line!

Without jQuery (lighter!)

var original_list = document.querySelectorAll('#listToOrder li');
const orders = {
  def: [0, 1, 2, 3, 4],
  foo: [3, 0, 2, 4, 1],
  bar: [3, 2, 4, 1, 0],
  baz: [2, 4, 0, 3, 1]
}

document.querySelector('#selector').addEventListener('change', function() {
  document.querySelector('#listToOrder').innerHTML = orders[this.value].map(index => original_list[index].outerHTML).join('');
});
<ul id="listToOrder">
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
  <li>Fourth</li>
  <li>Fifth</li>
</ul>
<label>Select order: </label>
<select id="selector">
  <option value="def">Default</option>
  <option value="foo">Foo</option>
  <option value="bar">Bar</option>
  <option value="baz">Baz</option>
</select>

With jQuery

var original_list = $('#listToOrder li');
const orders = {
  def: [0, 1, 2, 3, 4],
  foo: [3, 0, 2, 4, 1],
  bar: [3, 2, 4, 1, 0],
  baz: [2, 4, 0, 3, 1]
}

$('#selector').on('change', function() {
  $('#listToOrder').html(orders[this.value].map(index => original_list[index].outerHTML));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="listToOrder">
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
  <li>Fourth</li>
  <li>Fifth</li>
</ul>
<label>Select order: </label>
<select id="selector">
  <option value="def">Default</option>
  <option value="foo">Foo</option>
  <option value="bar">Bar</option>
  <option value="baz">Baz</option>
</select>

Old snippet

I couldn't think about an easier way… but .map() was a good point!

var original_list = $('#listToOrder li');
const orders = {
  def: [0, 1, 2, 3, 4],
  foo: [3, 0, 2, 4, 1],
  bar: [3, 2, 4, 1, 0],
  baz: [2, 4, 0, 3, 1]
}

$('#selector').on('change', function() {
  var lis = '';
  orders[this.value].forEach(function(i){
    lis += original_list[i].outerHTML;
  })
  $('#listToOrder').html(lis);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="listToOrder">
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
  <li>Fourth</li>
  <li>Fifth</li>
</ul>
<label>Select order: </label>
<select id="selector">
  <option value="def">Default</option>
  <option value="foo">Foo</option>
  <option value="bar">Bar</option>
  <option value="baz">Baz</option>
</select>
halfer
  • 19,824
  • 17
  • 99
  • 186
Takit Isy
  • 9,688
  • 3
  • 23
  • 47
3

I suggest to work with a constant for data and an array of states objects to give the appropriate order.

dataOrder key in the states will hold the order for the indexed data array:

const data = ['First', 'Second', 'Third', 'Fourth', 'Fifth'];
const states = [
  {
    state: 'foo',
    dataOrder: [0, 1, 2, 3]
  },
  {
    state: 'bar',
    dataOrder: [3, 2, 1, 0]
  }
];

Note: In the template view you can use Mustache or Handlebars as you wrote in the question

Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46
  • 1
    This is also what I thought initially. I'm waiting for better ways than this. Otherwise this is the answer in my opinion. – radiantshaw Aug 17 '18 at 07:59
  • 2
    This is fine if you need to inevitably order all the different `states` but if you are simply trying to access one of them, an array is probably not the best option because you'd have to dig through the `states` array to find the order. – aug Aug 17 '18 at 08:08
  • 1
    @aug very good comment.. By the way, with an object as you post in your answer is the best approach!! +1 – Yosvel Quintero Aug 17 '18 at 08:22
  • It's kind of funny how your orders are exactly the same as mine…(https://stackoverflow.com/a/51891366/5061000) since you added the other approach. Anyway, @aug got the best approach. You should have created a snippet with that, aug. – Takit Isy Aug 17 '18 at 08:57
  • @TakitIsy You right: removed.. Buy yes, you can see there is needed a good refactor on your answer ;) – Yosvel Quintero Aug 17 '18 at 09:03
2

Probably the best option is to just maintain a map of the states which map to the order.

const statesOrder = {
  'foo':  [0, 1, 2, 3],
  'baz': [2, 1, 3, 0],
   ...
}

And then access the order by passing in the state.

const listOrder = statesOrder[state];

Also don't forget that if you only need to order the list in a visual way, CSS has an order property you might be able to take advantage of.

aug
  • 11,138
  • 9
  • 72
  • 93
  • Good point about CSS `order` property. This was also one of the solutions I came across when searching for it. But the same issue that I have to define individual orders for each condition using `.classes`. But maybe I'll use this is it's not too long. – radiantshaw Aug 17 '18 at 08:17
  • 1
    @radiantshaw while generally frowned upon, you could still append the order values directly as `style` attributes. So you could do `
  • Fourth
  • `, It really depends on your requirements though. – aug Aug 17 '18 at 08:49
  • I think this will be acceptable up to a certain point. Good point! – radiantshaw Aug 17 '18 at 09:03