Ordenar valores con JavaScript
Spanish (Español) translation by Steven (you can also view the original English article)
Las listas y tablas son a menudo la mejor manera de mostrar datos en la web; pero no deberías tener que preocuparte por ordenar esa información manualmente. En el tutorial de hoy, harás un complemento de jQuery que pondrá a todos tus patos en una fila con la facilidad de JavaScript.
Prefacio
Así que, ¿cómo funciona exactamente el ordenamiento en JavaScript? No es demasiado complicado: cualquier objeto de matriz tiene un método de ordenamiento. Si no le pasas ningún parámetro, convertirá los objetos de la matriz en cadenas, los ordenará de forma pseudo-alfabética y los devolverá. Por lo general, esto es terrible; considera ordenar los números del 0 al 10 alfabéticamente. Obtendrías esto: [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]. Afortunadamente, podemos pasar una función al método de ordenamiento. Esa función debe tomar dos parámetros (los dos elementos que se compararán): luego, devolverá 0 si son iguales, un número negativo si el primer parámetro tiene prioridad, o un número positivo del segundo parámetro debe aparecer primero. Así que los números son en realidad lo más simple de ordenar "manualmente":
1 |
numberArray.sort(function(a, b) { |
2 |
return a - b |
3 |
});
|
Obviamente, esto devolverá 0 si los números son iguales, un número negativo si a debería ser el primero, y un número positivo si b debería ser el primero.
Vamos a ver cómo se ordenan varios tipos diferentes de datos, algunos en múltiples formatos; pero todo esto será mucho más útil si lo envolvemos en un complemento de jQuery, ¡así que comencemos por configurar ese shell!
El plugin Shell


