Mariano Guerra: Riak Core Tutorial Part 3: Ping Command

The content of this chapter is in the `01-template` branch.

https://gitlab.com/marianoguerra/tanodb/tree/01-template

This is part of a series, see the previous one at Riak Core Tutorial Part 2: Starting

How it Works

Let's see how ping works under the covers.

Its entry point and public API is the tanodb module, that means we have to look into tanodb.erl:

-module(tanodb).
-export([ping/0]).
-ignore_xref([ping/0]).

%% @doc Pings a random vnode to make sure communication is functional
ping() ->
    % argument to chash_key has to be a two item tuple, since it comes from riak
    % and the full key has a bucket, we use a contant in the bucket position
    % and a timestamp as key so we hit different vnodes on each call
    DocIdx = riak_core_util:chash_key({<<"ping">>, term_to_binary(os:timestamp())}),
    % ask for 1 vnode index to send this request to, change N to get more
    % vnodes, for example for replication
    N = 1,
    PrefList = riak_core_apl:get_primary_apl(DocIdx, N, tanodb),
    [{IndexNode, _Type}] = PrefList,
    riak_core_vnode_master:sync_spawn_command(IndexNode, ping, tanodb_vnode_master).
DocIdx = riak_core_util:chash_key({<<"ping">>, term_to_binary(os:timestamp())}),

The line above hashes a key to decide to which vnode the call should go, a riak_core app has a fixed number of vnodes that are distributed across all the instances of your app's physical nodes, vnodes move from instance to instance when the number of instances change to balance the load and provide fault tolerance and scalability.

The call above will allow us to ask for vnodes that can handle that hashed key, let's run it in the app console to see what it does:

(tanodb@127.0.0.1)1> DocIdx = riak_core_util:chash_key({<<"ping">>, term_to_binary(os:timestamp())}).

<<126,9,218,77,97,108,38,92,0,155,160,26,161,3,200,87,134,213,167,168>>

We seem to get a binary back, in the next line we ask for a list of vnodes that can handle that hashed key:

PrefList = riak_core_apl:get_primary_apl(DocIdx, N, tanodb),

Let's run it to see what it does:

(tanodb@127.0.0.1)2> PrefList = riak_core_apl:get_primary_apl(DocIdx, 1, tanodb).

[{{730750818665451459101842416358141509827966271488, 'tanodb@127.0.0.1'},
     primary}]

We get a list with one tuple that has 3 items, a long number, something that looks like a host and an atom, let's try changing the number 1:

(tanodb@127.0.0.1)3> PrefList2 = riak_core_apl:get_primary_apl(DocIdx, 2, tanodb).

[{{730750818665451459101842416358141509827966271488,
   'tanodb@127.0.0.1'}, primary},
 {{753586781748746817198774991869333432010090217472,
   'tanodb@127.0.0.1'}, primary}]

Now we get two tuples, the first one is the same, so what this does is to return the number of vnodes that can handle the request from the hashed key by priority.

The first number is the vnode id, it's what we get on the ping response.

Next line just unpacks the pref list to get the vnode id and ignore the other part:

[{IndexNode, _Type}] = PrefList,

Finally we ask riak_core to call the ping command on the IndexNode we got back:

riak_core_vnode_master:sync_spawn_command(IndexNode, ping, tanodb_vnode_master).

Let's try it on the console:

(tanodb@127.0.0.1)5> [{IndexNode, _Type}] = PrefList.

[{{730750818665451459101842416358141509827966271488,
   'tanodb@127.0.0.1'}, primary}]

(tanodb@127.0.0.1)6> riak_core_vnode_master:sync_spawn_command(IndexNode, ping, tanodb_vnode_master).

{pong,730750818665451459101842416358141509827966271488}

You can see we get IndexNode back in the pong response, now let's try passing the second IndexNode:

(tanodb@127.0.0.1)7> [{IndexNode1, _Type1}, {IndexNode2, _Type2}] = PrefList2.

[{{730750818665451459101842416358141509827966271488,
   'tanodb@127.0.0.1'}, primary},
 {{753586781748746817198774991869333432010090217472,
   'tanodb@127.0.0.1'}, primary}]


(tanodb@127.0.0.1)9> riak_core_vnode_master:sync_spawn_command(IndexNode2, ping, tanodb_vnode_master).

{pong,753586781748746817198774991869333432010090217472}

We get the IndexNode2 back, that means that the request was sent to the second vnode instead of the first one.

But where does the command go?

Let's see the content of tanodb_vnode.erl (just the useful parts):

-module(tanodb_vnode).
-behaviour(riak_core_vnode).

-export([start_vnode/1,
         init/1,
         terminate/2,
         handle_command/3,
         is_empty/1,
         delete/1,
         handle_handoff_command/3,
         handoff_starting/2,
         handoff_cancelled/1,
         handoff_finished/2,
         handle_handoff_data/2,
         encode_handoff_item/2,
         handle_overload_command/3,
         handle_overload_info/2,
         handle_coverage/4,
         handle_exit/3]).

