Lección 5: Validaciones | Curso de UI Emacs Lisp

Lección 5: Validaciones

Ya hemos aprendido que widgets existen y como podemos utilizarlos para crear una interfaz gráfica. Ahora vamos a mejorar un aspectos elemental como validar los datos de entrada, un requisito que no podemos pasar por alto cuando estamos creando una UI o formulario. Solo es válido si el usuario introduce texto libre, como puede ser editable-field o text.

Demo validaciones

Para validar debes jugar con 2 propiedades: :valid-regexp y :error.

(widget-create 'editable-field
	       :size 5
	       :help-echo "Escribe un número"
	       :valid-regexp "^[0-9]+$" ;; Nuevo
	       :error "Error: Solo se permite números" ;; Nuevo
	       :notify #'input-show-error ;; Nuevo
	       :format "Escribe solo números: %v")

La propiedad :valid-regexp nos permite definir una expresión regular para validar el valor del campo. Por ejemplo, si buscamos aceptar solo números podemos utilizar la expresión regular ^[0-9]+$ que significa que solo se aceptan números del 0 al 9 y que el campo no puede estar vacío.

El mensaje de error se define con la propiedad :error. Pero, ¿donde se mostrará? Como si fuera un formulario HTML, eres tú quien debe definir el lugar. En el ejemplo he decidido visualizarlo en el minibuffer con la función message. Queda en tu mano decidir otros lugares, como por ejemplo, un tooltip o un overlay, o incluso, un widget de tipo item que muestre el mensaje justo debajo del campo.

Queda otro detalle importante, ¿cuando se debe mostrar el mensaje de error? ¿Según escribes? ¿Al salir del campo? (perder el foco) ¿Al pulsar sobre un botón? Sea cual sea tu decisión, debes enfrentarte a la propiedad :notify. Nos permite definir una función que se ejecutará siempre que el campo cambie (similar a un onchange de JavaScript). No hay más eventos a capturar. Ello nos obliga a fabricar funciones con lógicas complejas para saber cuando mostrar el mensaje de error. En mi caso he decidido tomar el camino rápido, validar según el usuario escribe. Para ello he creado la función input-show-error que se ejecutará cada vez que el usuario escriba en el campo.

(defun input-show-error (widget &rest ignore)
  "Muestra un mensaje de error si el valor del input no es válido."
  ;; Comprueba si el cambio no es válido y no está vacío
  (when (and
     (widget-apply widget :validate)
     (not (string= (widget-value widget) "")))
    ;; Muestra el mensaje de error en el minibuffer
    (let (
      (message-error
       (propertize
        (format "Error: %s"
            ;; Colorea el mensaje de error en rojo
            (widget-get widget :error)) 'face '(:foreground "red"))))
      (message message-error))))

Veamos punto por punto que hace.

Antes de mostrar el mensaje, debemos saber si el valor del input es válido. Puedes utilizar el siguiente código para ello:

(widget-apply widget :validate)

En caso de que el valor no sea válido, la función widget-apply devolverá t.

Si la unimos a una condición de que el campo no puede estar vacío, obtenemos lo siguiente:

(when (and
     (widget-apply widget :validate)
     (not (string= (widget-value widget) "")))
     ;; Muestra el mensaje de error
     )

Para terminar he decidido mostrar el mensaje de error de color rojo en el minibuffer.

(let (
      (message-error
       (propertize
        (format "Error: %s"
            (widget-get widget :error)) 'face '(:foreground "red"))))
      (message message-error))))

La función es reutilizable, puedes vincularla en todos los campos que necesites, como he hecho yo con ambos campos.

Ejemplo completo

A continuación puedes ver una sencilla calculadora que suma 2 números. He añadido un botón para calcular la suma y visualizar el resultado en un campo de solo lectura. En caso de que el usuario introduzca un valor no numérico, se mostrará un mensaje de error en el minibuffer.