Si no estás familiarizado con la escritura de complementos de jQuery, consulta el Screencast de Jeffrey Way "¿Aún no puedes crear un complemento de jQuery?" ¡Te pondrá al día en poco tiempo si te sientes cómodo con jQuery! (Confesión verdadera: en realidad nunca escribí un plugin hasta que hice este).
Configuraremos nuestro complemento, llamado datasort, de esta manera: le pasaremos una serie de elementos para ordenar; podemos especificar cuatro parámetros.
- datatype (el tipo de dato que estás ordenando)
- sortElement (el elemento hijo que deseas ordenar, si lo deseas)
- sortAttr (el atributo que deseas ordenar, si lo deseas)
- reverse (la dirección en la que se deben ordenar)
Por lo tanto, una llamada completamente modificada a nuestro complemento podría tener este aspecto:
1 |
$('ul.names li).datasort({ |
2 |
datatype : 'alpha', |
3 |
sortElement : 'span.first', |
4 |
sortAttr : 'rel', |
5 |
reverse : true
|
6 |
});
|
Aquí está el plugin shell:
1 |
(function ($) { |
2 |
$.fn.datasort = function(options) { |
3 |
var defaults = { |
4 |
//set the default parameter values
|
5 |
datatype : 'alpha', |
6 |
sortElement : false, |
7 |
sortAttr : false, |
8 |
reverse : false |
9 |
},
|
10 |
// combine the default and user's parameters, overriding defaults
|
11 |
settings = $.extend({}, defaults, options), |
12 |
datatypes = {}, |
13 |
base = {}, |
14 |
that = this; |
15 |
|
16 |
if (typeof settings.datatype === 'string') { |
17 |
that.sort(datatypes[settings.datatype]); |
18 |
}
|
19 |
if (typeof settings.datatype === 'function') { |
20 |
that.sort(settings.datatype); |
21 |
}
|
22 |
if(settings.reverse) { |
23 |
that = $($.makeArray(this).reverse()); |
24 |
}
|
25 |
$.each(that, function(index, element) { that.parent().append(element); }); |
26 |
};
|
27 |
})(jQuery); |
Así es como funcionará: configuraremos todas las variables al principio. Luego, si el parámetro datatype es una cadena, encontraremos la función de ordenamiento correspondiente en el objeto datatypes y la ordenaremos; si el parámetro del tipo de dato es una función, lo ordenaremos. Finalmente, si la configuración inversa se establece en verdadero, invertiremos el orden de los elementos ordenados (dado que los objetos jQuery no son verdaderos arreglos de JavaScript, la función inversa no funcionará en ellos, por lo que podemos usar $.makeArray() para convertirlo en uno, y luego, una vez que se invierte, lo re-jquery-fy!
Poniendo un poco más de trabajo de base
En el nivel más bajo, puedes ordenar casi cualquier tipo de dato de una de las siguientes dos formas: los llamaremos alfabéticamente y numéricamente. Vamos a crear estas dos funciones como propiedades de tu objeto base.
1 |
base = { |
2 |
alpha : function(a, b) { |
3 |
a = a.toUpperCase(); |
4 |
b = b.toUpperCase(); |
5 |
return (a < b) ? -1 : (a > b) : 1 : 0; |
6 |
//ternary operator: condition ? returnIfTrue : returnIfFalse
|
7 |
},
|
8 |
number : function(a, b) { |
9 |
a = parseFloat(a); |
10 |
b = parseFloat(b); |
11 |
return a - b; |
12 |
}
|
13 |
},
|
Bastante simple, ¿eh? Simplemente normaliza los dos valores, compara y regresa. La parte difícil es analizar los datos que queremos enviar a estas funciones; eso es lo que haremos ahora. Sin embargo, hay una cosa más.
Al ordenar los elementos de la matriz, es posible que no queramos clasificarlos simplemente por el texto del propio elemento. Los parámetros sortElement y sortAttr de nuestro complemento son para este fin. Por ejemplo, es probable que queramos ordenar las filas de la tabla en función de una determinada columna de celdas de la tabla. En ese caso, usaríamos $('table tr').datasort({ sortElement : 'td.price' }). O tal vez queremos ordenar una lista de imágenes por sus atributos alt: $('ul li').datasort({sortElement : 'img', sortAttr : 'alt'}). Debido a todo esto, necesitamos agregar una función más a nuestro objeto base:
1 |
base = { |
2 |
alpha : function (a, b) { ... }, |
3 |
number : function (a, b) { ... }, |
4 |
extract : function (a, b) { |
5 |
var get = function (i) { |
6 |
var o = $(i); |
7 |
if (settings.sortElement) { |
8 |
o = o.children(settings.sortElement); |
9 |
}
|
10 |
if (settings.sortAttr) { |
11 |
o = o.attr(settings.sortAttr); |
12 |
} else { |
13 |
o = o.text(); |
14 |
}
|
15 |
return o; |
16 |
};
|
17 |
return { |
18 |
a : get(a), |
19 |
b : get(b) |
20 |
};
|
21 |
}
|
22 |
},
|
Puede parecer complicado, pero no lo es. Simplemente creamos un objeto jQuery con cada elemento; si se establece sortElement, usamos el método children() para obtener los elementos correctos. Entonces, si se establece un sortAttr, obtenemos su valor; si no, obtenemos el texto del elemento. Hemos establecido todo esto en una función interna y hemos devuelto un objeto con dos propiedades; estas propiedades son los valores que debemos analizar y enviar a la función de ordenamiento base apropiada.
Esto probablemente parecía mucho trabajo de preparación, pero lo que realmente estábamos haciendo es abstraer la mayor cantidad de código posible. De esta manera, tendrán un código de repetición mucho menor, porque las acciones importantes se han agrupado como funciones.
Ordenar palabras y números
Finalmente estamos aquí: ¡la parte divertida! Comenzaremos por desarrollar dos funciones simples para nuestro objeto datatypes. Estos simplemente pasarán los valores a base.extract() y luego pasarán esos valores de retorno a la clase de ordenamiento apropiado.
1 |
datatypes = { |
2 |
alpha : function (a, b) { |
3 |
var o = base.extract(a, b); |
4 |
return base.alpha(o.a, o.b); |
5 |
},
|
6 |
number : function(a, b) { |
7 |
var o = base.extract(a, b); |
8 |
for (var e in o) { |
9 |
o[e] = o[e].replace(/[$]?(-?\d+.?\d+)/, '\$1'); |
10 |
}
|
11 |
return base.number(o.a, o.b); |
12 |
},
|
13 |
},
|
Nuestro clasificador alfabético debe ser obvio. El clasificador de números hace un poco más: antes de pasar los valores extraídos, elimina un signo de dólar en el frente. He mantenido esta expresión regular simple, pero podrías analizar muchos formatos de números diferentes aquí si quisieras ser complejo. Vamos a probar nuestro plugin en evolución; crear una página html básica:
1 |
<!DOCTYPE html>
|
2 |
<html>
|
3 |
<head>
|
4 |
<meta charset='utf-8' /> |
5 |
<title>Data Sorting</title> |
6 |
<style type='text/css'> |
7 |
ul, table { |
8 |
display:table; |
9 |
float:left; |
10 |
background:#ececec; |
11 |
margin:10px; |
12 |
padding:0; |
13 |
border:1px solid #ccc; |
14 |
}
|
15 |
li, tr { |
16 |
margin:0; |
17 |
padding:8px; |
18 |
border-top:1px solid #fff; |
19 |
border-bottom:1px solid #ccc; |
20 |
list-style-type:none; |
21 |
}
|
22 |
li:first-child { border-top:0 } |
23 |
li:last-child { border-bottom:0 } |
24 |
</style>
|
25 |
</head>
|
26 |
<body>
|
27 |
<table class='a'> |
28 |
<thead>
|
29 |
<tr>
|
30 |
<th rel='alpha' class='first'>First Name</th> |
31 |
<th rel='alpha' class='last'>Last Name</th> |
32 |
</tr>
|
33 |
</thead>
|
34 |
<tbody>
|
35 |
<tr><td class="first">Jeffrey</td> <td class="last">Way</td></tr> |
36 |
<tr><td class="first">Sean</td> <td class="last">Hodge</td></tr> |
37 |
<tr><td class="first">Adam</td> <td class="last">Miller</td></tr> |
38 |
<tr><td class="first">Ian</td> <td class="last">Yates</td></tr> |
39 |
<tr><td class="first">Adrian</td> <td class="last">Try</td></tr> |
40 |
<tr><td class="first">Caleb</td> <td class="last">Aylsworth</td></tr> |
41 |
</tbody>
|
42 |
</table>
|
43 |
|
44 |
<ul class='n'> |
45 |
<li>4.09</li> |
46 |
<li>4.10</li> |
47 |
<li>67.8</li> |
48 |
<li>100</li> |
49 |
<li>-98</li> |
50 |
<li>67.7</li> |
51 |
<li>23</li> |
52 |
</ul>
|
53 |
|
54 |
<ul class="curr"> |
55 |
<li>$299.66</li> |
56 |
<li>$299.57</li> |
57 |
<li>$0.14</li> |
58 |
<li>$80.00</li> |
59 |
</ul>
|
60 |
|
61 |
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" /></script> |
62 |
<script src="jquery.datasort.js" /></script> |
63 |
<script type="text/javascript"> |
64 |
$('table.a tbody tr').datasort({sortElement : 'td.last'}); |
65 |
$('ul.n li').datasort({datatype: 'number', reverse: true}); |
66 |
$('ul.curr li').datasort({ datatype: 'number' }); |
67 |
</script>
|
68 |
</body>
|
69 |
</html>
|
He incluido una tabla y dos listas (y las he diseñado brevemente). Toma nota de nuestras llamadas a los complementos: estamos usando el tipo de dato predeterminado para la tabla, pero ordenando por las celdas de la tabla con una clase de último; intenta cambiar esto a 'td.first'. Luego, ordenamos las listas numéricamente, y revertimos una de ellas. Aquí está la prueba de nuestros trabajos:


Bastante bien, pero esos eran valores relativamente simples; ¿Qué pasa si queremos poder ordenar múltiples formatos para un tipo?
Ordenar fechas
Hay varias formas diferentes de escribir fechas, lo que hace que sea bastante difícil analizarlas para clasificarlas. Sin embargo, podemos cubrir la mayoría de ellos con esto:
1 |
date : function(a, b) {
|
2 |
var o = base.extract(a, b); |
3 |
for (var e in o) {
|
4 |
o[e] = o[e].replace(/-/g, '') |
5 |
.replace(/january|jan/i, '01') |
6 |
.replace(/february|feb/i, '02') |
7 |
.replace(/march|mar/i, '03') |
8 |
.replace(/april|apr/i, '04') |
9 |
.replace(/may/i, '05') |
10 |
.replace(/june|jun/i, '06') |
11 |
.replace(/july|jul/i, '07') |
12 |
.replace(/august|aug/i, '08') |
13 |
.replace(/september|sept|sep/i, '09') |
14 |
.replace(/october|oct/i, '10') |
15 |
.replace(/november|nov/i, '11') |
16 |
.replace(/december|dec/i, '12') |
17 |
.replace(/(\d{2}) (\d{2}), (\d{4})/, '\$3\$1\$2')
|
18 |
.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '\$3\$2\$1');
|
19 |
} |
20 |
return base.number(o.a, o.b); |
21 |
}, |
¿Entonces que hacemos aqui? Primero, aquí está la lógica: si todas las fechas tienen el formato YYYYMMDD, se ordenarán correctamente con la clasificación numérica. Nuestro analizador puede ordenar los siguientes formatos de fecha:
- YYYY-MM-DD
- YYYYMMDD
- DD/MM/YYYY
- month DD, YYYY
Primero, eliminamos nuestros guiones, lo que dejará a YYYY-MM-DD listo para el análisis. Luego, reemplazamos el nombre o la abreviatura de cada mes con su valor numérico. Finalmente, tenemos que reorganizar los números para DD/MM/YYY y month DD, YYYY. Eso es lo que hacen las dos últimas expresiones. Para intentarlo, pega esta lista en nuestro HTML:
1 |
<ul class='date'> |
2 |
<li>2009-10-06</li> |
3 |
<li>sept 25, 1995</li> |
4 |
<li>1990-06-18</li> |
5 |
<li>20100131</li> |
6 |
<li>June 18, 2009</li> |
7 |
<li>02/11/1993</li> |
8 |
<li>15941219</li> |
9 |
<li>1965-08-05</li> |
10 |
<li>1425-12-25</li> |
11 |
</ul>
|
Y llámalo con esto:
1 |
$('ul.date li').datasort({datatype: 'date'}); |


