ProgramandooIntentándolo

Tutorial Less: 10. Mixins recursivos (Bucles en Less)

Logo less

En Less se puede hacer la llamada a un mixin desde dentro de otro y claro si el mixin al que llamamos es el mismo desde el que se hace la llamada pues nos encontramos con que tenemos una llamada recursiva y de este modo podemos crear bucles para generar clases en las que solo cambian los valores de algunos atributos como por ejemplo los grids donde lo que cambia es el ancho de las columnas o para crear clases para botones, avisos, etc. donde lo que cambiamos son los colores por ejemplo.

Parece claro que la posibilidad de crear bucles en Less con los mixins recursivos nos puede ayudar a conseguir un código mucho más compacto y reutilizable, pero como no todo puede ser perfecto también vamos a hacerlo un poco más complejo, así que tampoco hay que querer caer en el error de querer generar cualquier cosa usando bucles.

Para ilustrar el funcionamiento de los mixins recursivos vamos a ver un par de ejemplos sencillos con los que nos podremos hacer una buena idea de que cosas podemos hacer.

Crear un Grid con Less

Las rejillas o grids son una de las bases de gran parte de los layouts de las webs porque permiten organizar fácilmente el contenido usando “columnas”, probablemente al pensar en grids te venga a la cabeza Bootstrap, vamos que no vamos a inventar la rueda pero sabiendo cómo se hace vamos a poder evitar usar frameworks completos para usar una sola o un par de características.

Para crear un bucle que nos genere todas las clases que necesitemos necesitamos un mixin con 2 características, la primera es que se llame a sí mismo para que se genere la recursión y la segunda es que tenga alguna condición que termine el bucle.

Para crear un grid está claro que el límite es el número de columnas que queremos que tenga, y que queremos crear una clase desde 1 hasta ese número de columnas que tengan el ancho correspondiente.

Por lo tanto lo que vamos a hacer es un mixin con 2 parámetros, el número de columnas y un índice que nos va a servir para ir generando cada una de los anchos de columna posibles, y como no tiene sentido empezar en un número distinto de 1 lo ponemos directamente como valor por defecto para el índice. Y cada vez que hacemos una llamada recursiva aumentamos el índice para generar el siguiente tamaño y no quedarnos en un bucle infinito.


.crear-grid(@columnas; @i: 1) when (@i =< @columnas) {
  .col-@{i} {
    width: (percentage(@i / @columnas));
  }
  
  .crear-grid(@columnas; (@i + 1));
}  

.crear-grid(12);

Y ya tenemos un mixin recursivo para generar las clases de un grid responsive con el número de columnas que le pasemos como parámetro.

El contenido del mixin no tiene mucha ciencia, utilizamos el índice para nombrar la clase y obviamente para calcular el porcentaje que le corresponde y luego hacemos la llamada recursiva. La llamada recursiva la hacemos al final para que se generen las clases en orden 1,2,3,… pero como el orden de las clases no tiene importancia no habría problema alguno en hacer la recursión al principio y la única diferencia sería que se generarían en orden inverso 12,11,10,…

El CSS resultante de nuestro mixin es este:


.col-1 {
  width: 8.33333333%;
}
.col-2 {
  width: 16.66666667%;
}
.col-3 {
  width: 25%;
}
.col-4 {
  width: 33.33333333%;
}
.col-5 {
  width: 41.66666667%;
}
.col-6 {
  width: 50%;
}
.col-7 {
  width: 58.33333333%;
}
.col-8 {
  width: 66.66666667%;
}
.col-9 {
  width: 75%;
}
.col-10 {
  width: 83.33333333%;
}
.col-11 {
  width: 91.66666667%;
}
.col-12 {
  width: 100%;
}

Esta claro que nos hemos ahorrado escribir unas cuantas líneas y unos cuantos cálculos, pero la principal ventaja que tenemos es que si ahora queremos que tenga 5 columnas solo tenemos que cambiar la llamada .crear-grid(5); y ya lo tenemos.

Siguiendo con los grids vamos a modificar el anterior ejemplo para poder crear una rejilla en el que el ancho total es fijo y le añadimos un margen proporcional al tamaño total.


