Litipk

Litipk RobotEste año el Concurso Universitario de Software Libre vuelve a la carga (esta es ya su sexta edición). Yo me presento por enésima vez, a ver si por fin consigo acabar lo que empiezo :) (sí, quiero acabar, no ganar).

El proyecto que presentaré se llama Litipk, y a mi entender será mucho más modesto y humilde que las barbaridades que "intenté" en las otras ediciones del concurso. Evidentemente eso hace que tenga menos posibilidades de ganar debido a la falta de innovación, pero mi gran propósito es ofrecer algo a la comunidad que pueda empezar a resultar útil a corto plazo.

¿Qué "será" Litipk?

Litipk "será" una aplicación web dirigida a facilitar la búsqueda, catalogación y compartición de material didáctico online, haciendo especial incapié en el aspecto social y comunitario. Mi deseo es conseguir que la gente se ayude mutuamente a la hora de intentar aprender :) .

¿Por qué Litipk?

Desde hace tiempo la red se está llenando de cursos, tutoriales, vídeos, libros y muchos otros recursos didácticos o formativos. Quien quiere aprender puede hacerlo sin demasiados problemas, pero eso sí, probablemente perderá un tiempo precioso buscando material, tiempo que podría ahorrarse si facilitamos un poco las cosas :) .

Por otro lado, hay un factor importantísimo que me ha motivado a decidirme por este proyecto: la crisis económica. Cada día vemos más recortes en el sector de la educación, ya sea en educación primaria, secundaria obligatoria, bachillerato, formación profesional o las mismas universidades.

Hay muchos potenciales estudiantes que no han podido encontrar plaza allí donde creían oportuno formarse, y otros simplemente no tienen la posibilidad económica de costearse los estudios. Y aunque eso ya debería ser suficiente, pues la educación y la formación tienen valor por sí mismas, es importante destacar también que la economía depende fuertemente de la formación de la ciudadanía. Es por eso que pensé en aportar mi pequeño granito de arena.

Algunos apuntes más

Litipk será desarrollado en CoffeeScript , haciendo uso de NodeJS y una buena lista de librerías y microframeworks libres. Tengo que admitir que a nivel técnico no es una decisión demasiado acertada, es cierto que puedo conseguir resultados espectaculares en cuanto a rendimiento, pero también lo es que trabajaré el doble para conseguir lo que podría hacer con Symfony2 o CakePHP. Sea como sea mi decisión está tomada, y lo que me ha motivado a tomar este rumbo es mi tendencia a probar cosas nuevas :) .

Respecto al blog, esta vez he decidido no crear un blog aparte para el proyecto. Todas las entradas estarán en mi blog personal, Viricmind Labs, pero bajo la etiqueta Litipk. Lo hago así para evitar dispersar demasiado mi atención y poder controlar mejor el histórico de mi trabajo.

Para finalizar, quiero decir también que Litipk ya tiene algo parecido a un logotipo :) , se trata del robot que podéis ver en este mismo artículo. El dibujo es obra de Cristina Pérez (aunque yo la llamo Pistacho), y está licenciada bajo Creative Commons 3.0 By-Sa .

Añadir filtro de búsqueda en una tabla con jQuery

Gnome System Search IconPara completar el anterior artículo que escribí sobre ordenación de tablas con jQuery [1] hoy os hablaré sobre la creación de un filtro de búsqueda para nuestras tablas, algo prácticamente imprescindible hoy en día en multitud de aplicaciones de gestión que presentan parte de sus datos de forma tabular.

El código que veréis a continuación es bastante sencillo, no permite búsquedas con operadores lógicos (AND, OR, NOT), ni que se restrinjan a columnas concretas, sin embargo sí que aprovecha toda la potencia de las expresiones regulares para realizar búsquedas relativamente complejas. Para mi gusto es más importante la característica de los operadores lógicos y las restricciones a columnas concretas que la posibilidad de usar expresiones regulares, pero creo que no vale la pena complicar el artículo por algo que no tiene demasiada miga a nivel técnico.

El código es tal que así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function setup_searchable (selector) {
	var search_input = $("#"+selector+"-searcher");
 
	search_input.keyup ((function () {
		var tbody = $("#"+selector+"-table").find('tbody');
		var hidden_rows = tbody.find('tr');
 
		var searcher = function () {
			var search_text = search_input.val().toUpperCase();
 
			tbody.html('');
 
			var shown_rows = hidden_rows.filter (function () {
				var re = new RegExp (search_text),
					 matched = false;
 
				$(this).find('td').each(function (i2) {
					matched = matched || ($(this).text().toUpperCase().match(re) != null);
				});
 
				return matched;
			});
 
			tbody.append (shown_rows);
		};
 
		return searcher;
	})());
}

En este ejemplo trabajamos con dos elementos básicos, un elemento input de tipo texto y una tabla. El código asume que el selector se referirá a su identificador único id, y que los respectivos ids tendrán una estructura del tipo selector-searcher para el elemento input, y selector-table para la tabla.

Sobre el código, puede ser interesante notar que uso el evento keyup en vez del evento change, esto permite que la tabla se actualice en tiempo real, en contraposición a lo que sucedería con change, que necesita que apretemos la tecla Enter y cambiemos o cambiemos el foco de nuestro cursor. Otro detalle importante es que guardamos en memoria la totalidad de las filas antes de efectuar el filtrado, de modo que evitamos perderlas, y cuando cambiamos los criterios de búsqueda no tenemos que recargar ningún dato desde el servidor. Este último hecho implica que conviene llamar a la función setup_searchable cada vez que recarguemos la tabla con nuevos datos desde el servidor, pues de lo contrario nuestro buscador trabajará con datos "antiguos".

