Saltar al contenido principal
Dashboard > Actions > Forms > Campo personalizado
Con el campo personalizado, puede ampliar la apariencia y la funcionalidad de sus formularios, y añadir una lógica potente con JavaScript, HTML y CSS. El campo personalizado incluye métodos internos para facilitar el envío de datos al formulario, añadir validaciones de frontend y backend, y gestionar eventos comunes como focus o blur. Puede usar el campo personalizado para crear:
  • Campos con una estructura de datos personalizada.
    • Ejemplo: Objetos, matrices de cadenas
  • Campos que usan widgets de terceros.
    • Ejemplo: Autocompletado de direcciones de Google
  • Campos con lógica para ocultar o mostrar otros campos.
  • Campos que requieren API externas para obtener un valor.
Para usar campos personalizados, debe habilitar dominios personalizados. Si renderiza un formulario con un campo personalizado fuera de un dominio personalizado, se mostrará un error.

Configuración de campos personalizados

La configuración de los campos personalizados es la siguiente:

Parámetros

Agregue pares clave-valor para hacer referencia a ellos en el código fuente del campo personalizado. Los pares clave-valor pueden incluir variables de campos del formulario.
Los valores de los parámetros solo están disponibles después de que el formulario llame al método init().
Ejemplo: En el ejemplo siguiente, la configuración de parámetros del campo personalizado se rellena con los pares clave-valor symbol={{fields.symbol}} y separator=,
function CustomComponent(context) {
  const input = document.createElement('input');
  let mask = null;

  function mountComponent() {
    /** El método getParams() devuelve los parámetros que configuraste en tu input */
    const config = context.custom.getParams();
    const { symbol, separator } = config;

    mask = IMask(input,
    {
      mask: `${symbol}num`,
      blocks: {
        num: {
          mask: Number,
          thousandsSeparator: separator,
        }
      }
    });
  }

  return {
    /** Se invoca una vez cuando se crea el campo */
    init() {
      mountComponent();
      return input;
    },
    ...
  };
}

Código fuente

Agrega tu código JavaScript al campo personalizado.
Ejemplo:
function customInput() {
  const input = document.createElement('input');
  input.type = 'text';

  return {
    init() {
      return input;
    },

    block() {
      input.disabled = true;
    },

    unblock() {
      input.disabled = false;
    },

    getValue() {
      return input.value;
    }
  };
}

Esquema JSON

De forma predeterminada, el campo personalizado acepta cualquier formato de valor. Sin embargo, puedes usar JSON Schema para validar los valores en el servidor.
Ejemplo:
{
  "type": "array",
  "items": {
    "type": "string"
  },
  "minItems": 2
}
Para adaptarte a requisitos de validación complejos, puedes usar un flujo.

CSS

Añade tus estilos CSS al campo personalizado.

Manejadores de campos personalizados

Puedes usar estos manejadores para añadir un comportamiento personalizado al campo:

init()

Se invoca una sola vez cuando se crea el campo y recibe los valores de params que configuras en la sección parámetro. Devuelve un elemento HTML, una cadena o ningún valor. Ejemplo:
const input = document.createElement('input');
input.type = 'text';

init() {
  return input;
}

update()

Se invoca cuando el usuario vuelve a visitar el mismo paso del formulario. Esta opción es útil cuando necesita volver a renderizar la lógica de la interfaz de usuario o actualizar los valores de los parámetros que podrían haber cambiado.

onFocus()

Se invoca cuando el foco pasa al elemento HTML del campo personalizado.

onBlur()

Se invoca cuando el elemento HTML del campo personalizado pierde el foco.

getValue()

Se invoca cuando el formulario necesita obtener el valor del campo personalizado una o varias veces. Por lo general, esto ocurre cuando el usuario envía ese paso del formulario. Si necesita realizar validaciones del lado del cliente, puede generar un error para mostrar un mensaje de error personalizado al usuario. Ejemplo:
function customTextInput() {
  const input = document.createElement('input');
  input.type = 'text';

  return {
    init() {
      return input;
    },

    getValue() {
      if (input.value !== 'Auth0') {
        throw new Error('The value must be Auth0')
      }

      return input.value;
    }
  };
}

