La guia de platanus
  • README
  • Acuerdos
    • Guía de Estilo
      • Ejemplo: Módulo para variables de entorno
  • Stack
    • Getting Started
    • Nuestro MVC extendido
    • Ruby/Rails
      • Power Types
        • General
        • Patrones
          • Commands
          • Utils
          • Services
          • Values
          • Observers
      • Potassium
      • Power API
      • Active Admin
        • General
        • Active Admin Addons
      • Pundit
      • Shrine
        • General
        • Manejo y procesamiento de imágenes
      • Pry
      • Strong Migrations
      • Data Migrate
      • Active Job
      • Gems
      • Engines - Modularización en Rails
    • JavaScript
      • Vue
        • General
        • Testing
      • AlpineJS
    • CSS
    • Mobile
      • Expo
      • React Navigation
      • Redux
        • Crear y conectar una slice en Redux
      • Styling
        • Usando Tailwind en React Native
      • Recursos
    • Resolviendo problemas (debugging)
    • Machine Learning
  • Setup
    • Configuración de tu entorno local
      • Instalación Base
        • OSX
        • Windows
        • Linux
      • Tecnologías
        • Ruby
        • Docker
        • Node
      • Herramientas
        • Linters
        • Editores
          • IDE/Editores de Código
            • Visual Studio Code
            • Sublime Text
        • Git
    • Configuración de proyectos
      • Getting Started
      • Heroku
      • Rails
      • Circle CI
      • Vue
      • Apple App Store
      • Google Play
      • Expo
      • S3
      • Git
      • Cloudflare
      • Sendgrid
      • Dominio + Mailing
      • Google Tag Manager, Analytics, Search Console, etc.
        • Google Tag Manager
          • Configurar Google Tag Manager
        • Google Analytics
        • Indexación en Google
        • Google Ads
      • Crear un bucket de S3
      • SlackBot
      • Google BigQuery
  • Deployment
    • Rails
    • Ruby Gems
    • Browser and Node (Open Source)
    • Mobile
      • Mobile Resources
      • Apple App Storage
      • Google Play
  • Upgrades
    • Upgrade de Vue 2 a Vue 3
    • Migración Hound → reviewdog
    • Upgrade de Postgresql
Con tecnología de GitBook
En esta página
  • ActiveAdmin + AlpineJS
  • Instalando AlpineJS
  • Intro
  • Formatear el valor de un campo al escribir
  • Validar un campo
  • Esconder y mostrar un campo
  • Campos Select2
  • Has Many
  • Formularios Complejos (Solo AlpineJS 3 - Alpine.data)
  1. Stack
  2. JavaScript

AlpineJS

ActiveAdmin + AlpineJS

AlpineJS es una excelente alternativa para agregar algo de inteligencia a nuestras vistas de ActiveAdmin. En la mayoría de los casos podemos definir todo lo que usamos directamente en nuestro recurso de ActiveAdmin, sin necesidad de archivos JS externos.

Instalando AlpineJS

# Si estamos usando Shakapacker, Webpack 5+ u otro bundler moderno
yarn add alpinejs

# Si estamos usando Webpacker
yarn add alpinejs@2

Después tenemos que agregar lo siguiente al archivo donde ActiveAdmin se inicializa, normalmente el que tiene la linea import '@activeadmin/activeadmin';

import '@activeadmin/activeadmin';

import Alpine from 'alpinejs';

window.Alpine = Alpine;
Alpine.start();

Si estás usando Alpine 2 (por Webpacker), tienes que usar lo siguiente:

import '@activeadmin/activeadmin';

import 'alpinejs'

💡 Todos los ejemplos en esta guía y el repositorio asociado usan AlpineJS 3 pero deberían funcionar en AlpineJS 2.

Intro

Un componente de AlpineJS es un elemento HTML con el atributo (también llamado directiva) x-data con todas las variables que vamos a usar dentro de un objeto de javascript.

<div x-data="{open: false}"></div>

Para que esto funcione en ActiveAdmin, tenemos que agregar el atributo x-data a inputs, input o cualquier otro elemento "wrapper"

f.inputs 'x-data':  CGI.escapeHTML("{...#{f.resource.attributes.to_json}}") do
  f.input :name
end

💡 Tenemos que usar CGI.escapeHTML para evitar que el objeto producido por Rails no escape del atributo x-data, lo que normalmente pasa por una comilla doble.

💡 f.resource.attributes nos da acceso a todos los atributos del modelo. En vez de usarlo, también puedes declarar los valores a mano, siempre recordando que el resultado final debe ser un objeto válido de javascript.

💡 Si un ejemplo en la guía no incluye el atributo x-data, de todas maneras se asume que fue declarado

Una vez que hemos inicializado el componente con x-data podemos empezar a usar las otras directivas de AlpineJS.