-record(state, {partition}).

%% API
start_vnode(I) ->
    riak_core_vnode_master:get_vnode_pid(I, ?MODULE).

init([Partition]) ->
    {ok, #state { partition=Partition }}.

%% Sample command: respond to a ping
handle_command(ping, _Sender, State) ->
    {reply, {pong, State#state.partition}, State};
handle_command(Message, _Sender, State) ->
    lager:warning("unhandled_command ~p", [Message]),
    {noreply, State}.

Let's go by parts, first we declare our module:

-module(tanodb_vnode).

We specify that we want to implement the riak_core_vnode behavior:

-behaviour(riak_core_vnode).

Behaviors in Erlang are like interfaces, a set of functions that a module must implement to satisfy the behaviour specification, you can read more in the Erlang documentation.

In this case riak_core defines a behavior with a set of functions we must implement to be a valid riak_core vnode, you can get an idea of the kind of functionality we need by looking at the exported functions:

-export([start_vnode/1,
         init/1,
         terminate/2,
         handle_command/3,
         is_empty/1,
         delete/1,
         handle_handoff_command/3,
         handoff_starting/2,
         handoff_cancelled/1,
         handoff_finished/2,
         handle_handoff_data/2,
         encode_handoff_item/2,
         handle_overload_command/3,
         handle_overload_info/2,
         handle_coverage/4,
         handle_exit/3]).

For the moment most of them have a "dummy" implementation where they just do the minimal amount of work to satisfy the behavior and not more, it's our job to change the default implementation to fit our needs.

We will have a record called state to keep info between callbacks, this is typical Erlang way of managing state so I won't cover it here:

-record(state, {partition}).

We implement the api to start the vnode:

%% API
start_vnode(I) ->
    riak_core_vnode_master:get_vnode_pid(I, ?MODULE).

Note that on init we store the Partition value on state so we can use it later, this is what I referred above as vnode id, it's the big number you saw before:

init([Partition]) ->
    {ok, #state { partition=Partition }}.

Now for the interesting part, here we have our ping command implementation, we match for ping in the Message position (the first argument):

handle_command(ping, _Sender, State) ->

Return a response with the second item in the tuple being the actual response that the caller will get where we reply with the atom pong and the partition number of this vnode, the last item in the tuple is the new state we want to have for this vnode, since we didn't change anything we pass the current value:

{reply, {pong, State#state.partition}, State};

We implement a catch all that will just log the unknown command and give no reply back:

handle_command(Message, _Sender, State) ->
    lager:warning("unhandled_command ~p", [Message]),
    {noreply, State}.

This is the roundtrip of a ping call, our task to add more commands will be:

  • Add a function on tanodb.erl that hides the internal work done to distribute the work
  • Add a new match on handle_command to match the command we added on tanodb.erl and provide a reply

Facundo Batista: Imanes-receta: La colección

Una tradición que ejecutamos con Moni en los primeros cinco cumpleaños de los peques fue, junto con algunas otras cositas, regalar como souvenir del festejo un imán para la heladera, cada uno conteniendo una receta distinta.

Como Male ya cumplió cinco este año, no vamos a hacer más de estos, entonces es tiempo de presentar aquí la colección completa :)

Las recetas son, en orden cronológico, las siguiente:

  • Pan Felipe
  • Masa para jugar
  • Galletitas de queso
  • Scons salados
  • Bizcochuelo casero
  • Torta de chocolate
  • Brownies en taza
  • Zo-zo-pita
  • Budín de mandarinas
  • Flan casero

Estas son miniaturas de los imanes, si hacen click en la imagen van a una galería donde se ven mucho mejor, por si les interesa hacer la receta, :)

Todos los imanes, todos

Facundo Batista: Bondiolita, paso a paso

Por clamor popular (?) les cuento cómo hacer la famosa bondiolita.

Aclaro, no soy yo el experto del tema, el maestro acá es Manu, y para más lujo pueden consultar el #ProyectoMondiola de la genia de Paulina. Pero bueno, esta es MI forma de hacerla...

Lo primero es conseguirse una bondiola de cerdo. Si no es súper-industrial-de-carnicería mejor, pero yo siempre las compré en la carnicería, no tengo otro lugar para conseguirlas. Si la consigo chiquita, mejor, tiene menos grasa. Pero si no, a no desesperar, a propósito en este post la hago partida al medio...

La bondiola enjuagada y lista para empezar

Enjuáguenla bien, y séquenla. Yo después de enjuagarla la dejo un rato al aire libre, y la termino secando con un par de servilletas.

Etapa final de secado

Después, el proceso de secado con sal. Agarren un tupper, le hacen una base de sal gruesa, ponen la bondiola y la tapan. Dicen de mezclarle una cucharadita de azúcar con la sal. Y Manu ahora está usando también sal de cura, un experto el tipo.

Poca, bastante y toda la sal

La dejan dos/tres días en algún lugar fresco y seco. Luego, le cambian la sal (y etcéteras), y la vuelven a dejar dos/tres días.

Al final de esta etapa, hay que lavar bien la bondiola bajo un chorro de agua, para sacarle cualquier excedente de sal. Y obvio que la vuelven a secar (cómo al principio). Fíjense que incluso en este momento ya tiene una pinta de "fiambre" bárbara:

Ya secada con la sal tiene una buena pinta

Ahora hay que preparar algo para ponerle alrededor. A mí me gusta mezclar pimentón ahumado, pimienta, y aceite de oliva. Otros dejan ajo macerándose en vino blanco y usan eso en vez del aceite. Hay muchas opciones de sabores para este "gustito de alrededor" de la bondiola. Lo importante es no usar nada con hojas (como orégano) ya que parece que se humedece y honguea.

Menjunje para pintar la bondiola

A la hora de pasarle este menjunje lo mejor es extender el material que vamos a usar para envolver la bondiola, apoyar la misma ahí, chorrearle el menjunje, y masajear la bondiola con las manos distribuyendo bien todo por todos lados. Sí, es un enchastre, pero las manos se lavan fácil, y no ensucian otra cosa porque todo el resto cae en el envoltorio en sí.

Después envuelven la bondiola y listo. Este paso que parece fácil, no lo es tanto, sin embargo. Mis primeras bondiolas las hice envolviéndolas con papel manteca común y atándolas con hilo de cocina cual matambre arrollado. Pero, por un lado, una vez el papel manteca que tenía casi no dejó respirar la bondiola y se hongueó un poco, y por el otro soy un desastre atando matambres o bondiolas :p.

Así que lo mejor es que compren papel microperforado y la red correspondiente. Este papel está preparado para que la humedad salga, y la red se pasa en segundos y queda re profesional.

Miren una mitad pintada y a punto de ser envuelta, y la otra ya terminada:

Media pintada, la otra mitad ya envuelta

La etapa final es dejarlas dos o tres semanas nuevamente en un lugar fresco y seco. Si hace mucho calor una buena alternativa es en la heladera, donde menos enfríe. Y el tiempo es variable, vayan toqueteándolas para ver como se van endureciendo, y vean cuanto se aguantan las ganas de comerlas :p

Ñam ñam

Finalmente, hacen una linda picadita y me invitan, claro :)

Mariano Guerra: Creemos en la Web: Recursos online

Si venís siguiente todas las secciones de esta serie habrás notado un patrón:

  1. Esto parece bastante repetitivo
  2. Abra alguien que haya hecho algo para facilitar esto?
  3. Si!

En esta sección vamos a ver algunos recursos que nos van a hacer mas fácil empezar y adaptar los recursos disponibles a lo que necesitemos.

Adaptando boostrap a nuestros gustos

Whoostrap cuenta con una lista de temas para aplicar a bootstrap y cambiar su aspecto básico.

Acá hay un ejemplo de como usarlo:

Guardamos el texto del CSS en un archivo y lo incluimos en nuestro proyecto, aca hay un ejemplo: https://thimbleprojects.org/marianoguerra/512478/

La pagina tambien provee algunos themes predefinidos themes.guide

Otras paginas quen nos brinda themes gratuitos que podemos descargar y usar: hackerthemes y Now UI Kit

Copiando fragmentos de HTML

Muchas de las partes de una pagina son generales y se repiten, por ejemplo la barra de navegación superior, el pie de pagina, una lista de productos o características, como esas cosas son repetitivas pero no hay una forma simple de "abstraerlas" sin tener que aprender javascript, hay paginas que nos muestran distintos fragmentos de HTML para componentes comunes. En ingles le llaman cheatsheets, acá hay una de bootstrap que es muy útil:

Bootstrap Cheatsheet

Hace click en el componente que querés ver y te va a mostrar el HTML a la izquierda y como se ve a la derecha.

Mariano Guerra: Creemos en la Web: Audio y Video

Incluir un video o audio seria algo tan simple como un tag y la ubicación del archivo.

Por cuestiones históricas hay muchos formatos de audio y video y las organizaciones que desarrollan los navegadores mas usados (Microsoft: Internet Explorer 11 y Microsoft Edge, Apple: Safari, Google: Chrome, Mozilla: Firefox) tienen distintos objetivos e intereses que hacen que soporten algunos formatos y otros no.

Video

Empecemos con un ejemplo que según esta tabla de compatibilidad para el formato webm, no va a andar en IE 11 y Safari.

<video src="/cew_files/12/example.webm" type="video/webm" controls></video>

Viendo la tabla de compatibilidad para el formato mp4 vemos que podemos hacerlo funcionar en mas versiones pero no necesariamente todas.

<video src="/cew_files/12/example.mp4" type="video/mp4" controls></video>

Que pasa si queremos hacerlo funcionar en la mayor cantidad de plataformas posibles priorizando formatos mas livianos y con mejor calidad?

Podemos especificar los videos en orden de preferencia, el navegador va a intentar en orden del primero al ultimo cargarlos, cuando encuentre uno que sirve lo va a usar.

<video controls>
    <source src="/cew_files/12/example.webm" type="video/webm">
    <source src="/cew_files/12/example.mp4" type="video/mp4">
</video>

Otro formato que suele usarse es ogv, acá la tabla de compatibilidad del formato ogv.

Vista Previa

Cuando la pagina carga y el video esta en pausa el navegador va a elegir una vista previa automáticamente, si queremos tener mas control sobre la imagen mostrada podemos especificarsela explícitamente con el atributo poster:

<video controls poster="/cew_files/12/poster.png">
    <source src="/cew_files/12/example.webm" type="video/webm">
    <source src="/cew_files/12/example.mp4" type="video/mp4">
</video>

Subtítulos

Ya sea por cuestiones de accesibilidad o para traducir o explicar el contenido del video, podemos agregar subtítulos a un video usando el tag track.

El formato del archivo es bastante simple:

WEBVTT

00:01.000 --> 00:04.000
Primer mensaje, del segundo 1 al 4

00:05.000 --> 00:08.000
Segundo mensaje, del segundo 5 al 8

...

Empieza con WEBVTT en la primera linea, un salto de linea y luego tantas veces como sea necesario:

[Tiempo comienzo] --> [Tiempo fin]
Texto del subtítulo

Podemos tener mas de un tag track para agregar subtitulos en distintos idiomas y marcar uno por defecto, aca un ejemplo con subtítulos en Español:

<video controls poster="/cew_files/12/poster.png">
    <source src="/cew_files/12/example.webm" type="video/webm">
    <source src="/cew_files/12/example.mp4" type="video/mp4">

    <track src="/cew_files/12/subtitulo.vtt"
        label="Subtitulos en Español"
        kind="captions"
        srclang="es"
        default>

</video>

Fragmentos

Que pasa si tenemos un video bastante largo pero solo queremos mostrar un fragmento?

Para eso podemos especificarle el principio y/o final del fragmento que nos interesa.

Notar que al momento de escribir esto es una característica bastante nueva, (ver tabla de compatibilidad de media fragments al momento de leer esto para ver si sigue siendo nueva y poco soportada).

Podemos indicarle el comienzo (segundo 10) y que reproduzca hasta el final:

t=10

Indicar solo el final, que reproduzca del principio y reproduzca hasta el segundo 20:

t=,20

O el principio y el final, que arranque en el segundo 10 y reproduzca hasta el segundo 20:

t=10,20

Veamoslo en nuestro video, que reproduzca desde el segundo 3 al 8 (puede que no funcione en tu navegador).

<video src="/cew_files/12/example.mp4#t=3,8" type="video/mp4" controls></video>

Embebiendo

Y que pasa si quiero poner en mi pagina un video que esta en una pagina de videos como youtube?

Para eso podemos embeber (embed en ingles) el contenido en nuestra pagina.

Si miras el video de los ejemplos de arriba, podrás ver que si vamos a share y luego seleccionamos embed, youtube nos da un HTML que podemos incluir en nuestra pagina para incluir el video directamente desde youtube.

<iframe width="560" height="315"
    src="https://www.youtube.com/embed/XM3eaJPB2Cc"
    frameborder="0"
    allow="autoplay; encrypted-media"
    allowfullscreen></iframe>

Podemos ver un video de youtube embebido que muestra un video de como embeber un video de youtube :)

El dialogo en youtube nos permite configurar algunos parametros que cambian el HTML que nos muestra, en el resultado de arriba vemos que podemos modificar el ancho, alto, si tiene borde, si hace auto play y si permite ponerlo en pantalla completa.

Audio

Como con video, hay muchos formatos de audio y cada navegador soporta un subset distinto, dado que hay mas formato de audio en uso listo las tablas de compatibilidad primero:

La canción que vamos a usar de ejemplo es Rough Patches de Solstar.

Empezamos con un audio en formato ogg:

<audio controls src="/cew_files/12/example.ogg"></audio>

Como veras el HTML es bastante similar al tag video.

Si no funciona o si tenes un mp3:

<audio controls src="/cew_files/12/example.mp3"></audio>

Pero si viste las tablas de compatibilidad y queres soportar la mayor cantidad de navegadores, al igual que con el tag video se puede incluir mas de un archivo.

<audio controls>
    <source src="/cew_files/12/example.ogg" type="audio/ogg"/>
    <source src="/cew_files/12/example.mp3" type="audio/mpeg"/>
</audio>

Embebiendo

Como con videos, hay paginas web que brindan audios y nos permiten embeberlos, en este caso uno de los mas usados es soundcloud, al igual que en youtube, si hacemos click en share y luego en embed, nos da un fragmento de HTML que podemos incluir en nuestra pagina:

<iframe
    width="100%"
    height="300"
    scrolling="no"
    frameborder="no"
    allow="autoplay"
    src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/72505324&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true">
</iframe>

Aca hay un video de como obtener el HTML:

Mariano Guerra: Creemos en la Web: Dibujando Formas en 3D

Ya dibujamos en 2D, se podrá en 3D?

La respuesta es si, pero como es un área que todavía esta en desarrollo no es soportada 100% y de forma simple en todos los navegadores por lo que nos vamos a ayudar de una librería llamada aframe.

Una librería es uno o mas archivos que al cargarlos en nuestra pagina le agregan funcionalidades.

En este caso nos permiten crear escenas en 3D como si estuvieramos escribiendo HTML.

Como dije esto no esta completamente estandarizado así que aframe define sus propios tags que no son parte de ningún estándar que soporten todos los navegadores.

Entonces empecemos cargando la librería aframe:

<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>

Y luego creamos nuestra escena:

<div style="width: 100%; height: 25em">
        <a-scene embedded>
          <a-box position="-1 0.5 -3" rotation="0 45 0" color="blue"></a-box>
          <a-sphere position="0 1.25 -5" radius="1.45" color="yellow"></a-sphere>
          <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
          <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
          <a-sky color="#BBBBBB"></a-sky>
        </a-scene>
</div>

El tag raíz se llama a-scene, que traducido seria "una escena", el cual contiene a-box "una caja", a-shere "una esfera", a-cylinder "un cilindro", a-plane "un plano" y a-sky "un cielo".

Cada uno con atributos especificando la posicion en 3 dimensiones, su tamaño, ya sea con su radio o su alto y su ancho y su color.

Fijate que con el mouse y las flechas del teclado podes moverte dentro de la escena, no es algo fijo sino algo que podes explorar. Si tenes un dispositivo que soporte Realidad Virtual (los últimos smartphones o anteojos de realidad virtual) apretando el icono en la esquina inferior derecha podes "sumergirte" en la escena en realidad virtual.

La escena de arriba en un proyecto de thimble así podes modificar los colores, formas, posiciones y tamaños.

Abrí https://thimbleprojects.org/marianoguerra/510288/ y hace click en Remix para copiar el proyecto a tu cuenta.

Aframe también puede usarse para imágenes y videos panorámicos, veamos un ejemplo:

<div style="width: 100%; height: 25em">
        <a-scene embedded>
          <a-sky src="https://raw.githubusercontent.com/aframevr/aframe/v0.7.0/examples/boilerplate/panorama/puydesancy.jpg" rotation="0 -130 0"></a-sky>

          <a-text value="Puy de Sancy, Francia" width="6" position="-2.5 0.25 -1.5"
                          rotation="0 15 0"></a-text>
        </a-scene>
</div>

Por el momento aframe solo soporta una escena por pagina, por lo que no muestro el resultado directamente, abrí https://thimbleprojects.org/marianoguerra/511087/ y hace click en Remix para copiar el proyecto a tu cuenta.

Podes ver mas ejemplos en https://aframe.io/examples/showcase/snowglobe/

Mariano Guerra: Creemos en la Web: Dibujando Formas en 2D

Hasta ahora la mayoría del contenido que creamos consiste principalmente en texto y "cajas", es decir, cuadrados dentro de cuadrados.

Pero que pasa si quiero una linea, un triangulo o un circulo en mi pagina?

Para eso existe un set de tags llamado SVG que nos permite crear dibujos "vectoriales", es decir que su contenido son las formas en si y se ven bien en cualquier resolución de pantalla, no como las imágenes hechas de pixeles, donde si la imagen es chica y la agrandamos empezamos a perder calidad.

Empecemos con un ejemplo simple:

<svg height="100" width="100">
    <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>

Los tags son nuevos y específicos de SVG, es decir, solo los podemos usar dentro de un tag raíz svg, lo bueno es que son bastante descriptivos.

En el ejemplo arriba decimos que queremos dibujar en svg, dentro de un cuadro de 100x100.

El dibujo consiste de un circulo (circle en ingles) con centro x=50 y centro y=50, con un radio de 40, borde de 3 negro y relleno rojo.

Veamos algunos otros ejemplos:

<svg width="400" height="180">
  <rect x="50" y="20" rx="20" ry="20" width="150" height="150" style="fill:blue;stroke:pink;stroke-width:5;opacity:0.5" />
</svg>

Acá vemos un rectángulo (rect) posicionado en x=50, y=20, con ancho de 150 y alto de 150 y bordes redondeados.

Como veras el resto de las propiedades se las define con el atributo style al igual que en HTML, algunos atributos son nuevos pero el resto sigue aplicando.

Algunos mas:

<svg height="250" width="500">
    <ellipse cx="240" cy="100" rx="220" ry="30" style="fill:purple" />
    <ellipse cx="220" cy="70" rx="190" ry="20" style="fill:lime" />
    <ellipse cx="210" cy="45" rx="170" ry="15" style="fill:yellow" />

    <polygon points="200,10 250,190 160,210" style="fill:lime;stroke:purple;stroke-width:1" />

    <line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" />
    <polyline points="0,40 40,40 40,80 80,80 80,120 120,120 120,160" style="fill:white;stroke:red;stroke-width:4" />

    <text x="0" y="15" fill="red" transform="rotate(30 20,40)">Texto en SVG</text>
</svg>
Texto en SVG

Pero lo mejor que tiene SVG es que hay editores libres y gratuitos que nos permiten dibujar como cualquier editor de imágenes y luego ver el código SVG generado.

Este editor se llama Inkscape y lo podes descargar desde la pagina.

Luego de instalarlo se ve algo así:

/galleries/cew/10/inkscape.png

Abriendo el editor XML de Inkscape podemos ver como se crea cada forma, que tags y atributos se usan.

/galleries/cew/10/inkscape-xml.gif

Si usamos un editor para crear un SVG que queremos insertar en nuestra pagina tenemos dos opciones:

  • Guardar el dibujo como un archivo SVG y copiar el contenido del archivo abriendolo con un editor de texto y pegandolo en nuestra pagina
  • Insertandolo como una imagen externa

Ya vimos como insertar SVG directamente en el HTML, ahora veamos como incluirlo como una imagen externa:

<img src="/galleries/cew/10/example.svg" width="475" height="336">

Fuente

Una ultima observación sobre SVG, si bien son parecidos a HTML, SVG es mas estricto en cuanto a los nombres y atributos de tags permitidos y con la necesidad de "cerrar" todos los tags, si cometemos un error en HTML, el navegador va a hacer lo mejor que pueda para presentar el contenido igual, en SVG muy probablemente no se dibuje nada.

Mariano Guerra: Creemos en la Web: Filas, columnas y pantallas de todos los tamaños

Hasta ahora hemos creado paginas con HTML donde el documento tiene una estructura básica: una cosa debajo de la otra.

Si prestas atención a sitios que visitas notaras que la estructura de esas paginas es mas complejas, la forma principal de organizarlas es con filas y columnas.

El lenguaje HTML provee algunos tags para indicar la intención del contenido dentro de esos tags pero no provee la estructura en si misma, para eso usamos CSS, el tema con los atributos CSS para definir estructura es que son muy específicos y flexibles, la idea es que con ellos podamos lograr cualquier tipo de estructura que deseemos, pero con gran flexibilidad viene gran complejidad.

Por esta razón han surgido distintas librerías CSS que permiten describir la estructura de un documento en términos mas generales (lo que los hace menos flexibles) pero de una forma que sirven para la mayoría de los casos que necesitamos.

Esta sección va a explorar como usar bootstrap para definir la estructura de nuestra pagina.

Vocabulario

Cuando definimos la estructura de un documento con bootstrap las palabras que aparecen son las siguientes:

Contenedor (container en ingles)
La raiz de una estructura con filas y columnas, podemos tener contenedores dentro de columnas pero no es algo común
Fila (row en ingles)
Una sección horizontal dentro de un contenedor o dentro de una columna
Columna (col/column en ingles)
Una sección vertical dentro de una fila
Puntos de corte (breakpoints en ingles)
Limites de resolución horizontal en la cual ciertas reglas cambian de significado (si, suena vago, vamos a ver esto en detalle luego)

Empezando

Estructura básica

Vamos a empezar con el ejemplo mas básico, un contenedor que tiene una fila que tiene una columna.

<div class="container-fluid cew-9">
 <div class="row">
  <div class="col">
    Columna 1
  </div>
 </div>
</div>

Nada raro, simplemente esta estructura definida con divs y clases:

  • container
    • row
      • col

Agregue una clase cew-9 a container-fluid para poder resaltar los distintos tags con css ya que si no lo hago es difícil percibir la estructura, los colores son los siguientes:

  • container-fluid: rojo
  • row: gris y blanco
  • col: verde
Columna 1

Usamos la clase container-fluid para que el contenedor se estire el 100% del ancho del tag que lo contiene, la clase container puede ser usada cuando queremos mas control sobre el ancho del contenedor.

Dos columnas

Un paso mas, dos columnas, que ya es algo que nos permite replicar bastantes estructuras encontradas en la web.

<div class="container-fluid cew-9">
 <div class="row">
  <div class="col">
    Columna 1
  </div>
  <div class="col">
    Columna 2
  </div>
 </div>
</div>
Columna 1
Columna 2

La estructura es

  • container
    • row
      • col 1
      • col 2

Normalmente en la web encontramos esta estructura de dos columnas pero donde una de ellas es una especie de menú o contenido secundario y la otra es el contenido principal, por lo cual el contenido principal usa mas espacio.

Para poder indicar esto de una forma que se adapte a todas las resoluciones de pantalla bootstrap define que una fila puede estar dividida en 12 "columnas", si no lo indicamos cada columna toma una cantidad igual de esas 12 columnas, por lo que si tenemos una columna tomara las 12, si tenemos 2 cada una tomara 6.

En nuestro caso queremos que la segunda sea la columna principal, por lo que le vamos a indicar que queremos que tome 8 de esas 12 columnas.

<div class="container-fluid cew-9">
 <div class="row">
  <div class="col">
    Columna 1
  </div>
  <div class="col-8">
    Columna 2
  </div>
 </div>
</div>
Columna 1
Columna 2

Para hacerlo cambiamos la clase col por la clase col-8 que indica que queremos que tome 8 de las 12 columnas disponibles.

Dos columnas con cabecera y pie de pagina

Esta estructura es la mas común para blogs o artículos, arriba tenemos una cabecera que ocupa todo el ancho con logo, titulo, navegación y algunas otras cosas, luego el contenido en si con dos columnas, luego un pie de pagina con información extra.

<div class="container-fluid cew-9">
 <div class="row">
  <div class="col">
   Cabecera
  </div>
 </div>

 <div class="row">
  <div class="col">
    Columna 1
  </div>
  <div class="col-8">
    Columna 2
  </div>
 </div>

 <div class="row">
  <div class="col">
   Pie de pagina
  </div>
 </div>
</div>
Cabecera
Columna 1
Columna 2
Pie de pagina

La estructura queda así:

  • container
    • row (cabecera)
      • col (contenido de cabecera)
    • row (cuerpo)
      • col 1 (contenido secundario)
      • col 2 (contenido principal)
    • row (pie de pagina)
      • col (contenido de pie de pagina)

Tortugas hasta el fondo

Un célebre científico dio una conferencia sobre astronomía.
Describió cómo la Tierra gira alrededor del Sol y cómo éste, a su vez,
gira alrededor de un inmenso conjunto de estrellas al que llamamos nuestra galaxia.

Al final de la conferencia, una vieja señora se levantó del fondo de la sala y dijo:

- Todo lo que nos ha contado son disparates.
  En realidad, el mundo es una placa plana que se sostiene sobre el caparazón
  de una tortuga gigante

El científico sonrió con suficiencia antes de replicar:

- ¿Y sobre qué se sostiene la tortuga?
- Sobre el caparazón de otra torguta gigante. -respondió la señora
- ¿Y qué sostiene a esa otra tortuga? volvió a preguntar el científico.
- Se cree usted muy agudo, joven, dijo la anciana,
  pero hay tortugas hasta el fondo.

Como hacemos si queremos tener una columna que a su vez tiene su propia estructura?

Podemos tener filas dentro de columnas.

<div class="container-fluid cew-9">
 <div class="row">
  <div class="col">
   Cabecera
  </div>
 </div>

 <div class="row">
  <div class="col">
    Columna 1
  </div>
  <div class="col-8">
   <div class="row">
    <div class="col">
     Columna 2.1.1
    </div>
    <div class="col">
     Columna 2.1.2
    </div>
   </div>

   <div class="row">
    <div class="col">
     Columna 2.2.1
    </div>
    <div class="col">
     Columna 2.2.2
    </div>
    <div class="col">
     Columna 2.2.3
    </div>
   </div>
  </div>
 </div>

 <div class="row">
  <div class="col">
   Pie de pagina
  </div>
 </div>
</div>
Cabecera
Columna 1
Columna 2.1.1
Columna 2.1.2
Columna 2.2.1
Columna 2.2.2
Columna 2.2.3
Pie de pagina

La estructura queda así:

  • container

    • row (cabecera)

      • col (contenido de cabecera)
    • row (cuerpo)

      • col 1 (contenido secundario)
      • col 2 (contenido principal)
      • row 2.1
        • col 2.1.1
        • col 2.1.2
      • row 2.2
        • col 2.2.1
        • col 2.2.2
        • col 2.2.3
    • row (pie de pagina)

      • col (contenido de pie de pagina)

Resoluciones de pantalla

Con este nuevo conocimiento creamos una pagina con una estructura perfecta para la pantalla que estamos usando y orgullosamente la compartimos con gente para que la vean, el primer mensaje que recibimos es:

- No se ve bien en mi celular, todo es muy chico y con poco espacio

No pensamos en que la pagina va a ser vista por personas usando un smartphone viejo, uno de ultima generación, una tablet, una laptop chica, una grande, una PC y la pantalla de un diseñador con mas pixeles de los que podemos contar.

Como hacemos para que nuestra pagina se adapte a la resolución de cualquier dispositivo que quiera visitar nuestra pagina?

Una idea seria ver cuales son las resoluciones mas comunes en pixeles y aplicar reglas para esos, si bien eso funcionaba en la prehistoria de la web (esto es, hace 10 años), ya no es así, veamos algunas de las resoluciones mas comunes disponibles actualmente:

/galleries/cew/9/resoluciones.png

Intentando hacer esto manejable entran en juego los puntos de corte que mencionamos al principio del articulo.

Los puntos de corte son limites de resolución que agrupan a la resolución de los dispositivos en 5 grandes grupos, similares a los de la ropa:

  • xs: Extra Small
    • Extra pequeño, dispositivos con menos de 576 pixeles de ancho
  • sm: Small
    • Pequeño, dispositivos con menos de 768 pixeles de ancho
  • md: Medium
    • Medio, dispositivos con menos de 992 pixeles de ancho
  • lg: Large
    • Grande, dispositivos con menos de 1200 pixeles de ancho
  • xl: Extra Large
    • Extra Grande, dispositivos con 1200 pixeles de ancho o mas

Como los usamos? indicando el ancho de la columna con el tipo de dispositivo mínimo para el cual el tamaño aplica, entonces podemos decir algo como:

"Esta columna ocupa 12 columnas si es una resolución xs y 6 si no"

lo expresamos en clases: .col-12 .col-sm-6

O mas complejas como

"Esta columna ocupa 12 columnas si es una resolución xs, 8 si es una resolución sm y 6 si no"

lo expresamos en clases: .col-12 .col-sm-8 .col-md-6

Control completo si especificamos todas:

"Esta columna ocupa 12 columnas si es una resolución xs, 8 si es una resolución sm, 6 si es una resolución md, 4 si es lg y 2 si es xl"

lo expresamos en clases: .col-12 .col-sm-8 .col-md-6 .col-lg-4 .col-xl-2

Bootstrap va a buscar el grupo mas cercano a la resolución actual y aplicar esa regla, si nuestro dispositivo tiene una resolución de 1000 pixeles y hay una regla para md (< 992px) va a aplicar esa, sino va a buscar la regla sm y sino la xs.

Probemos un ejemplo:

<div class="container-fluid cew-9">
 <div class="row">
  <div class="col-12 col-sm-6 col-md-3">
       Reglas: col-12 col-sm-6 col-md-3
  </div>
  <div class="col-12 col-sm-6 col-md-6">
       Reglas: col-12 col-sm-6 col-md-6
  </div>
  <div class="col-12 col-sm-12 col-md-3">
        Reglas: col-12 col-sm-12 col-md-3
  </div>
 </div>
</div>
Reglas: col-12 col-sm-6 col-md-3
Reglas: col-12 col-sm-6 col-md-6
Reglas: col-12 col-sm-12 col-md-3

Si estas viendo esto en una PC o una laptop probablemente la regla que aplique sea md, donde vas a ver 3 columnas, la del medio el doble de ancho que las laterales.

Pero como probamos para distintas resoluciones sin tener disponibles dispositivos para cada punto de corte?

En Firefox en el menú Herramientas > Desarrollador web > Vista de diseño adaptable o el atajo de teclado Ctrl+Shift+M

/galleries/cew/9/firefox-ctrl-shift-m.png

En Chrome en el menú Menú > Mas Herramientas > Herramientas para desarrolladores y al abrirse seleccionamos el segundo icono de arriba a la derecha o el atajo de teclado Ctrl+Shift+M

/galleries/cew/9/chrome-ctrl-shift-m.png

Esto va a abrir una herramienta que nos permite simular distintas resoluciones y ver como la pagina se adapta a los cambios, lo único que vamos a usar ahora es cambiar la resolución manualmente o elegir un dispositivo predeterminado.

Así es como se ve en mi computadora en firefox:

/galleries/cew/9/ff-xs.png

Resolución xs: 320x480

/galleries/cew/9/ff-sm.png

Resolución sm: 760x480

/galleries/cew/9/ff-md.png

Resolución md: 800x480

Te recomiendo que lo pruebes vos, actividad extra, navega por paginas que visites frecuentemente con esta herramienta abierta, fijate como se adapta (o no) a la resolución que elegiste.

Marcos Dione: identity-countries-languages-and-currencies

I started watching PyCon's videos. One of the first ones I saw is Amber Brown's "How we do identity wrong". I think she[1] is right in raising not only the notion of not assuming things related to names, addresses and ID numbers, but also that you shouldn't be collecting information that you don't need; at some point, it becomes a liability.

In the same vein about assuming, I have more examples. One of them is deciding what language you show your site depending on what country the client connects form. I'm not a millennial (more like a transmillennial, if you push me to it), but I tend to go places. Every time I go to a new place, I get sites in new languages, but maps in US!

Today I wanted to book a hotel room. The hotel's site asked me where do I live, so I chose France. Fact is, for them country and language is the same thing (I wonder what would happen if I answer Schweiz/Suisse/Svizzera/Svizra), so I can't say that I live in France but prefer English, so I chose United Kingdom instead. Of course, this also meant that I got prices in GBP, not EUR, so I had to correct that one too. At least I could.

Later they asked me country of residence and nationality; when I chose italian, the country was set to Italia, even when I chose France first!

I leave you all with an anecdote. As I said, I lake to go places, most of the times with friends. Imagine the puzzled expression of the police officer that stopped us to find a car licensed in France, driven by an italian, with an argentinian, a spanish and a chilean passangers, crossing from Austria to Slovakia, listening to US music. I only forgot to put the GPS in japanese or something.

So, don't assume; if you assume, let the user change settings to their preferences, and don't ask for data you don't actually need. And please use the user's Accept-Language header; they have it for a reason.


[1] I think that's the pronoun she[1] said she[1] preferred. I'm sorry if I got that wrong.


python misc