block()

Se invoca cuando el campo personalizado debe bloquearse. Normalmente se ejecuta cuando el usuario envía ese paso del formulario y los datos se están procesando en nuestro backend.

unblock()

Se invoca cuando se debe desbloquear el campo personalizado. Normalmente se ejecuta después de que el usuario envía ese paso del formulario o de que los datos dejan de procesarse en nuestro back-end debido a un error de validación.

getScripts()

Devuelve una lista de URL cuya carga el formulario garantiza que habrá finalizado antes de que se invoque el método init(). Ejemplo:
getScripts() {
  return ['https://example.com/script_a.js', 'https://example.com/script_b.js'];
}

Objeto de contexto

Al pasar un objeto de contexto, puedes usar estos métodos para gestionar la lógica de tu formulario y tus componentes.

Métodos personalizados

context.custom.getValue()

Devuelve el valor del campo personalizado actual.

context.custom.setValue()

Establece un valor para el campo personalizado actual. Ejemplo:
function customInput(context) {
  const input = document.createElement('input');
  input.type = 'text';

  input.addEventListener('change', () => {
    context.custom.setValue(input.value);
  });

  return {
    init() {
      return input;
    },
  };
}

context.custom.createUid()

Devuelve un identificador único del campo personalizado actual. Ejemplo:
function customInput(context) {
  const input = document.createElement('input');
  input.type = 'text';
  input.id = context.custom.createUid();

  return {
    init() {
      return input;
    },

    getValue() {
      return input.value;
    }
  };
}

context.custom.getParams()

Obtiene los parámetros de la configuración actual del campo personalizado.
Los valores de los parámetros solo están disponibles después de que el formulario invoque el método init().
Ejemplo:
function customInput(context) {
  // Acceder a los parámetros en la raíz de la función NO funciona
  // const { defaultValue } = context.custom.getParams();
  // console.log(defaultValue); // undefined

  function buildInput() {
    const { defaultValue } = context.custom.getParams();
    const input = document.createElement('input');
    input.type = 'text';
    input.value = defaultValue;

    return input;
  }

  return {
    init() {
      return buildInput();
    },

    getValue() {
      return input.value;
    }
  };
}

Métodos del formulario

Cuando necesites interactuar con el formulario para obtener valores de otros campos o ir a otros pasos del formulario, puedes usar los siguientes métodos del formulario:

context.form.getId()

Devuelve un identificador único del formulario actual.

context.form.getRoot()

Devuelve el elemento HTML raíz del formulario actual.

context.form.goForward()

Pasa al siguiente paso del formulario.

context.form.goPrevious()

Pasa al paso anterior del formulario.

context.form.isValid()

Devuelve un valor booleano si el formulario supera todas las validaciones del cliente.

context.form.validate()

Valida los valores actuales de los campos en el cliente antes de continuar. Si un campo no supera la validación, aparecerá un mensaje de error.

context.form.getAllHiddenFields()

Devuelve un objeto con los valores de todos los campos ocultos.

context.form.setHiddenField(id, value)

Establece el valor de un campo oculto.
ParámetroDescripción
idString. El ID del campo oculto.
valueString. El valor del campo oculto.

context.form.getValues()

Devuelve un objeto con los valores de todos los campos, incluidos los campos ocultos.

context.form.getField(id)

Devuelve una instancia del campo especificado.
  • getNode() | true Devuelve el elemento HTML raíz del campo.
  • getValue() Devuelve el valor del campo.
  • setRequired(boolean) Marca o desmarca el campo como obligatorio.
ParámetroDescripción
id*String *. El valor del campo id.
Ejemplo:
const fullName = context.form.getField('full_name');
const fullNameValue = fullName.getValue();
console.log(fullNameValue); // John Doe
setRequired() solo marca o desmarca el campo como obligatorio en el cliente. Por ejemplo, si desmarca un campo como obligatorio, pero está marcado como obligatorio en la configuración del campo, el formulario devolverá un error si el campo no tiene ningún valor.

