1

I have this dom:

<ul id="appsList">
    <li><span>some value</span> <span>android</span></li>
    <li><span>some value</span> <span>ios</span></li>
    <li><span>some value</span> <span>facebook</span></li>
    <li><span>some value</span> <span>android</span></li>
    <li><span>some value</span> <span>ios</span></li>
    <li><span>some value</span> <span>android</span></li>
    <li><span>some value</span> <span>android</span></li>
</ul>

and I want to sort the list items by an array I got:

The array can be: [ios,android,facebook] and any other combination of it and not must contain all keys, can be just [ios].

I want the list to be sorted by the array so if I have in it [ios,android,facebook] then the list will be sorted like this:

<ul id="appsList">
    <li><span>some value</span> <span>ios</span></li>
    <li><span>some value</span> <span>ios</span></li>
    <li><span>some value</span> <span>android</span></li>
    <li><span>some value</span> <span>android</span></li>
    <li><span>some value</span> <span>android</span></li>
    <li><span>some value</span> <span>android</span></li>
    <li><span>some value</span> <span>facebook</span></li>
</ul>
1
  • This is the closest we got, there is a bug there that I cannot solve, look at the first result, suppose to be ios instead of anroid. plnkr.co/edit/9rAXxPvOMcd3pezIVJz9?p=preview Commented Jun 19, 2016 at 15:18

2 Answers 2

4

Update :

Final Plunker

For example :

<li><span>some value</span> <span class="sort">android</span></li>

And use .find("sort")


You have to tweak the .sort() function,

var arr = ['ios','android','facebook'] ;

var $li = $('#appsList li').sort(function(a,b) {
      var firstValue = $(a).find('.sort').text();
        var secondValue = $(b).find('.sort').text();

        var first = arr.indexOf(firstValue);
        var second = arr.indexOf(secondValue);

        var output = (second === -1 && first > -1) ? -1 :
        (second > -1 && first === -1) ? 1 :
        (second === -1 && first === -1) ? firstValue > secondValue :
        (second > first ? 1 : -1);
        return output;
});

$('#appsList').html($li);

Example snippet.

var arr = ['ios','android','facebook'] ;
    
var $li = $('#appsList li').clone().sort(function(a,b) {

        var firstValue = $(a).find('.sort').text();
        var secondValue = $(b).find('.sort').text();

        var first = arr.indexOf(firstValue);
        var second = arr.indexOf(secondValue);

        var output = (second === -1 && first > -1) ? -1 :
        (second > -1 && first === -1) ? 1 :
        (second === -1 && first === -1) ? firstValue > secondValue :
        (second > first ? 1 : -1);
        return output;
});

$('#resultAppsList').html($li)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

Before sort : 
 <ul id="appsList">
    <li><span>some value</span> <span class="sort">android</span></li>
    <li><span>some value</span> <span class="sort">ios</span></li>
    <li><span>some value</span> <span class="sort">facebook</span></li>
    <li><span>some value</span> <span class="sort">android</span></li>
    <li><span>some value</span> <span class="sort">ios</span></li>
    <li><span>some value</span> <span class="sort">android</span></li>
    <li><span>some value</span> <span class="sort">android</span></li>
 </ul>

After sort :
<ul id="resultAppsList">
</ul>

Sign up to request clarification or add additional context in comments.

6 Comments

It's worth noting that, in this case, $(this).text() will be exactly the same as $(this).find('span').text(), so you can reduce the chain a little to avoid unnecessary work; also, if you cache the text-property to use (var textProp = 'textContent' in document? 'textContent' : 'innerText'), then you could easily avoid jQuery in that portion and simply use this[textProp] to retrieve the text. But for those minor changes it's really not worth a separate answer to do exactly the same thing.
I was going through a very difficult answer then I saw your answer perfectly using sort() and it blew my mind! well done
@DavidThomas, yes you are totally right. I have added that line to my answer. The reason why I did not use $(this).text() is that li might contain some text, and the result is based purely on content of spans. Your 2nd point too is valid.
Yeah, it's very much a case-by-case possibility, and should you have taken that approach without warning of the difficulties I imagine you'd have had comments pointing out that issue anyway. Some days you just can't win! ;-)
Updated Plnkr Ugh. Silly mistakes I made!
|
0

While this is, of course, perfectly possible in jQuery it's worth noting that jQuery cannot do things that the native DOM cannot; therefore to complement other answers I chose to offer a non-jQuery approach using ES5 in order to maintain compatibility with older browsers (though an ES6 alternative is offered later):