f.inputs 'x-data':  CGI.escapeHTML("{...#{f.resource.attributes.to_json}}") do
  f.input :name, input_html: { 'x-model': 'name' }
end

Formatear el valor de un campo al escribir

Casos de uso: Números de teléfono, valores en formato de moneda local, números de cédula de identidad, etc.

Para empezar tenemos que agregar la función para formatear en el mismo archivo donde inicializamos Alpine y exponer la función a la página en ActiveAdmin.

window.Alpine = Alpine;
Alpine.start();

window.formatters = {
  // Formats a number to currency
  currency: new Intl.NumberFormat('es-CL', { style: 'currency', currency: 'CLP' }),
  // Removes everything that's not a number from a string
  numberCleaner(value) {
    return value.replaceAll(/\\D/g, '');
  },
};

Después, dentro de nuestro recurso en ActiveAdmin, podemos usar x-on-input (o @input) para formatear el valor cada vez que escribimos un valor en el campo de texto.

💡 Para este ejemplo en especifico, tenemos que limpiar el número (para obtener 1000 en vez de 1.000), asi que ejecutamos numberCleaner antes de `format.

f.input :amount, input_html: {
  'x-model': 'amount',
  'x-on:input': 'amount = formatters.currency.format(formatters.numberCleaner($event.target.value));'
}

Si actualizamos la página el monto no estará formateado porque el formateador solo se ejecuta con el evento input. Para arreglar esto tenemos que editar el valor iniciar en x-data.


f.inputs 'x-data': CGI.escapeHTML("{ amount: formatters.currency.format('#{f.resource.attributes['amount']}')}") do

  f.input :amount, input_html:
    'x-model': 'amount',
    'x-on:input': 'amount = formatters.currency.format(formatters.numberCleaner($event.target.value));
    '

En el caso que el atributo del modelo sea un integer en la base de datos, no podremos guardar el valor formateado. Para lograr guardar el valor actualizado pero mantener el formato al editarlo tenemos que agregar un par de cosas para tener dos campos: el campo formateador y el campo "real" que se guarda en la base de datos.

En el modelo agregamos:

class FormatFieldExample < ApplicationRecord
  attr_accessor :active_admin_amount
end

💡 attr_accessor es necesario ya que ActiveAdmin no permite mostrar valores que no existen como campos en su formulario.

Después, en el archivo de ActiveAdmin, agregamos amount directamente y además agregamos el campo active_admin_amount formateado a x-data.

f.inputs 'x-data': CGI.escapeHTML("{
    amount: #{f.resource.attributes['amount']},
    active_admin_amount: formatters.currency.format('#{f.resource.attributes['amount']}')},
  }") do

Reemplazamos el campo amount por active_admin_amount y en el evento x-on:input agregamos que también se actualice amount.

  f.input :active_admin_amount, input_html:
    'x-model': 'active_admin_amount',
    'x-on:input': '
      active_admin_amount = formatters.currency.format(formatters.numberCleaner($event.target.value));
      amount = formatters.numberCleaner(active_admin_amount);
    '

Finalmente agregamos un campo oculto con el amount real para que se guarde en la base de datos como número.

f.input :amount, as: :hidden, input_html: {
  'x-bind:value': 'amount'
}

Validar un campo

Casos de uso: Prevenir que un formulario se pueda guardar si un valor no es válido, mostrar cuando un campo es obligatorio o tiene un valor inválido.

Al igual que en el ejemplo anterior, tenemos que agregar la función de validación a la variable window para que esté disponible en la página de ActiveAdmin.

import { rutValidate } from 'rut-helpers';

window.validators = {
  // Formats a value to the standard RUT format.
  rut: rutValidate
};

En este ejemplo queremos cambiar la clase CSS del campo cuando el valor no es válido. Para esto necesitamos usar x-bind:class(o :class) para que la clase error sea agregada dinámicamente cuando validators.rut(rut) sea false:

f.input :rut, input_html: {
  'x-model': 'rut',
  'x-bind:class': '{error: !validators.rut(rut)}'
}

Si también queremos desactivar el botón para guardar, podemos editar la acción submit para agregar el atributo disabled. x-bind:disabled (o :disabled) automáticamente agregan el atributo cuando la validación falla.

f.actions do
  f.action :submit, button_html: { 'x-bind:disabled': "!validators.rut(rut)" }
end

Esconder y mostrar un campo

Para poder mostrar y esconder un campo podemos usar la directiva x-show.

Primero necesitamos un campo con x-model para tener acceso a su valor.

f.input :has_description, input_html: {
  'x-model': 'has_description'
}

Después agregamos la directiva x-show al campo que queremos mostrar o esconder dependiendo del valor que tenga el campo has_description.

f.input :description, wrapper_html: {
  'x-show': 'has_description'
}

💡 Tenemos que usar wrapper_html en vez de input_html para esconder toda la fila, tanto el label como el input.

Campos Select2

yarn add active-admin-alpine-fixes

Después tenemos que agregar el fix a la variable window para que esté disponible en la página de ActiveAdmin

import { select2 } from 'active-admin-alpine-fixes';

window.alpineFixes = { select2 };

Finalmente agregamos el fix a nuestro component agregando la directiva x-init para que ejecute el fix apenas el componente sea evaluado por el navegador.

f.inputs 'x-init': 'alpineFixes.select2.init', 'x-data':  CGI.escapeHTML("{...#{f.resource.attributes.to_json}}") do
  f.input :choices, input_html: { 'x-model': 'choices' }
end

Has Many

ActiveAdmin nos permite tener formularios anidados cuando un recurso tiene un has_many. Pero cuando hacemos click en el botón para crear un recurso nuevo en este formulario anidado ActiveAdmin usa jQuery para crear los campos nuevos y AlpineJS se confunde.

yarn add active-admin-alpine-fixes

Después tenemos que agregar el fix a la variable window para que esté disponible en la página de ActiveAdmin

import { hasMany } from 'active-admin-alpine-fixes';

window.alpineFixes = { hasMany };

En nuestro componente tenemos que agregar el fix a la directiva x-init y en nuestro x-data tenemos que agregar explícitamente el recurso anidado. Dentro del has_many tenemos que usar x-model con el índice que nos da para que AlpineJS sepa a qué campo corresponde qué elemento en el arreglo.

f.inputs 'x-init': 'alpineFixes.hasMany.init',
          'x-data': CGI.escapeHTML("{
            ...#{f.resource.attributes.to_json},
            children: #{f.resource.children.to_json}
          }") do
  f.has_many :children, allow_destroy: true do |co, i|
    # has_many index starts with 1 while javascript's starts with 0 so we subtract one
    co.input :name, input_html: {
      'x-model': "children[#{i - 1}].name"
    }
  end
end

Formularios Complejos (Solo AlpineJS 3 - Alpine.data)

Si nuestro formulario es muy complejo o tiene funcionalidad que puede ser fácilmente re-usada, podemos usar Alpine.data en nuestro javascript para declarar un objeto que puede ser usado en nuestro formulario sin tener que usar window. En otras palabras, podemos tener un archivo JS separado con todo lo que necesitamos.

// activeadmin/complex_example.js

export default (attributes = {}) => {
  function init() {
    // We need to pass the Alpine context (this) so it can find the element
    select2.init.bind(this)();
  }

  const currencyFormat = new Intl.NumberFormat('es-CL', { style: 'currency', currency: 'CLP' });

  function numberCleaner(value) {
    return value.replaceAll(/\\D/g, '');
  }


  // We return an object that will be available inside our component
  return { ...attributes, init, currencyFormat, numberCleaner };
};
import complexExample from './activeadmin/complex_example';

Alpine.data('complexExample', complexExample);
Alpine.start();

Una vez hecho lo anterior, podemos usar complexExample en nuestro x-data, el que recibe los atributos que necesitamos para inicializar el objeto que usa Alpine.

💡 Como complexExample todavía no se ejecuta, currencyFormat todavía no está disponible para ser usado en x-data. Puedes agregar la función a la variable window, procesar los atributos dentro de complexExample o, como en este caso, usar Ruby para lograr el mismo resultado.

form do |f|
  f.inputs 'x-data': "complexExample(#{CGI.escapeHTML("{
      ...#{f.resource.attributes.to_json},
      active_admin_amount: '#{number_to_currency(f.resource.attributes['amount'])}'
    }")})" do
    f.input :name

    f.input :active_admin_amount, input_html: {
      'x-model': 'active_admin_amount',
      # We can use currencyFormat and numberCleaner directly since the are available inside
      # the data object returned by the complexExample function.
      'x-on:input': '
        active_admin_amount = currencyFormat.format(numberCleaner($event.target.value));
        amount = numberCleaner(active_admin_amount);
      '
    }

    f.input :choices, input_html: { 'x-model': 'choices' }

    f.input :amount, as: :hidden, input_html: {
      'x-bind:value': 'amount'
    }

    f.actions do
      f.action :submit
    end
  end
end
AnteriorTestingSiguienteCSS

Última actualización hace 2 años

transforma todos los select para que usen Select2, para facilitar el uso de colección grandes o tags. Sin embargo, AlpineJS no tiene idea qué hacer con los elementos de Select2 y viceversa.

Para que funcionen los elementos select con atributos x-model tenemos que instalar .

Para que funcionen tenemos que instalar .

ejemplo
ejemplo
ejemplo
ActiveAdmin Addons
active-admin-alpine-fixes
ejemplo
active-admin-alpine-fixes
ejemplo
ejemplo