Ejemplos de campos personalizados

En las secciones siguientes se muestran ejemplos de campos personalizados que puedes agregar a tus formularios:

Campo personalizado de rango

Un campo personalizado que devuelve un valor dentro de un rango predeterminado.
Código fuente:
function rangeInput() {
  const input = document.createElement('input');
  input.type = 'range';
  input.min= '0';
  input.max= '100';
  input.value = '0';

  return {
    init() {
      return input;
    },

    getValue() {
      return input.value;
    }
  };
}

Campo personalizado de selector de color

Un campo personalizado que devuelve un valor hexadecimal de color.
Código fuente:
function colorInput() {
  const input = document.createElement('input');
  input.type = 'color';
  input.value = '#20c5a0';

  return {
    init() {
      return input;
    },

    getValue() {
      return input.value;
    },
  };
}

Campo personalizado de autocompletado con valores de una API

Un campo personalizado que devuelve un valor de autocompletado mediante una API de terceros.
Código fuente:
function textInputWithAutocomplete(context) {
  const input = document.createElement('input');
  input.type = 'text';

  function populateInputValue(json) {
    const { city } = json;

    input.value = city;
  }

  function fetchIpInfo() {
    const url = 'https://ipinfo.io/json';
    fetch(url)
      .then((res) => res.json())
      .then((json) => populateInputValue(json));
  }

  return {
    init() {
      fetchIpInfo();
      return input;
    },

    getValue() {
      return input.value;
    },
  };
}

Campo personalizado con lista desplegable dinámica y valores de una API

Un campo personalizado que devuelve un valor de una lista desplegable dinámica mediante una API de terceros.
Código fuente:
function dynamicDropdown() {
  const select = document.createElement('select');
  select.classList.add('af-stringField-input');

  function buildOption(data) {
    const { name: { first } } = data;
    const option = document.createElement('option');
    option.value = first;
    option.innerText = first;

    return option;
  }

  function populateNames(json) {
    const { results } = json;

    results.forEach((o) => {
      const option = buildOption(o);

      select.appendChild(option);
    });
  }

  function fetchNames() {
    const url = 'https://randomuser.me/api/?results=10&inc=name';
    fetch(url)
      .then((res) => res.json())
      .then((json) => populateNames(json));
  }

  return {
    init() {
      fetchNames();
      return select;
    },

    getValue() {
      return select.value;
    },
  };
}

Campo personalizado de entrada dinámica con un botón (+) para añadir más campos