Algunas mejoras obvias para este código aparte de las mencionadas anteriormente serían las siguientes:

  • Posibilidad de trabajar con selectores más generales (lo que puede permitir, por ejemplo, tener dos campos de búsqueda para una misma tabla, uno en la cabecera y otro en el pie).
  • Añadir un parámetro callback que permita actualizar ciertos datos no mostrados en la tabla en función de lo que haya filtrado nuestra búsqueda. Un ejemplo muy simple sería la actualización de un contador, o en casos más complejos podríamos estar tratando datos como totales, medias, varianzas, etc.

Espero que esto le resulte útil a alguien, agradeceré cualquier comentario y propuesta de mejora, saludos! :)

  1. Ordenación de elementos del DOM con Javascript y jQuery

Ordenación de elementos del DOM con Javascript y jQuery

Shellsort gráfico (from Wikipedia)Hace unos días tuve que lidiar con un problema común en el diseño de aplicaciones web: ordenar bajo demanda las filas de una tabla según el valor de una columna concreta. Evidentemente lo primero que hice fue buscar por Internet alguna solución que me permitiera no tener que programar demasiado... y tengo que decir que lo encontré, pero por pequeños detalles nada de eso me sirvió.

Así que en este artículo explicaré el código que acabé picando yo por mi cuenta. Lo que he escrito depende de jQuery, aunque en realidad se puede sustituir por su "subconjunto" Sizzle, o incluso por la microbiblioteca Zepto.JS (que tiene una sintaxis compatible con la de jQuery).

Vayamos pues al código directamente, he creado un fichero llamado tablesorter.js en el que está todo el código necesario, salvo la llamada a una función, que se hace allí donde nos convenga :) .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function row_comparer (row1, row2, index) {
	var r1 = $(row1).find('td').filter(function(){
                        return $(this).index() === index;
        }).text().toUpperCase();
	var r2 = $(row2).find('td').filter(function(){
                        return $(this).index() === index;
        }).text().toUpperCase();
 
	return  (r1 > r2)? 1:
	       ((r1 < r2)?-1:0);
}
 
function shsort (list, comparer, order) { // Shell sort (with Marcin Ciura's gaps)
	var gaps = [701, 301, 132, 57, 23, 10, 4, 1],
	    s = list.length,
	    t;
 
	for (var k=0; k<8; k++) { // 8 is gaps size
		for (var g = gaps[k], i=g; i<s; i++) {
			t = list[i];
			for (var j = i; j >= g && order*comparer(list[j-g], t) == 1; j-= g) {
				list[j] = list[j - g];
			}
			list[j] = t;
		}
	}
}
 
function sort_table (table, index, order) {
	var tbody = table.find ('tbody');
	var rows = tbody.find('tr');
 
	var comparer = function (row1, row2) {
		return row_comparer (row1, row2, index);
	}
 
	shsort (rows, comparer, order);
 
	tbody.html('');
	tbody.append (rows);
}
 
function setup_sortable (selector) {
	$(selector).each (function (i1) {
		$(this).find('th').each (function (i2) {
			var order = 1;
 
			$(this).click ( function () {
				sort_table ($(this).closest('table'), i2, order);
 
				order *= -1;
			})
		});
	});
}

Supongo que algunos de los que leéis esto preferiríais que hubiese creado un plugin para jQuery, siento decir que no es así. Eso no es por ninguna razón en particular, simplemente pasa que todavía no me he tomado la molestia de aprender como se hace, y no sé si es complicado o no, supongo que en breve habrá una versión en forma de plugin para jQuery :) .

En cuanto a como usarlo, la función setup_sortable debe ser llamada utilizando como parámetro un selector para las tablas a las que queramos aplicar ordenación "automática" (con este script las tablas se ordenan cuando se hace click en los elementos th de la tabla, básicamente las cabeceras). En mi caso añadí la clase sortable a todas las tablas que quería añadir esta característica y usé el selector ".sortable".

En cuanto al código, supongo que es destacable que hago un uso intensivo de las funciones anónimas y las clausuras, aunque no es nada que deba sorprender a nadie. En mi humilde opinión, lo más interesante es el algoritmo de ordenación, un "simple" Shell sort (basado en Insertion Sort).

Escogí este algoritmo por las siguientes razones:

  • Es estable, es decir, se mantiene el orden relativo entre elementos con la misma clave (el valor que se usa para realizar las comparaciones) que había en la lista antes de la ordenación. Esto es útil cuando queremos ordenar por distintos criterios a la vez.
  • Es de fácil implementación, solo tenéis que ver el código. Aquí puede parecer un poco más complicado porque uso la función comparer en vez del típico operador de comparación, pero no es una gran dificultad.
  • No usa recursión, ni pilas: los requisitos de memoria son pequeños y constantes.
  • Dependiendo de la implementación (y en particular con esta se consigue), se tiene que para ordenar una lista se deben realizar solo una cantidad de operaciones del orden de O(n·log²(n)).
  • Se trata de una rara avis, le cogí cariño. Aunque no se trata de un algoritmo destacable por su rendimiento, resulta que es tremendamente complicado a nivel teórico. Tanto que su velocidad depende fuertemente de una serie de parámetros de los que casi no se sabe nada a día de hoy. Yo he usado los de Marcin Ciura, que hasta donde yo sé, son los mejores encontrados hasta el momento.

Bien, hasta aquí el apunte de hoy :) . Espero que os pueda resultar útil, agradeceré cualquier comentario.. y si a alguien le interesa, estoy abierto a aceptar ayuda en la jqueryzación de esta minilibrería. Saludos!