martes, 22 de enero de 2013

JQuery: multi listas ordenables

A continuación veremos como implementar una lista de selección, donde podemos elegir varios elementos y pasarlos a otra lista. En esta nueva lista, podremos ordenar los elementos.

Vamos a realizar la generación con HAML y los scripts con JQuery, y el relleno de la lista se hará mediante código ruby, como vimos en el post de cómo crear combos de selección.

La parte que creará las listas será la siguiente:

= select_tag :selectfrom, options_for_select(@lista_elementos.map{|x| [x[:nombre], x[:codigo]]}), :multiple => true, :size => 15

.buttons_containers
  .buttons
    %button#btn-add{:type => "button"}
      = '→'.html_safe
    %br
    %button#btn-remove{:type => "button"}
      = '←'.html_safe

= select_tag :selectto, nil, :multiple => true

.buttons_containers
  .buttons
    %button#btn-up{:type => "button"}
      = '↑'.html_safe
    %br
    %button#btn-down{:type => "button"}
      = '↓'.html_safe


Esto creará las listas, que dando los estilos adecuados, no quedarán así:



Ahora tenemos que meter la funcionalidad, para poder pasar los elementos de una lista a la otra con los botones del medio:

  $('#btn-add').click(function(){
    $('#selectfrom option:selected').each( function() {
      $('#selectto').append("");
      $(this).remove();
    });
  });
  $('#btn-remove').click(function(){
    $('#selectto option:selected').each( function() {
      $('#selectfrom').append("");
      $(this).remove();
    });
  });


Así pasaremos de una lista a otra todas las opciones que estén seleccionadas. Para introducir la ordenación de la lista de la derecha:

  $('#btn-up').bind('click', function() {
    $('#selectto option:selected').each( function() {
      var newPos = $('#selectto option').index(this) - 1;
      if (newPos > -1) {
        $('#selectto option').eq(newPos).before("");
        $(this).remove();
      }
    });
  });

  $('#btn-down').bind('click', function() {
    var countOptions = $('#selectto option').size();
    $('#selectto option:selected').each( function() {
      var newPos = $('#selectto option').index(this) + 1;
      if (newPos < countOptions) {
        $('#selectto option').eq(newPos).after("");
        $(this).remove();
      }
    });
  });


Y así podremos subir o bajar elementos:



Si finalmente queremos tratar los elementos seleccionados para realizar alguna acción, como por ejemplo enviar a otra página, lo podemos hacer con un bucle de JQuery:

  $('#btnsumbit').click(function(){
    $('#selectto option').each(function(index, value){
      alert($(this).val());
    })
  });

Este bucle each de JQuery recorre todos los elementos del select final.

lunes, 21 de enero de 2013

JAVA: distintos componentes en JPanel

A continuación vamos a ver la manera de sacar, fácil y rápidamente, diversos componentes en un JPanel, que saldrán a modo de diálogo. Vamos a usar instancias de la clase JOptionPane.

Cuadro de texto


Para mostrar un cuadro de texto, y almacenar el input en una variable, simplemente haremos:

String name = JOptionPane.showInputDialog(new JFrame(),
    "Element Name", "");


Y en la variable name tendremos el texto introducido.

Opciones tipo Si/No/Cancelar


En este caso, damos al usuario dos o tres opciones. Podrá elegir una pulsando el botón adecuado:

Object[] options = {"opcion A", "opcion B", "opcion C"};
int opt = JOptionPane.showOptionDialog(new JFrame(), "Type", "Select option type",
  JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
  options, options[1]);

El entero que recibimos en opt será el índice de la opción seleccionada.

Spinner numérico


En esta ocasión, el usuario verá un spinner donde podrá elegir un valor numérico:


SpinnerModel sm = new SpinnerNumberModel(0,0,360, 25);
JSpinner jsp = new JSpinner(sm);
JOptionPane.showOptionDialog(null, jsp, "Number of cars per hour", JOptionPane.CLOSED_OPTION, JOptionPane.CLOSED_OPTION, null, null, null);
El valor retornado lo tendremos en sm.getValue();



Lista de opciones desplegable

Aquí, mostramos un desplegable con las opciones que puede seleccionar el usuario:


Object[] speedOptions = {"30","50","70","90","100","120"};