// caching the '#appsList' element:
var list = document.getElementById('appsList'),

  // working out which text-property is present in the browser
  // (Chrome and Firefox, to the best of my knowledge, support
  // both innerText and textContent; whereas IE < 9 supports
  // only innerText):
  textProp = 'textContent' in document ? 'textContent' : 'innerText',

  // retrieving an Array of the (element) children of the list,
  // using Array.prototype.slice() and Function.prototype.call()
  // to turn the collection of child elements into an Array;
  // we then sort the Array using Array.prototype.sort() and
  // its anonymous function:
  sorted = Array.prototype.slice.call(list.children, 0).sort(function(a, b) {
    // here we find the first/only <span> element in the previous
    // Array element ('a') and the first/only <span> in the current
    // ('b') element and comparing its text:
    return a.querySelector('span')[textProp] > b.querySelector('span')[textProp];
  }),

  // creating a documentFragment:
  fragment = document.createDocumentFragment();

// iterating over the sorted Array of elements:    
sorted.forEach(function(el) {

  // appending a clone of the element ('el') to the
  // fragment (in order to show the before/after together):
  fragment.appendChild(el.cloneNode(true));
});

// retrieving the element to show the newly-sorted result:
document.getElementById('resultAppsList').appendChild(fragment);

var list = document.getElementById('appsList'),
  textProp = 'textContent' in document ? 'textContent' : 'innerText',
  sorted = Array.prototype.slice.call(list.children, 0).sort(function(a, b) {
    return a.querySelector('span')[textProp] > b.querySelector('span')[textProp];
  }),
  fragment = document.createDocumentFragment();

sorted.forEach(function(el) {
  fragment.appendChild(el.cloneNode(true));
});
document.getElementById('resultAppsList').appendChild(fragment);
ul {
  list-style-type: none;
  margin: 0 0 1em 0;
  padding: 0;
  display: inline-block;
  width: 45vw;
  box-sizing: border-box;
}
li {
  width: 100%;
  padding: 0;
  margin: 0 0 0.2em 0;
}
span {
  margin-left: 1em;
}
ul::before {
  content: attr(data-state)': ';
  margin-bottom: 0.2em;
  display: block;
  width: 60%;
  border-bottom: 1px solid #000;
  color: limegreen;
}
<ul id="appsList" data-state="Before">
  <li>1<span>android</span>
  </li>
  <li>2<span>ios</span>
  </li>
  <li>3<span>facebook</span>
  </li>
  <li>4<span>android</span>
  </li>
  <li>5<span>ios</span>
  </li>
  <li>6<span>android</span>
  </li>
  <li>7<span>android</span>
  </li>
</ul>
<ul id="resultAppsList" data-state="After">
</ul>

JS Fiddle demo.

To bring the above a little more up-to-date for modern browsers, here we have the ES6 update (though I suspect it could probably be even more ES6-ified):

// using the let statement to initialise variables:
let list = document.getElementById('appsList'),
  textProp = 'textContent' in document ? 'textContent' : 'innerText',

  // using Array.from() to convert the Array-like collection into an
  // Array:
  sorted = Array.from(list.children).sort(function(a, b) {
    return a.querySelector('span')[textProp] > b.querySelector('span')[textProp];
  }),
  fragment = document.createDocumentFragment();

// using Arrow functions within the Array.prototype.forEach() call, since we're
// not using the 'this' keyword and it's pleasantly abbreviated:
sorted.forEach(el => fragment.appendChild(el.cloneNode(true)));

document.getElementById('resultAppsList').appendChild(fragment);

let list = document.getElementById('appsList'),
  textProp = 'textContent' in document ? 'textContent' : 'innerText',
  sorted = Array.from(list.children).sort(function(a, b) {
    return a.querySelector('span')[textProp] > b.querySelector('span')[textProp];
  }),
  fragment = document.createDocumentFragment();

sorted.forEach(el => fragment.appendChild(el.cloneNode(true)));
document.getElementById('resultAppsList').appendChild(fragment);
ul {
  list-style-type: none;
  margin: 0 0 1em 0;
  padding: 0;
  display: inline-block;
  width: 45vw;
  box-sizing: border-box;
}
li {
  width: 100%;
  padding: 0;
  margin: 0 0 0.2em 0;
}
span {
  margin-left: 1em;
}
ul::before {
  content: attr(data-state)': ';
  margin-bottom: 0.2em;
  display: block;
  width: 60%;
  border-bottom: 1px solid #000;
  color: limegreen;
}
<ul id="appsList" data-state="Before">
  <li>1<span>android</span>
  </li>
  <li>2<span>ios</span>
  </li>
  <li>3<span>facebook</span>
  </li>
  <li>4<span>android</span>
  </li>
  <li>5<span>ios</span>
  </li>
  <li>6<span>android</span>
  </li>
  <li>7<span>android</span>
  </li>
</ul>
<ul id="resultAppsList" data-state="After">
</ul>

References:

1 Comment

This is the closest we got to the solution: plnkr.co/edit/9rAXxPvOMcd3pezIVJz9?p=preview

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.