La estructura la explicaré en la siguiente lección, de momento céntrate en las validaciones:

;; -*- coding: utf-8 -*-
;; Imports
(require 'widget)

(eval-when-compile
  (require 'wid-edit))

;; Variables
(defvar input-field-1)
(defvar input-field-2)
(defvar input-result)

;; Functions

(defun input-show-error (widget &rest ignore)
  "Show error message if the input is invalid."
  ;; Validate the input and ignore if is empty
  (when (and
     (widget-apply widget :validate)
     (not (string= (widget-value widget) "")))
    ;; Show error message
    (let (
      (message-error
       (propertize
        (format "Error: %s"
            (widget-get widget :error)) 'face '(:foreground "red"))))
      (message message-error))))

(defun sum-inputs (widget &rest ignore)
  "Sum the two numberns in the input fields."
  (let ((num1 (widget-value input-field-1))
        (num2 (widget-value input-field-2))
        result)
    (setq result (+ (string-to-number num1) (string-to-number num2)))
    (widget-value-set input-result (format "%s" result))))

(defun main-layout ()
  "Make widgets for the main layout."
  (interactive)
  ;; Create the buffer
  (switch-to-buffer "*Sum calculator*")
  ;; Clear the buffer
  (kill-all-local-variables)
  (let ((inhibit-read-only t))
    (erase-buffer))
  (remove-overlays)
  ;; Create the widgets
  (widget-insert "Sum Calculator\n\n")
  (setq input-field-1 (widget-create 'editable-field
                     :size 5
                     :tag "Number 1"
                     :help-echo "Type a number"
                     :valid-regexp "^[0-9]+$"
                     :error "Invalid number"
                     :notify #'input-show-error
                     :format "%v"))
  ;; Add hook focus-out input-field-1
  (widget-insert " + ")
  (setq input-field-2 (widget-create 'editable-field
                     :size 5
                     :tag "Number 2"
                     :help-echo "Type a number"
                     :valid-regexp "^[0-9]+$"
                     :error "Invalid number"
                     :notify #'input-show-error
                     :format "%v"))
  (widget-insert " = ")
  (setq input-result (widget-create 'item
                    :size 5
                    :tag "Result"
                    :format "%v"
                    :value "0"))
  (widget-insert "\n\n")
  (widget-create 'push-button
         :notify #'sum-inputs
         :tag "Calculate"
         :help-echo "Calculate the sum of the two numbers"
         :highlight t
         "Calculate")
  (widget-insert "\n\n")


  ;; Display the buffer
  (use-local-map widget-keymap)
  (widget-setup)
  ;; Go to the first input field
  (widget-forward 1))

;; Initialization
(main-layout)

Le he dado el foco al primer campo de entrada para que el usuario no tenga que hacerlo manualmente. Para ello ejecuto la función widget-forward.

(widget-forward 1)

Debes hacerlo después de crear todos los widgets.

(use-local-map widget-keymap)
(widget-setup)
(widget-forward 1)

Además he usado el campo un campo de tipo item para que el resultado sea de solo lectura.

(setq input-result (widget-create 'item
                    :size 5
                    :tag "Result"
                    :format "%v"
                    :value "0"))

También puedes apreciar algunos retoques en la maquetación para que sea más legible y bonito.

Actividad 1

Mejora la calculadora para que acepte números decimales.

Actividad 2

Crea un formulario de identificación.

  • Email: Valida que el texto introducido sea un email.
  • Contraseña: Debe contener al menos 8 caracteres.
  • Botón: Al pulsar sobre el botón, muestra un mensaje de bienvenida en el minibuffer si se ha introducido un email y una contraseña válida.

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

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me ayudas?

Comprame un café
Pulsa sobre la imagen

No te sientas obligado a realizar una donación, pero cada aportación mantiene el sitio en activo logrando que continúe existiendo y sea accesible para otras personas. Además me motiva a crear nuevo contenido.

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

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

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

Escribe el primer comentario