.crear-grid-fijo(@ancho; @columnas; @i: 1) when (@i =< @columnas) {
  .col-f-@{i} {
    width: ((@i / @columnas) * @ancho) - ((@ancho / @columnas) * 0.1);
    margin-left: (@ancho / @columnas) * 0.05;
    margin-right:(@ancho / @columnas) * 0.05;
  }

  .crear-grid-fijo(@ancho; @columnas; (@i + 1));
}  

.crear-grid-fijo(1200px, 6);  

La estructura es la misma pero en este caso tenemos un parámetro adicional, el ancho que lo utilizaremos para calcular los diferentes anchos de columna y los márgenes que como no sabemos de antemano cual va a ser el tamaño pues por ejemplo podemos hacer que el 10% (también podríamos añadir un parámetros más y hacerlo parametrizable) del ancho de una columna se reserve para el margen (la mitad por cada lado) y si, los márgenes se calculan sobre la columna de menor tamaño y son iguales para todas para que se mantenga ordenado.


.col-f-1 {
  width: 180px;
  margin-left: 10px;
  margin-right: 10px;
}
.col-f-2 {
  width: 380px;
  margin-left: 10px;
  margin-right: 10px;
}
.col-f-3 {
  width: 580px;
  margin-left: 10px;
  margin-right: 10px;
}
.col-f-4 {
  width: 780px;
  margin-left: 10px;
  margin-right: 10px;
}
.col-f-5 {
  width: 980px;
  margin-left: 10px;
  margin-right: 10px;
}
.col-f-6 {
  width: 1180px;
  margin-left: 10px;
  margin-right: 10px;
} 

Cuanto más complejos vamos haciendo los mixins más vemos la cantidad de trabajo que nos pueden ahorrar, y eso que esto no deja de ser un ejemplo básico.

Y aquí está el resultado de estos 2 ejemplos añadiéndoles unas pocas líneas más para que el resultado sea “presentable” y así le damos una vueltita más y reutilizamos el estilo para ambos tipos de grids con un :extend() dentro de los bucles.

See the Pen Ejemplo de grid creado con less by Ivan Salas (@isc7) on CodePen.

Crear botones de colores con Less

Con los ejemplos del grid hemos hecho el bucle sobre un índice para ir generando los diferentes tamaños de columna, pero en lugar de usar el índice directamente en el bucle lo podemos usar para recorrer una lista.

En este ejemplo vamos a utilizar una lista de colores para generar una clase para cada color de botón, pero la misma idea la podríamos aplicar para crear los distintos tipos de avisos o cualquier otra cosa que queramos crear en base a un conjunto de valores preestablecidos.

Para manejar listas en variables vamos a necesitar a necesitar utilizar las siguientes funciones:

  • length: la típica función para calcular el tamaño de la lista. ej. el resultado de length(a, b, c); es 3.
  • extract: devuelve el elemento en la posición indicada de la lista (el índice empieza en 1). ej. extract(a, b, c; 2); retorna b.

Con esto y con lo que hemos visto en los ejemplos anteriores es sencillo crear nuestro mixin para generar botones de colores, solo necesitamos 2 listas una para los colores y otra para los nombres de las clases o bien una sola lista en la que metamos tanto el color como el nombre correspondiente de la clase (separando el color del nombre con un espacio y el conjunto con una coma), para el ejemplo voy a optar por la primera opción pero dejo comentada la forma de hacerlo con una solo lista.


// Lista de colores y de nombres para las clases
@colores: #0275d8, #5cb85c, #5bc0de, #f0ad4e, #d9534f;
@nombres: primary, success, info, warning, danger;

// Usando una sola lista
// @colores: #0275d8 primary, #5cb85c success, #5bc0de info, #f0ad4e warning, #d9534f danger;

.boton {
  display: inline-block;
  padding: 6px 12px;
  margin-bottom: 0;
  font-size: 14px;
  font-weight: 400;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  border: 1px solid transparent;
  border-radius: 4px;
}

// Mixin generador de botones
.botones-colores(@nombres; @colores; @i: 1) when (@i =< length(@colores)) {
  
  @color: extract(@colores, @i); 
  @nombre: extract(@nombres, @i);

  // Para usar solo una lista
  //@color: extract(extract(@colores, @i), 1);
  //@nombres: extract(extract(@colores, @i), 2);  
  
  .boton-@{nombre}:extend(.boton) {
    border-color: darken(@color, 10%);
    color: #fff;
    background-color: @color;
      
    &:hover {
      border-color: darken(@color, 50%);
      background-color: darken(@color, 10%);
      color: lighten(@color, 20%);
    }
  }
  
  .boton-outline-@{nombre}:extend(.boton) {
    border-color: @color;
    color: @color;
    background-color: inherit;
      
    &:hover {
      background-color: @color;
      color: #fff;
    }
  }

  .botones-colores(@nombres; @colores; (@i + 1));
}

// Creamos los botones con la lista de nombres y colores
.botones-colores(@nombres; @colores);

Como puedes ver aunque en este caso estamos generando a la vez dos clases de botones de cada color y las clases tienen un poco más de cuerpo que en los ejemplos del grid, se puede ver que estructura es la misma, tenemos la llamada recursiva modificando el valor del índice y la condición de parada que en este caso se calcula mediante una función pero no cambia la idea.


.boton,
.boton-primary,
.boton-outline-primary,
.boton-success,
.boton-outline-success,
.boton-info,
.boton-outline-info,
.boton-warning,
.boton-outline-warning,
.boton-danger,
.boton-outline-danger {
  display: inline-block;
  padding: 6px 12px;
  margin-bottom: 0;
  font-size: 14px;
  font-weight: 400;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  border: 1px solid transparent;
  border-radius: 4px;
}
.boton-primary {
  border-color: #025aa5;
  color: #fff;
  background-color: #0275d8;
}
.boton-primary:hover {
  border-color: #000000;
  background-color: #025aa5;
  color: #43a7fd;
}
.boton-outline-primary {
  border-color: #0275d8;
  color: #0275d8;
  background-color: inherit;
}
.boton-outline-primary:hover {
  background-color: #0275d8;
  color: #fff;
}
.boton-success {
  border-color: #449d44;
  color: #fff;
  background-color: #5cb85c;
}
.boton-success:hover {
  border-color: #060f06;
  background-color: #449d44;
  color: #a3d7a3;
}
.boton-outline-success {
  border-color: #5cb85c;
  color: #5cb85c;
  background-color: inherit;
}
.boton-outline-success:hover {
  background-color: #5cb85c;
  color: #fff;
}
.boton-info {
  border-color: #31b0d5;
  color: #fff;
  background-color: #5bc0de;
}
.boton-info:hover {
  border-color: #0a2730;
  background-color: #31b0d5;
  color: #b0e1ef;
}
.boton-outline-info {
  border-color: #5bc0de;
  color: #5bc0de;
  background-color: inherit;
}
.boton-outline-info:hover {
  background-color: #5bc0de;
  color: #fff;
}
.boton-warning {
  border-color: #ec971f;
  color: #fff;
  background-color: #f0ad4e;
}
.boton-warning:hover {
  border-color: #3a2405;
  background-color: #ec971f;
  color: #f8d9ac;
}
.boton-outline-warning {
  border-color: #f0ad4e;
  color: #f0ad4e;
  background-color: inherit;
}
.boton-outline-warning:hover {
  background-color: #f0ad4e;
  color: #fff;
}
.boton-danger {
  border-color: #c9302c;
  color: #fff;
  background-color: #d9534f;
}
.boton-danger:hover {
  border-color: #220807;
  background-color: #c9302c;
  color: #eba5a3;
}
.boton-outline-danger {
  border-color: #d9534f;
  color: #d9534f;
  background-color: inherit;
}
.boton-outline-danger:hover {
  background-color: #d9534f;
  color: #fff;
}

El CSS que nos ahorramos escribir en este caso vuelve a ser considerable y eso que no están cubiertos todos los estados de los botones porque no aportan nada nuevo para el ejemplo, pero el mayor beneficio no es el código que nos ahorramos escribir si no la facilidad con la que podemos hacer cualquier cambio.

Con este ejemplo vemos como la idea de hacer bucles no se limita solo a los casos más obvios como los grids y que se puede utilizar para otras muchas cosas, en general cualquier caso en el que hacemos múltiples llamadas a un mixin es susceptible de convertirse en un bucle y así centralizarlo todo al máximo, porque es más fácil añadir y quitar colores de una variable que no tener que añadir y borrar unas cuantas llamadas al mismo mixin cada vez que queremos cambiar algo ¿no?, pero sin volverse locos…

Y para terminar el resultado de nuestro maravilloso generador de botones, jejeje.

See the Pen Generador de botones less by Ivan Salas (@isc7) on CodePen.