Object seleccion = JOptionPane.showInputDialog(
  new JFrame(),
     "Select max speed",
     "Options",
     JOptionPane.QUESTION_MESSAGE,
     null,  
     speedOptions, 
     speedOptions[4]);


En el objeto de retorno seleccion obtendremos el valor del objeto del Array seleccionado.



Más Información en chuwiki: JOptionPane y diálogos modales.

martes, 15 de enero de 2013

Ruby: Funciones en el valor de una Hash

Una Hash esta formada por pares clave-valor. en ocasiones, necesitaremos quealguno de esos valores sea una llamada a un método. Pongamos, por ejemplo, Time.now.

Si creamos la hash directamente con la llamada....

 hTime = {:a => Time.now}
 => {:a=>2013-01-15 10:45:58 +0100}

Cada vez que volvamos a acceder al elemento :a obtendremos lo mismo. No repite la llamada al método:

 
 hTime[:a]
 => 2013-01-15 10:45:58 +0100

La solución para  cuando queremos que el método se ejecute cada vez, es introducir una función lambda, con lo que podremos ejecutar el contenido del valor de la hash cuando queramos:

 hTime = {:a => lambda {Time.now}}
 => {:a=>#} 

Ahora, al hacer la llamada con la ejecución del procedimiento lambda:

 
hTime[:a].call
 => 2013-01-15 10:54:11 +0100

Si la repetimos, comprobamos que nuestra función se está llamando de nuevo:
 
hTime[:a].call
 => 2013-01-15 10:55:19 +0100

Un mini-ejemplo aplicado. Tenemos una hash. Uno de sus miembros es un array de numeros. El otro
miembro lo usaremos para saber si la suma del array es par.

 
hNumeros = {:nums => [1,2,3], :total_par => lambda{ hNumeros[:nums].sum.even?}}

hNumeros[:total_par].call
 => true 

Modificamos el array...
 
 hNumeros[:nums] << 7
 => [1, 2, 3, 7] 

hNumeros[:total_par].call
 => false 

Y de esta forma, realizamos la llamada a métodos y funciones desde el valor de una Hash

viernes, 11 de enero de 2013

Ordenar Arrays en Ruby

Para ordenar un Array en Ruby, hay varios métodos.

Si tenemos un array normal, de enteros, por ejemplo:

Con el método sort se realiza el orden por defecto:

[1,2,56,8,6, 12].sort
 => [1, 2, 6, 8, 12, 56]

Si queremos otro orden, hay que incluir un bloque a la llamada. Por ejemplo, primero los impares:

 [1, 5, 7, 2, 56, 8, 3, 6, 12].sort{|x,y| y%2 <=> x%2}
 => [1, 5, 7, 3, 56, 8, 2, 6, 12] 


En este caso, salen primero los impares y luego los pares. Pongamos que queremos hacer esto, pero además, que salgan los números ordenados. Es decir, primero por par/impar, y luego por tamaño. Para ordenar por varios campos:

[1, 5, 7, 2, 56, 8, 3, 6, 12].sort{|x,y| [x%2, x] <=> [y%2, y]}
 => [2, 6, 8, 12, 56, 1, 3, 5, 7] 

En el caso de que en lugar de un array normal como el anterior, tuviéramos un array de objetos, de hash o de cualquier tipo de dato, la operación es similar:

[{:a=>1, :b=>"a"}, {:a=>11, :b=>"f"}, {:a=>34, :b=>"ah"}, {:a=>154, :b=>"dfg"}, {:a=>323, :b=>"tre"}, {:a=>16, :b=>"mh"}].sort{|x,y| x[:a] <=> y[:a]}
 => [{:a=>1, :b=>"a"}, {:a=>11, :b=>"f"}, {:a=>16, :b=>"mh"}, {:a=>34, :b=>"ah"}, {:a=>154, :b=>"dfg"}, {:a=>323, :b=>"tre"}] 


De la misma manera, con varios campos:

 [{:a=>1, :b=>"a"}, {:a=>11, :b=>"f"}, {:a=>34, :b=>"ah"}, {:a=>154, :b=>"dfg"}, {:a=>323, :b=>"tre"}, {:a=>16, :b=>"mh"}].sort{|x,y| [y[:b], x[:a]] <=> [x[:b], y[:a]]}
 => [{:a=>323, :b=>"tre"}, {:a=>16, :b=>"mh"}, {:a=>11, :b=>"f"}, {:a=>154, :b=>"dfg"}, {:a=>34, :b=>"ah"}, {:a=>1, :b=>"a"}]

martes, 8 de enero de 2013

Crear un combo de selección en un formulario de Rails

Dentro de la poca complejidad que tiene hacer un formulario en Rails, quizá el combo de selección puede que sea el único con algo más de dificultad.

El resultado que queremos obtener es algo como:

<select id="persona" name="persona">
 <option>1</option> 
 <option>2</option>
 <option>3</option>
 <option>4</option>
</select>

Para esto, las opciones que tenemos son:

Meter el option directamente

Es decir, Html puro con el conjunto de los option, en una cadena, y escapándolo con html_safe. Funcionará, aunque en mi opinión no es una opción muy elegante.

Obtener una colección de opciones del modelo

Si el dato que queremos recuperar en el obtión es un objeto en sí mismo, lo correcto es formar a partir de este objeto el option.
select_tag "personas", options_from_collection_for_select(@personas, "id", "nombre")

De esta manera crearemos un 'select' con todos los objetos que haya en @personas, mostrando el nombre para la selección, y enviando el ID cuando se haga el submit.

Array con los valores


Para el caso en que simplemente queramos enviar un array con valores:

select_tag :personas, options_for_select((1..10).to_a, nil)


El primer parámetro es el array de valores. Si es un array donde cada elemento , a su vez, es un array de dos elementos, el segundo es el valor, y el primero el que se muestra.

El segundo parámetro determina el valor que estará seleccionado por defecto.

viernes, 4 de enero de 2013

CookieOverflow y las sesiones de Rails

Si al acceder a nuestra aplicación en Rails, nos da el error:

ActionDispatch::Cookies::CookieOverflow error

Es muy posible que se deba a que almacenamos en sesión más datos de los permitidos (4Kb) . Esto pasa si usamos la cookie_store de Rails.

Si vamos a necesitar almacenar datos en la sesión de usuario, lo mejor es hacerlo en base de datos. La modificación es muy sencilla. Se crean las tablas de sesión con el generador de Rails y se cambia el
session_store para que use active_record_store.

Ejecutamos:
rake db:sessions:create
rake db:migrate
Y en el fichero 'config/initializers/session_store.rb':

MyApp::Application.config.session_store :cookie_store, key: '_myapp_session'

Así nos evitaremos estos problemas de espacio, ya que generalmente hay bastantes cosas que conviene tenerlas en sesión para una mayor agilidad en la aplicación.

miércoles, 2 de enero de 2013

Deshacer una migración en ruby

En ocasiones, cuando realizamos una migración en Ruby, nos damos cuenta de que nos hemos dejado algo. Un tipo de dato mal, un nombre que no es el adecuado, ...

Para deshacer el comando migrate, simplemente ejecutamos:

rake db:rollback

Y desharemos la última migración realizada.

Un detalle importante, que guarda relación con esto y que puede llevar a errores en algunos casos:

Cuando una migración tiene varias partes, como por ejemplo, cambios en varias tablas, y hay un error al realizar una de ellas, no se guardará la ejecución del comando migrate.

Por ejemplo, en una migración cambiamos primero el nombre de un campo en una tabla y en segundo lugar el tipo de dato del campo de otra tabla. Pongamos que, por error, hemos escrito mal el nombre del segundo campo: en este caso, modificaremos el nombre del campo de la primera tabla, y al realizar el cambio del tipo de dato nos dará un error, pues este campo no existe.

Lo típico será ir al fichero de migración y corregir el nombre mal escrito, para ejecutar de nuevo la migración. Nos volverá a dar error, pues el primer campo si fue modificado, y el nombre ya no coincide con el de la migración.
En este punto, no servirá un rollback!, puesto que la migración no consta como realizada. Las posibles soluciones son: modificar directamente las tablas en base de datos o bien modificar momentáneamente la migración para que no haga la primera parte.
Para prevenir esto, lo correcto sería no agrupar migraciones que se vayan a realizar en varios pasos en el mismo fichero.