Hay determinadas ocasiones en las que el negocio de la aplicación requiere que un cliente pueda gestionar los atributos solicitados en determinados procesos que de antemano no conoce o son muy cambiantes. Por ejemplo, en los flujos de inscripción o metadatos a encuestados, en ambos casos me he encontrado con que la casuística de atributos es cambiante. Dependiendo de los datos disponibles del usuario más los de negocio, se puede cambiar la experiencia del usuario y mejorarla ofreciendo servicios personalizados; como puede ser a través de una pregunta a un usuario que se inscribe en un curso presencial desde una IP a más de 100 kilómetros de la sede del evento. Si necesita transporte al centro o alojamiento si el curso es de más de dos días.
Para ello no necesitamos software pesado ni “ensuciar” nuestras entidades con atributos cambiantes, que además requieren que intervenga un programador cada vez que se necesite un nuevo atributo. Simplemente, utilizando formularios dinámicos permitiremos a nuestros clientes expandir los suyos sin alterar la estructura ni el flujo base de la captación del proceso.
Para ello hemos utilizado schemaform. Es un visualizador de formularios como módulo de Angular a partir de una definición en json schema del formulario que necesitemos.
Una vez agregada la dependencia al proyecto (lo prefiero como dependencia de bower bower install angular-schema-form –save):
tendremos disponibles las directivas sf*, esta directiva gestiona cuatro atributos básicos que almacenaremos en la base de datos relacionada con nuestra entidad de negocio, en este caso una inscripción:
- sf-schema, objeto json schema de la definición del formulario.
- sf-form particularización del formulario, cambios que se aplican a la definición, aunque estos cambios también se pueden especificar en sf-schema
- sf-model, modelo donde se bindean las respuestas del formulario.
- sf-options, opciones de configuración como validaciones y feedback.
Dado que la finalidad es tener atributos dinámicos en nuestros formularios, los valores del sf-scheme y sf-form los obtendremos de la configuración del cliente en la aplicación de backoffice que los enviará al API de la aplicación, para ello diseñamos una pantalla donde se pueden agregar distintos tipos de campos y sus propiedades:
Por defecto la librería permite los campos de tipo fieldset, section, actions, text, textarea, number, password, checkbox, checkboxes, select, submit, button, radios, radios-inline, radiobuttons, help, template, tab, array y tabarray.
En el ejemplo citado habilitamos fieldset, textarea, checkboxes y select, la estructura básica la guardamos en una constante que nos ayudará a generar el json schema del campo:
[js].constant(‘INPUTFIELDTYPES’, {STRING: {
typeTitle: ‘Texto’,
schema: { type: "string", title: "" },
value: ‘STRING’
},
NUMBER: {
typeTitle: ‘Número’,
schema: { type: "number", title: "" },
value: ‘NUMBER’
},
SELECT: {
typeTitle: ‘Selector’,
schema: { type: "string", enum: [] },
value: ‘SELECT’
},
CHECKBOX: {
typeTitle: ‘Checkbox’,
schema: { type: "boolean" },
value: ‘CHECKBOX’
},
TEXTAREA: {
typeTitle: ‘Área de texto’,
schema: {
type: ‘string’,
title: »,
‘x-schema-form’: {
type: ‘textarea’,
fieldHtmlClass: ‘textarea-rows’
}
},
value: ‘TEXTAREA’
}
});
[/js]
Ahora definimos en nuestro controlador las variables para utilizar y crear el formulario. Respecto a las opciones solo se ha requerido que el campo sea obligatorio o no, esto será el valor del atributo sf-options.
[js] var formOptions = {validationMessage: {
302: ‘Campo obligatorio’
},
feedback: "{ ‘is-required’: form.required && !hasSuccess() && !hasError() ,’has-success’: hasSuccess(), ‘has-error’: hasError() }"
},
[/js]
Para el modelo y esquema partimos de un objeto con vacío:
[js] vm.questionsExtra = {formOptions: formOptions
schema: {
type: "object",
properties: {}
},
form: ["*"],
modelForm: {}
};[/js]
En la visualización de los campos que va agregando el usuario, utilizaremos una lista de atributos vm.fieldsList, donde guardaremos los campos que el usuario va configurando, a partir de la cual generamos el esquema del formulario que será el valor que almacenemos. También permitimos al usuario que introduzca un nombre y una descripción para el formulario que dé contexto a los atributos que va a configurar. Se han utilizado las directivas de angular materials para su visualización, ejemplo de recorrido de los atributos y renderizado por tipo de campo:
[html] <div id="fieldList" layout="column" ng-repeat="elem in edVm.fieldsListAttendant" flex><md-input-container class="md-block" flex>
<label class="md-required">Nombre del campo</label>
<input name="fieldName[$index]" ng-model="elem.title" required ng-model-options="{debounce: 400}" ng-change="edVm.generateFieldSchema(elem, {{elem}}, edVm.edition.jsonSchemaAttendant)" ng-readonly="!edVm.editMode.edit"></input>
<div ng-messages="edVm.formAssistantCreator.fieldName[$index].$error">
<div ng-message="required">El campo es requerido</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label class="md-required">Tipo</label>
<md-select name="fieldType[$index]" ng-model="elem.type" ng-required="true" ng-change="edVm.generateFieldSchema(elem, {{elem}}, edVm.edition.jsonSchemaAttendant)" ng-disabled="!edVm.editMode.edit">
<md-option ng-repeat="item in edVm.inputTypes" ng-value="item.value">{{item.typeTitle}}</md-option>
</md-select>
<div ng-messages="edVm.formAssistantCreator.fieldType[$index].$error">
<div ng-message="required">El campo es requerido</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<md-checkbox ng-model="elem.required" aria-label="requerido" ng-change="edVm.generateFieldSchema(elem, {{elem}}, edVm.edition.jsonSchemaAttendant)" ng-disabled="!edVm.editMode.edit">Requerido</md-checkbox>
</md-input-container>
</div>
[/html]
Para las opciones de tipo selector utilizamos el md-chips de material angular:
[html] <div ng-if="elem.type === ‘SELECT’" flex><md-chips ng-model="elem.enum" md-on-add="edVm.generateFieldSchema(elem, null, edVm.edition.jsonSchemaAttendant) && edVm.redrawForm()" md-on-remove="edVm.generateFieldSchema(elem, null, edVm.edition.jsonSchemaAttendant) && edVm.redrawForm()" placeholder="Añade opción y pulsa ‘enter’" secondary-placeholder="+Opción y pulsa ‘enter’" readonly="!edVm.editMode.edit"></md-chips>
</div>
[/html]
Tanto al añadir, eliminar o modificar atributos debemos regenerar el esquema que será el atributo que guardemos, básicamente consiste en guardar en schema.properties los atributos del field seleccionado:
[js] function generateFieldSchema(field, oldField, schema) {if (field.type && field.title) {
var transformedName = field.title.replace(/\s+/g, ‘_’).toLowerCase();
if (oldField && oldField.title && oldField.title !== » && field.title !== oldField.title) {
var transformedOldName = oldField.title.replace(/\s+/g, ‘_’).toLowerCase();
var jsonStringfied = angular.toJson(schema.properties);
var newjsonStringfied = jsonStringfied.replace(transformedOldName, transformedName);
schema.properties = angular.fromJson(newjsonStringfied);
}
schema.properties[transformedName] = angular.copy(INPUTFIELDTYPES[field.type].schema);
schema.properties[transformedName].title = field.title;
schema.properties[transformedName].required = field.required;
if (field.enum && field.enum.length > 0) {
schema.properties[transformedName].enum = field.enum;
}
}
}
[/js]
Ejemplo de esquema resultado:
[plain]{"title":"Un poco más de información",
"type":"object",
"properties":{
"necesito_alojamiento":{
"type":"boolean",
"title":"Necesito alojamiento",
"default":false
},
"me_quedo_a_comer":{
"type":"boolean",
"title":"Me quedo a comer",
"default":false
},
"observaciones":{
"type":"string",
"title":"Observaciones",
"x-schema-form":{
"type":"textarea",
"fieldHtmlClass":"textarea-rows"
}
},
"participa_como":{
"type":"string",
"enum":[
"Ponente",
"Oyente",
"Técnico",
"Técnico auxiliar"
],
"title":"Participa como"
}
}
}[/plain]
Para la previsualización del formulario que está creando el usuario utilizamos la directiva de schemeform, que nos renderiza el formulario a partir del esquema que hemos creado con las opciones introducidas:
[html] <div sf-schema="vm.edition.jsonSchema" sf-form="vm.questionsExtra.form" sf-model="vm.questionsExtra.modelForm" sf-options="vm.questionsExtra.formOptions"></div>[/html]
Y así es como logramos crear formularios dinámicos en angular con chemaform.io. Te invito a que te suscribas a nuestro blog y así puedas estar al tanto de todas las novedades, experimentos, pruebas e ideas que iremos subiendo toda la tripulación Belike.
Muy buen tutorial, Tienen el código fuente en algún lugar ? 🙂