Un campo personalizado que permite a los usuarios añadir más campos.
Código fuente:
function DynamicInputs(context) {
  const DEFAULT_INITIAL_INPUTS = 2;
  const DEFAULT_PLACEHOLDER = 'jane.doe@example.com';
  const DEFAULT_ADD_BUTTON_TEXT = 'Add new item';
  const DEFAULT_INPUT_TYPE = 'email';
  const STATE_VALUE = {};
  const FIELD_ID = context.custom.createUid();
  let UUID_COUNTER = 0;
  let INPUTS_COUNTER = 0;

  const container = document.createElement('div');

  const inputsContainer = document.createElement('div');
  container.appendChild(inputsContainer);

  function buildAddNewItem() {
    const config = context.custom.getParams();
    const { add_button_text } = config;

    const ADD_BUTTON_TEXT = add_button_text || DEFAULT_ADD_BUTTON_TEXT;

    const addInputButton = document.createElement('button');
    addInputButton.type = 'button';
    addInputButton.classList.add('af-dynamic-input-add-button');
    addInputButton.id = `${FIELD_ID}_add-input-button`;
    addInputButton.onclick = buildInputContainer.bind(this);

    const addInputButtonIcon = document.createElement('span');
    addInputButtonIcon.classList.add('af-button', 'af-dynamic-input-add-button-icon');
    addInputButtonIcon.innerText = '+';

    const addInputButtonText = document.createElement('span');
    addInputButtonText.classList.add('af-dynamic-input-add-button-text');
    addInputButtonText.innerText = ADD_BUTTON_TEXT;

    addInputButton.appendChild(addInputButtonIcon);
    addInputButton.appendChild(addInputButtonText);
    container.appendChild(addInputButton);
  }

  function removeInput(container, input) {
    delete STATE_VALUE[input.name];
    container.remove();
  }

  function buildRemoveInputButton(container, input) {
    const button = document.createElement('button');
    button.type = 'button';
    button.classList.add('af-button', 'af-dynamic-input-remove-button');
    button.innerText = '-';
    button.onclick = removeInput.bind(this, container, input);

    INPUTS_COUNTER--;

    return button;
  }

  function buildInput() {
    const config = context.custom.getParams();
    const { placeholder, input_type } = config;

    const PLACEHOLDER = placeholder || DEFAULT_PLACEHOLDER;
    const INPUT_TYPE = input_type || DEFAULT_INPUT_TYPE;

    const input = document.createElement('input');
    input.type = INPUT_TYPE;
    input.placeholder = PLACEHOLDER;
    input.classList.add('af-stringField-input');
    input.name = `${FIELD_ID}_${UUID_COUNTER}`;
    input.id = input.name;
    input.addEventListener('change', () => {
      STATE_VALUE[input.name] = input.value;
    });

    UUID_COUNTER++;

    return input;
  }

  function buildInputContainer() {
    const container = document.createElement('div');
    container.classList.add('af-dynamic-input-container');

    const input = buildInput();
    container.appendChild(input);

    const removeButton = buildRemoveInputButton(container, input);
    container.appendChild(removeButton);

    inputsContainer.appendChild(container);

    INPUTS_COUNTER++;
  }

  function initComponent() {
    const config = context.custom.getParams();
    const { initial_inputs } = config;

    const INITIAL_INPUTS = initial_inputs || DEFAULT_INITIAL_INPUTS;
    INPUTS_COUNTER = INITIAL_INPUTS

    for (let i = 0; i < INITIAL_INPUTS; i++) {
      buildInputContainer();
    }
  }

  function blockFields(value) {
    const inputKeys = Object.keys(STATE_VALUE);

    inputKeys.forEach((o) => {
      const selector = document.getElementById(o);
      selector.disabled = value;
    });
  }

  return {
    init() {
      buildAddNewItem();
      initComponent();
      return container;
    },

    block() {
      blockFields(true);
    },

    unblock() {
      blockFields(false);
    },

    getValue() {
      return Object.values(STATE_VALUE);
    },
  };
}
Código CSS:
.af-button.af-dynamic-input-remove-button {
  width: 48px;
  color: var(--button-font-color);
  background: var(--button-background-color);
}

.af-dynamic-input-container {
  display: flex;
  margin-bottom: var(--spacing-1);
}

.af-dynamic-input-container input {
  margin-right: var(--spacing-1);
}

button.af-dynamic-input-add-button {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  margin-top: var(--spacing-1);
  display: inline-flex;
  cursor: pointer;
  align-items: center;
}

.af-button.af-dynamic-input-add-button-icon {
  background: var(--primary-color);
  width: 24px;
  padding: 0;
  height: 24px;
  border-radius: .3em;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
  margin-right: var(--spacing-1);
}

.af-dynamic-input-add-button-text {
  color: var(--label-font-color);
  font-size: var(--label-font-size);
}

.af-dynamic-input-add-button:focus {
  outline: none;
}

.af-dynamic-input-add-button:hover .af-button {
  transition: filter var(--transition-normal), box-shadow var(--transition-normal);
  filter: brightness(1.1);
  box-shadow: 0 0 0 var(--outline-width) var(--outline-color), 0px 4px 8px -4px var(--shadow-color), 0px 16px 24px var(--shadow-color);
}

.af-dynamic-input-add-button:focus .af-button {
  box-shadow: 0 0 0 var(--outline-width) var(--outline-color), 0px 4px 8px -4px var(--shadow-color), 0px 16px 24px var(--shadow-color);
}