¿Es este un analizador perfecto de fechas? No de ninguna manera; no podemos clasificar DD/MM/YY, porque no hay manera de saber en qué siglo está. Además, no podemos distinguir la diferencia entre DD/MM/YY y MM/DD/YY, así que solo tenemos que eligir uno.
Ordenando el tiempo
Ordenar los valores de tiempo debe ser uno de los más difíciles de clasificar: debemos ser capaces de aceptar 12 horas, 24 horas y valores con o sin etiquetas AM/PM y segundos. Creo que es más fácil ordenar el tiempo alfabéticamente, a pesar de que son todos los números. ¿Por qué? Considera estas dos marcas de tiempo: 00:15:37 y 12:15. El primero debería venir primero, pero si los clasificamos por número, se analizarán como flotantes y terminarán como 1537 y 1215. Ahora, el segundo valor será el primero. Además, al ordenar alfabéticamente, no tenemos que eliminar los dos puntos (parseFloat() los ahogaría). Así que aquí está cómo se hace.
1 |
time : function(a, b) { |
2 |
var o = base.extract(a, b), |
3 |
afternoon = /^(.+) PM$/i; |
4 |
for (var e in o) { |
5 |
o[e] = o[e].split(':'); |
6 |
var last = o[e].length - 1; |
7 |
|
8 |
if(afternoon.test(o[e][last])) { |
9 |
o[e][0] = (parseInt(o[e][0]) + 12).toString(); |
10 |
o[e][last] = o[e][last].replace(afternoon, '\$1'); |
11 |
}
|
12 |
if(parseInt(o[e][0]) < 10 && o[e][0].length === 1) { |
13 |
o[e][0] = '0' + o[e][0]; |
14 |
}
|
15 |
o[e][last] = o[e][last].replace(/^(.+) AM$/i, '\$1'); |
16 |
|
17 |
o[e] = o[e].join(''); |
18 |
}
|
19 |
return base.alpha(o.a, o.b); |
20 |
}
|
Vayamos a través de línea por línea.
1 |
var o = base.extract(a, b), |
2 |
afternoon = /^(.+) PM$/i; |
Comenzamos con nuestras variables: nuestros valores extraídos y una expresión regular para verificar la etiqueta de PM.
1 |
for (var e in o) { |
2 |
o[e] = o[e].split(':'); |
3 |
var last = o[e].length - 1; |
4 |
|
5 |
if(afternoon.test(o[e][last])) { |
6 |
o[e][0] = (parseInt(o[e][0]) + 12).toString(); |
7 |
o[e][last] = o[e][last].replace(afternoon, '\$1'); |
8 |
}
|
A continuación, comenzaremos un bucle for, repasando cada uno de los valores que estamos ordenando; primero, lo dividimos en una matriz en los dos puntos. Creamos una manera fácil de llegar a los últimos elementos de la matriz: nuestra variable 'last'. Luego, probamos nuestra expresión regular de PM en el último elemento de nuestra matriz; si devuelve true, este valor tiene la etiqueta PM. Por lo tanto, agregaremos 12 al primer elemento de nuestra matriz, que será el valor de la hora; hacemos esto porque necesitamos que todos los valores estén formateados en un tiempo de 24 horas. (Ten en cuenta que para hacer esto, debemos convertirlo en un número, agregar 12 y luego volver a convertirlo en una cadena). Finalmente, usamos la expresión regular PM nuevamente para eliminar esa etiqueta del último elemento de la matriz.
1 |
if(parseInt(o[e][0]) < 10 && o[e][0].length === 1) { |
2 |
o[e][0] = '0' + o[e][0]; |
3 |
}
|
4 |
o[e][last] = o[e][last].replace(/^(.+) AM$/i, '\$1'); |
5 |
|
6 |
o[e] = o[e].join(''); |
7 |
}
|
8 |
return base.alpha(o.a, o.b); |
En este último fragmento, verificamos el valor de la hora para dos condiciones: ¿es menor que 10? y la cadena tiene un solo caracter? Esto es importante porque un valor como 08 se analizará como 8 y será menor que 10; pero estamos tratando de ver si necesitamos agregar un cero al frente. Si la cadena tiene solo un carácter, entonces agregamos el cero, entonces 3 se convierte en 03. ¡Esto mantendrá las cosas en orden!
Antes de unirnos a la matriz, eliminamos cualquier etiqueta de AM. Así que ahora esto...
1 |
<ul class='time'> |
2 |
<li>1:15:47</li> |
3 |
<li>3:45 PM</li> |
4 |
<li>12:00:17</li> |
5 |
<li>06:56</li> |
6 |
<li>19:39</li> |
7 |
<li>4:32 AM</li> |
8 |
<li>00:15:36</li> |
9 |
</ul>
|
... se puede ordenar con esto...
1 |
$('ul.time li').datasort({datatype: 'time'});
|
¡Y hemos terminado! He aquí los frutos de nuestro trabajo:

Más valores aleatorios
Hemos configurado nuestro complemento jQuery para que los usuarios puedan pasar las funciones de ordenamiento como el parámetro de tipo de dato. Esto nos permite extender fácilmente el complemento, aunque no tenemos acceso a la 'clase' base desde la llamada del complemento. Podemos escribir fácilmente una función para ordenar las psudeo-clasificaciones:
1 |
$('ul.rating li').datasort({datatype: function(a, b) { |
2 |
var o = { |
3 |
a : $(a).text(), |
4 |
b : $(b).text() |
5 |
}
|
6 |
for (var e in o) { |
7 |
o[e] = o[e].replace(/poor/i, 0) |
8 |
.replace(/satisfactory/i, 1) |
9 |
.replace(/good/i, 2) |
10 |
.replace(/excellent/i, 3); |
11 |
}
|
12 |
return o.a - o.b; |
13 |
}
|
14 |
});
|
Esto utiliza las expresiones regulares más simples posibles para ordenar una lista como esta:
1 |
<ul class="rating"> |
2 |
<li>Good</li> |
3 |
<li>Excellent</li> |
4 |
<li>Poor</li> |
5 |
<li>Satisfactory</li> |
6 |
</ul>
|
¡En resumen!
Ahora lo sabes: ordenar valores en JavaScript realmente no es tan difícil como podrías haber pensado. Puedes imaginar que esto sea útil para ordenar una tabla, con algo como esto:
1 |
$('table#myTable thead th').toggle(
|
2 |
function() {
|
3 |
var $this = $(this); |
4 |
$('table#myTable tbody tr').datasort({
|
5 |
datatype: $this.attr('rel'),
|
6 |
sortElement: 'td.' + $this.attr('class')
|
7 |
}); |
8 |
}, |
9 |
function() {
|
10 |
var $this = $(this); |
11 |
$('table#myTable tbody tr').datasort({
|
12 |
datatype: $this.attr('rel'),
|
13 |
sortElement: 'td.' + $this.attr('class'),
|
14 |
reverse: true |
15 |
}); |
16 |
} |
17 |
); |
(¡Trata de reemplazar el código jQuery para la tabla en el primer ejemplo con esto!)
Por supuesto, podríamos mejorar mucho este plugin; por ejemplo, podríamos hacer que verifique el atributo rel para un tipo de dato si no se proporciona uno como parámetro, y el valor predeterminado es alfa si no hay rel. Pero eso es aparte del ordenamiento .
En resumen, para ordenar con JavaScript, seguimos estos pasos:
- Determinar los diferentes formatos que deseamos ordenar.
- Decidir qué formato queremos ordenar.
- Ordenar la matriz de elementos con el método sort(), pasando una función que convertirá los dos elementos al formato deseado antes de compararlos.
¿Tienes un tipo de dato para agregar a nuestro complemento? ¿Tienes una mejor manera de clasificar uno de estos? ¡Vamos a leerlo en los comentarios!
- Síguenos en Twitter o suscríbete a Tuts+ RSS Feed para obtener los mejores tutoriales de desarrollo web.





