Lección 4: Seleccionables | Curso de UI Emacs Lisp

Lección 4: Seleccionables

Los campos seleccionables son aquellos donde el usuario no tiene la posibilidad de escribir, sino que debe elegir entre una serie de opciones. Aunque en Emacs, por su naturaleza de editor de texto, alguno de ellos se encuentran en una fina línea entre ser seleccionables o no.

Si ya has realizado formularios con anterioridad, te resultarán familiares algunos de ellos: select, radio, checkbox, toggle, color… Pero en Emacs, además, tenemos otros que no son tan comunes, como menu-choice o editable-list.

El widget es similar al select de HTML, pero con la diferencia de que no se puede escribir en ciertos campos. Es decir, el usuario debe elegir una opción entre una serie de opciones, pero podemos añadir opcionalmente un campo de texto libre para que el usuario pueda escribir lo que quiera.

(widget-create 'menu-choice
	       :tag "¿Qué smartphone tienes?"
	       :help-string "Selecciona tu smartphone"
	       :value "Android"
	       :notify (lambda (widget &rest ignore)
			 (message "Seleccionado: %s" (widget-value widget)))
	       '(choice-item :tag "Android" :value "Android")
	       '(item :tag "iPhone" :value "iPhone")
	       '(item :tag "Blackberry" :value "Blackberry")
	       '(editable-field :value "Otro" :format "Otro: %v"))

Login ejemplo

Aquí podemos ver como se ha desplegado las opciones.

Login ejemplo

Y en esta ocasión se ha seleccionado el campo editable “Otro”.

Login ejemplo

Los atributos son similares a otros widgets, como puede ser el editable-field, salvo que aquí demos añadir otros widgets dentro del menu-choice.

  • choice-item: Item seleccionado por defecto. Debes añadir el atributo :value a menu-choice con el mismo valor que el choice-item para que funcione. Revisa el ejemplo anterior. tag es el texto que se muestra en el desplegable y value es el valor que se asigna al menu-choice cuando se selecciona.
  • item: Item que se puede seleccionar. tag y value se comportan igual que choice-item.
  • editable-field: Campo de texto editable. Ya que se comporta como un editable-field, puedes añadirle el atributo :format para que se muestre un texto por defecto.

item (texto)

El widget nació y existe para crear un elemento seleccionable dentro de un grupo de botones como menu-choice o radio-button-choice. Pero por su naturaleza, también lo podemos utilizar de forma independiente para mostrar un texto plano editable.

(widget-create 'item :value "Texto de ejemplo")

Es importante que siempre lo guardes en una variable para poder modificarlo posteriormente.

(setq my-item (widget-create 'item :value "Texto de ejemplo"))

A continuación pudes ver un ejemplo donde un botón incrementa un número que es mostrado en el item de forma dinámica.

(setq number 0)
(widget-insert "\n")
(setq my-item (widget-create 'item :value "0"))
(widget-insert "\n")
(widget-create 'push-button
               :notify (lambda (&rest ignore)
			 (setq number (1+ number))
                         (widget-value-set my-item number))
               :help-echo "Púlsame para incrementar el número"
               "Incrementar")

En la documentación no encontrarás referencia alguna al uso del widget como un espacio de solo lectura o zona en el cual podamos renderizar strings. Sin embargo, nosotros lo usaremos con tal fin ya que no existe otro widget que nos permita hacerlo.

radio-button-choice (radio button)

Los radio buttons son aquellos botones que se agrupan y solo se puede seleccionar uno de ellos. En Emacs, utilizamos el widget radio-button-choice para crear el grupo. Cada botón se crea con el widget item.

Radio buttons ejemplo

(widget-insert "¿Qué quieres beber?:\n\n")
(widget-create 'radio-button-choice
    :value "Café"
    :notify (lambda (widget &rest ignore)
                (message "Has seleccionado %s" (widget-value widget)))
    '(item "Café")
    '(item "Té")
    '(item "Agua"))

En el caso que no queramos que ningún botón esté seleccionado por defecto, podemos omitir el atributo :value.

checkbox

Los checkbox son similares a los radio buttons, pero en este caso se pueden seleccionar varios a la vez. Para ello, usamos el widget checkbox.

Checkbox ejemplo

(widget-insert "\nAficiones favoritas:\n\n")
(widget-create 'checkbox
	       :help-echo "Marcar si te gusta leer"
	       :notify (lambda (&rest ignore) (message "Has seleccionado Leer")))
(widget-insert " Leer\n")
(widget-create 'checkbox
	       :help-echo "Marcar si te gusta ver películas"
	       :notify (lambda (&rest ignore) (message "Has seleccionado Ver series")))
(widget-insert " Ver series\n")
(widget-create 'checkbox
	       :help-echo "Marcar si te gusta jugar a videojuegos"
	       :notify (lambda (&rest ignore) (message "Has seleccionado Jugar a videojuegos")))
(widget-insert " Jugar a videojuegos\n")

No podemos utilizar el atributo :format para dar una etiqueta (label) a los checkbox. Al hacerlo deja de funcionar el widget. Posiblemente se deba a limitación técnica. Por lo tanto, debemos crear el texto nosotros mismos con widget-insert.

En el caso que queramos que alguno de los checkbox esté seleccionado por defecto, debemos incluir t como último argumento de checkbox.

(widget-create 'checkbox
           :help-echo "Marcar si te gusta leer"
           :notify (lambda (&rest ignore) (message "Has seleccionado Leer"))
           t)

toggle (switch)

Es similar a un checkbox en funcionalidad, pero visualmente es un switch de 2 posiciones: on o off. Es decir, un botón que puede estar en el estado activado o desactivado.

Toggle ejemplo

(widget-insert "¿Aceptas las condiciones? ")
(widget-create 'toggle
    :help-echo "¿Aceptas las condiciones?"
	:on "👍"
	:on-glyph '
	:off "👎"
	:off-glyph '
	:value t
	:notify (lambda (widget &rest ignore)
			 (message "Has seleccionado %s"
				  (if (widget-value widget)
				      "aceptar"
				    "rechazar"))))

Obligatoriamente debes definir:

  • :on: Texto que se muestra cuando el toggle está activado.
  • :off: Texto que se muestra cuando el toggle está desactivado.
  • :value: Valor inicial del toggle. t para activado y nil para desactivado.
  • :notify: Función que se ejecuta cuando se cambia el estado del toggle.

Opcionalmente puedes definir:

  • :on-glyph: Icono glyph, si están activados, que se muestra cuando el toggle está activado.
  • :off-glyph: Icono glyph, si están activados, que se muestra cuando el toggle está desactivado.
  • :help-echo: Texto que se muestra cuando se pasa el ratón por encima.

editable-list (lista editable)

Las listas editables es un widget de lo más peculiar. Permite crear una lista de elementos que se pueden editar. Es decir, podemos añadir, eliminar y modificar elementos de la lista. Similar a un TODO.

Editable list ejemplo

(widget-create 'editable-list
               :entry-format "%i %d %v"
	       :notify (lambda (widget &rest ignore) (message "%s" (widget-value widget)))
	       :value '("elisp" "emacs")
	       '(editable-field :value ""))

Si queremos incluir argumentos en los botones, como por ejemplo un texto de ayuda, debemos utilizar los atributos :insert-button-args, :delete-button-args y :append-button-args.

(widget-create 'editable-list
               :entry-format "%i %d %v"
	       :insert-button-args '(:help-echo "Inserta un elemento nuevo" :tag "Insertar")
	       :delete-button-args '(:help-echo "Borra el elemento" :tag "Borrar")
	       :append-button-args '(:help-echo "Inserta un elemento al final" :tag "Insertar nueva línea")
	       :value '("elisp" "emacs")
               '(editable-field :value ""))
  • :insert-button-args: Argumentos que se pasan al botón de insertar.
  • :delete-button-args: Argumentos que se pasan al botón de borrar.
  • :append-button-args: Argumentos que se pasan al botón de insertar que se encuentra al final de la lista.

No podrás utilizar :notify para obtener los cambios de la lista. En su lugar, debes utilizar :notify-insert y :notify-delete.

Existen otros atributos que podemos utilizar como :buttons que representa los widgets de los botones para insertar y borrar. Si buscas personalizarlos, puedes sobreescribirlos.

Como ejemplo práctico al widget, puede ver en el siguiente ejemplo he creado un formulario para crear hashtags.

(widget-insert "\nIncluye las etiquetas que quieras crear: \n\n")
(setq widget-preview (widget-create 'item :value ""))
(widget-insert "\n")
(widget-create 'editable-list
               :entry-format "%i %d %v"
               :notify (lambda (widget &rest ignore)
                         (widget-value-set widget-preview
				  ;; Convierte la lista a string, añadiendo un espacio de separación. Similar a join en otros lenguajes
				  (mapconcat 'identity
					     ;; Concadena a cada elemento un "#" al inicio.
					     (mapcar (lambda (item) (concat "#" item))
						     ;; Ignora los campos vacios
						     (seq-filter (lambda (item) (and (stringp item) (not (string= item ""))))
								 (widget-value widget))) " ")))
               :value '()
	       '(editable-field :value ""))

color

Es un widget que no esta documentado, pero si esta presente en el código fuente del paquete. Nos permite seleccionar un color de una paleta de colores.

Color ejemplo


(widget-create 'color
    :value "#ff0000"
	:tag "Color"
	:format "%{%t%}: %v (%{Previa%})\n"
	:notify (lambda (widget &rest ignore)
			 (message "Has seleccionado %s" (widget-value widget))))

El atributo :format es más complejo que el resto de widgets. Nos permite definir el texto del tag (%t), el valor (%v) y el texto de la previsualización (%{Previa%}).

Si deseas renombrar el botón de selección, puedes sobreescribir la función widget-color-value-create.

(defun widget-color-value-create (widget)
  (widget-field-value-create widget)
  (widget-insert " ")
  (widget-create-child-and-convert
   widget 'push-button
   :tag " Selecciona " :action 'widget-color--choose-action)
  (widget-insert " "))

group

No es un widget visual como todos los anteriores, su labor es funcional. Nos brinda la capacidad de agrupar widgets para organizar los comportamientos. Puedes pensar en él como una carpeta.

(setq form-contact (widget-create 'group :tag "form-contact"))
(setq input-email (widget-create
		      'editable-field
		      :size 20
		      :format "Email: %v"
		      :parent form-contact)) ; Nueva
(setq input-password (widget-create
		      'editable-field
		      :size 20
		      :format "Contraseña: %v"
		      :secret ?*
		      :parent form-contact)) ; Nueva

No te puedo dar más información sobre este widget, ya que no he encontrado documentación sobre él.

Archivos

Si leemos el código fuente del paquete widget (emacs/lisp/wid-edit.el), podemos encontrar otros selectores que no están documentados. Entre ellos, para seleccionar archivos, imágenes y directorios.

No los he incluido en el curso porque no he conseguido que funcionen. Si por tu lado consigues hacerlos trabajar, no dudes en compartirlo conmigo.

Actividad 1

Vamos a construir un formulario que nos ayude a calcular contidades con y sin impuestos.

El formulario debe tener los siguientes campos:

  • Campo de texto para introducir la cantidad
  • Desplegable
  • Botón para calcular
  • item para mostrar el resultado

El desplegable, o menu-choice, contendrá las siguientes opciones:

  • Sin impuestos
  • Con impuestos

Cuando se pulse el botón de calcular, se obtendrá el valor del campo de texto y se incrementará en un 21% si se ha seleccionado la opción "Con impuestos". En caso contrario, "Sin impuestos", se restará un 21%. El resultado de la operación se mostrará en el item.

Actividad 2

Crea un formulario con la siguiente estructura:

  • ¿Ocultar tareas completadas? toggle
  • item con el número de tareas completadas

Cuando se pulse en el toggle, se mostrará el número de tareas completadas en el item. La información se obtendrá del siguiente endpoint: https://dummyjson.com/todos

Actividad 3

Utiliza una lista editable para sumar números. Todos los campos de la lista deben ser numéricos. Después de la lista, se debe mostrar el resultado, puedes apoyarte de un item para ello.

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me invitas a un café? ☕

Puedes hacerlo usando el terminal.

ssh customer@andros.dev -p 5555

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario