Een introductie tot op maat gemaakte Liferay Forms hero image
CONTENT & SAMENWERKINGLIFERAY
19/11/2019 • Koen Olaerts

Een introductie tot op maat gemaakte Liferay Forms

Sinds mensenheugenis verzamelen mensen van alles en nog wat: stripboeken, videospelletjes, postzegels, oud geld en zelfs mineralen in gesteente. geïnteresseerd blijft.Maar... tijden veranderen en de meesten van ons zijn niet langer geïnteresseerd in het verzamelen van mineralen. In plaats daarvan verzamelen wij de persoonlijke informatie en meningen van andere mensen. Met de komst van Liferay 7.0, heeft Liferay ons gezegend met een tool die dit mogelijk maakt. Deze blogpost geeft een overzicht van Liferay Forms en legt uit hoe je ze naar eigen behoefte aan kunt passen!

Enter Liferay Forms!

De genadige opperheren bij Liferay hebben onze wens om data te verzamelen erkend en hebben ons een handleiding aangeleverd over hoe we informatie van gebruikers zouden moeten verzamelen. Onze datahongerige magen zijn echter niet gauw tevreden als het gaat om het verzamelen van informatie van anderen. Dus wat heeft Liferay Forms ons te bieden? Liferay Forms is een tool waarmee gebruikers dynamische en flexibele enquêtes of vragenlijsten kunnen maken. Standaard biedt Liferay Forms een breed scala aan veldtypen en instellingen. Laten we ze eens bekijken!

Standaard veldtypes

Veldtype   Gebruikt om
Form Text

 

Geeft de gebruiker informatie over de (noodzaak van het) formulier. Geen input noodzakelijk.
Text Field

 

Vul één of meer regels platte tekst in
Selecteer uit de lijst

 

Selecteer een item uit een voorgeconfigureerde vervolgkeuzelijst
Enkele selectie

 

Kies een enkele optie
Datum

 

Voer een datum in
Enkele checkbox

 

Bevestig een bewering
Meervoudige selectie

 

Selecteer één of meerdere opties

Standaardinstelling veldtype

Setting

Form Text

Text Field

Select from list

Single Selection

Date

Single Checkbox

Multiple Selection

Required field X X X X X X
Help text X X X X X X
Show label X

X X X X X
Placeholder X
Predefined value X X X X X X
Field validation X
Field visibility expression X X X X X X X
Repeatable X X X X
Prefill from external provider X

Daarnaast zijn er enkele algemene instellingen die voor elk formulier kunnen worden geconfigureerd, zoals:

  • een CAPTCHA inschakelen,
  • doorverwijzen naar een andere URL nadat een formulier met succes is ingediend,
  • het versturen van een e-mailbericht naar beheerders wanneer een formulier wordt ingediend,
  • één of meer pagina's per formulier gebruiken,
  • en meer!

Aanpasbare layout

Om de gebruiker ervan te overtuigen zijn kostbare persoonlijke informatie vrijwillig af te staan, moeten formulieren er zo aantrekkelijk mogelijk uitzien. Daartoe heeft Liferay een manier voorzien om de volledige 'look and feel' van het formulier aan te passen, met inbegrip van het lettertype en de kleur. De interessantste functie is het gebruik van een aanpak met multikolommen. Dit maakt het mogelijk om meerdere velden op een enkele rij te zetten en dit aan te passen voor elke volgende rij, zoals te zien is in onderstaande afbeelding.

Houd dit je niet entertained?

Om heel eerlijk te zijn, mij ook niet. Liferay Forms is zeer zeker een upgrade in vergelijking met zijn voorganger in 6.2 en biedt een manier om snel een dynamische enquête op te stellen. Maar de standaardinstellingen en veldtypes voelen echter nogal beperkt aan en laten te wensen over. Sommige dingen die ik persoonlijk mis zijn:

  • het formulier kan niet vooraf worden ingevuld met data die al in de database zijn opgeslagen,
  • externe dataproviders kunnen alleen worden gekozen in het veld 'Selecteren uit lijst'.

Door gebruik te maken van waardevolle data die al in de databank zijn opgeslagen, wordt de gebruiker overbodig werk bespaard. Het confronteert hen ook met de gruwelijke waarheid dat we al zoveel over hen weten.

Aangepaste velden maken voor Liferay forms

Op een dag kwam een tamelijk grote oude(re) man, met een grijze puntmuts naar me toe en vroeg me precies dat te doen: een veld automatisch vullen met data die al in de database zijn opgeslagen. Ik aanvaardde genadig deze zoektocht en zo begon mijn avontuur met aangepaste formuliervelden.

Waar begin je?

De genadige opperheren van Liferay hebben ons opnieuw gezegend met een geschenk. Deze keer hebben ze ons documentatie gegeven. Om vertrouwd te raken met het concept van het maken van een aangepast formulierveld, ben ik rechtstreeks in hun archieven gedoken op zoek naar nuttige informatie. Omdat ik zo'n lieve, aardige ziel ben, wil ik graag met jullie delen wat ik heb gevonden:

Een formulierveldmodule maken kan lastig en vermoeiend zijn. Gelukkig voor ons is er een Maven archetype beschikbaar om het werk voor ons te doen:

mvn archetype:generate \
    -DarchetypeGroupId=com.liferay \
    -DarchetypeArtifactId=com.liferay.project.templates.form.field \
    -DgroupId=com.liferay \
    -DartifactId=my-form-field-project \
    -Dpackage=com.liferay.docs \
    -Dversion=1.0 \
    -DclassName=MyFormField \
    -Dauthor=Joe Bloggs \
    -DliferayVersion=7.0

Dit archetype maakt een module aan met de juiste structuur, zoals beschreven in anatomie van een formulierveldmodule. Het bouwen en implementeren van deze module voegt het nieuw aangemaakte veld toe aan Liferay Forms.

Opmerking: het bouwen van deze module zou je sjabloon beschreven in my-form-field.soy moeten compileren en een bestand genereren met de naam 'my-form-field.soy.js'. Als dit niet het geval is, overweeg dan om het bestand handmatig te compileren en met een online compiler toe te voegen als een tijdelijke workaround.

Nu we een werkende module hebben, zijn we vrij om het sjabloon en de instellingen van ons veld (of velden) naar eigen goeddunken aan te passen.

Vooraf invullen van Liferay Forms

Stel je voor; je bent de trotse eigenaar van een prima zaak met de naam 'The Pizza Place' en je wilt je klantervaring verbeteren. Natuurlijk wil je dat je klanten online een pizza kunnen bestellen. Maar wat als we dat nog verder doortrekken en de bestelling gedeeltelijk voor hen zouden invullen? Voordat we op gaan scheppen over wat we allemaal kunnen, hun formulier volledig invullen en de pizza voor hen kiezen, beginnen we met iets eenvoudigs: hun naam.

Dat is precies wat de naamveld-module doet. Het gebruikt de naam van de ingelogde gebruiker en vult die automatisch in op het formulier. Desondanks kan de gebruiker nog steeds de naam wijzigen als dit nodig is. Om deze geweldige prestatie te kunnen leveren, moeten we wat aanpassingen doen. Ten eerste, als je kijkt naar de NameFieldRenderer, zie je de methode 'populateOptionalContext(... , ... , ...)', die we gebruiken om de gebruikersnaam door te geven aan het sjabloon. Met behulp van de DDMFormFieldRenderingContext hebben wij toegang tot de ThemeDisplay die ons op zijn beurt weer toegang geeft tot de ingelogde User.

@Override
protected void populateOptionalContext(Template template, DDMFormField ddmFormField, DDMFormFieldRenderingContext ddmFormFieldRenderingContext) {
        HttpServletRequest httpServletRequest = ddmFormFieldRenderingContext.getHttpServletRequest();
	ThemeDisplay themeDisplay = (ThemeDisplay) httpServletRequest.getAttribute(WebKeys.THEME_DISPLAY);
 
	User user = themeDisplay.getUser();
 
	template.put("username", user.getFullName());
}

Vervolgens moeten we toegang krijgen tot de gebruikersnaam, die in ons sjabloon beschreven staat in name-field.soy. Voeg gewoon de 'username' toe aan de lijst van parameters en geef die door aan het waarde-attribuut van het invoerveld.

Opmerking: het naam-attribuut van het invoerveld wordt samen met de waarde gebruikt om de waarde op te slaan en mag NIET worden verwijderd!

{namespace ddm}
 
/**
* Prints the form field.
*
* @param label
* @param name
* @param required
* @param showLabel
* @param tip
* @param username
*/
{template .NameField}
	<div class="form-group name-field" data-fieldname="{$name}">
		{if $showLabel}
			<label class="control-label">
				{$label}
 
				{if $required}
					<span class="icon-asterisk text-warning"></span>
				{/if}
			</label>
 
			{if $tip}
				<p class="liferay-ddm-form-field-tip">{$tip}</p>
			{/if}
		{/if}
		<br>
		<input class="field form-control" id="{$name}" name="{$name}" type="text" value="{$username}">
	</div>
{/template}

Gemakkelijk toch, of niet?

Ho, niet zo snel! Je hebt zojuist de valkuil in Liferay geactiveerd!

Om redenen die mijn huidige begrip te boven gaan, overschrijft Liferay het sjabloon met de component die we hebben beschreven in de naamveldcomponent. Aangezien we niet kunnen weten wat de gebruikersnaam is aan de client-kant, zal de gebruikersnaam die door de server wordt aangeleverd worden overschreven met wat wij definiëren als de waarde in deze component. In dit geval hebben we die waarde niet gedefinieerd, dus onze gebruikersnaam zou overschreven worden door een lege string. Meer informatie over deze component is te vinden in de documentatie van Liferay.

AUI.add(
	'name-field',
	function(A) {
		var NameField = A.Component.create(
			{
				ATTRS: {
					username:{}
				},
 
				EXTENDS: Liferay.DDM.Renderer.Field,
 
				NAME: 'name-field',
 
				prototype: {
					getTemplateContext: function() {
						var instance = this;
 
						return A.merge(
							NameField.superclass.getTemplateContext.apply(instance, arguments),
							{
								username: fetchUsername(instance)
							}
						);
					}
				}
 
			}
		);
 
		Liferay.namespace('DDM.Field').NameField = NameField;
	},
	'',
	{
		requires: ['liferay-ddm-form-renderer-field']
	}
);
 
function fetchUsername(instance) {
	return instance.get('container') == undefined ? '' : (instance.getInputNode() != null ? instance.getValue() : '');
}

Maar we kunnen via de objectinstantie toegang krijgen tot de waarde van het sjabloon, voordat het door onze component wordt overschreven, en het zo doorgeven aan onze eigen component. Dat is precies wat de functie 'fetchUsername(instance)' doet. Er zijn extra controles nodig om ervoor te zorgen dat het sjabloon niet wordt onderbroken wanneer je jouw formulier configureert in het configuratiescherm. Dit voelt op dit moment een beetje vreemd, maar tot nu toe heb ik nog geen andere manier gevonden om deze kleine ergernis te omzeilen.

Wat als mijn data ergens anders is opgeslagen?

Stel dat je data van een externe provider wilt gebruiken, hoe pakken we dat dan aan? Zoals eerder gezegd wordt het sjabloon dat door de server wordt doorgegeven overschreven door onze aangepaste component. Als we dit in ons voordeel benutten, kunnen we de data van de externe provider aan de client-kant ophalen en ze doorgeven aan onze component, zoals we hebben laten zien in de component e-mailveld.

Let op: Voor deze demonstratie heb ik een eindpunt voorzien, in de custom-form-fields-endpoints zorgt dat (met een user-Id) ervoor dat het e-mailadres van de gebruiker wordt opgehaald.

AUI.add(
	'email-field',
	function(A) {
		var EmailField = A.Component.create(
			{
				ATTRS: {
					email: {
						value: ''
					},
					prefill: {}
				},
 
				EXTENDS: Liferay.DDM.Renderer.Field,
 
				NAME: 'email-field',
 
				prototype: {
					getTemplateContext: function() {
						var instance = this;
 
						return A.merge(
							EmailField.superclass.getTemplateContext.apply(instance, arguments),
							{
								email: renderEmail(instance)
							}
						);
					}
				}
 
			}
		);
 
		Liferay.namespace('DDM.Field').EmailField = EmailField;
	},
	'',
	{
		requires: ['liferay-ddm-form-renderer-field']
	}
);
 
function renderEmail(instance) {
	return instance.get('prefill') ? fetchEmail() : '';
}
 
function fetchEmail() {
	var xmlHttp = new XMLHttpRequest();
	var url = "http://localhost:8080/o/custom-forms/forms/" + getLiferayUserId() + "/email";
	xmlHttp.open( "GET", url, false );
	xmlHttp.send( null );
 
	return xmlHttp.responseText
}
 
 
function getLiferayUserId() {
	return Liferay.ThemeDisplay.getUserId();
}

In dit scenario hoeven we ons niet bezig te houden met de EmailFieldRenderer en kunnen we gewoon een lege string doorgeven. Dat dient wel degelijk een doel: het zorgt ervoor dat het veld niet als 'null' wordt weergegeven, voordat het wordt overschreven door onze component.

Let op: Als je er de voorkeur aan geeft de oproep aan de server-kant te doen, kun je nog steeds de waarde doorgeven aan het sjabloon en de vorige aanpak gebruiken om het veld in te vullen.

@Override
protected void populateOptionalContext(Template template, DDMFormField ddmFormField, DDMFormFieldRenderingContext ddmFormFieldRenderingContext) {
   template.put("email", "");
}

Wat als de data-integriteit van de externe provider niet meer kan worden gegarandeerd?

Stel dat om een of andere reden de externe provider niet meer te vertrouwen is. In dat geval zou je dit veld niet meer automatisch in willen laten vullen. Dit kan worden bereikt met een enkele extra configureerbare instelling. In de EmailFieldTypeSettings moeten we een basisinstelling 'prefill' toevoegen aan de DDMFormLayout en deze definiëren als een boolean.

@DDMForm
@DDMFormLayout(
	paginationMode = com.liferay.dynamic.data.mapping.model.DDMFormLayout.TABBED_MODE,
	value = {
		@DDMFormLayoutPage(
			title = "%basic",
			value = {
				@DDMFormLayoutRow(
					{
						@DDMFormLayoutColumn(
							size = 12,
							value = {
									"label", "required", "tip", "prefill"
								}
							)
						}
					)
				}
			),
			@DDMFormLayoutPage(
				title = "%properties",
				value = {
					@DDMFormLayoutRow(
						{
							@DDMFormLayoutColumn(
								size = 12,
								value = {
									"dataType", "name", "showLabel", "repeatable",
									"type", "validation", "visibilityExpression"
								}
							)
						}
					)
				}
			)
		}
)
public interface EmailFieldTypeSettings extends DefaultDDMFormFieldTypeSettings {
 
	@DDMFormField(label = "%prefill")
	boolean prefill();
}

Als laatste, maar zeker niet minder belangrijk: we moeten een extra controle toevoegen aan onze component e-mailveld. Deze component bepaalt of data van de externe provider moeten worden opgehaald of niet.

function renderEmail(instance) {
	return instance.get('prefill') ? fetchEmail() : '';
}

Takeaway + afscheidscadeautje

En hiermee komt mijn korte avontuur in de wondere wereld van Liferay Forms ten einde. Hoewel het soms omslachtig aanvoelde, het me meer dan eens de wenkbrauwen deed fronsen en me een aantal fundamentele levensvragen deed stellen, voelt het wel of Liferay Forms het potentieel heeft om op een flexibele en uitbreidbare manier te doen waarvoor het ontworpen is: informatie en feedback van mensen ontvreemden en verzamelen.

Als beloning voor het feit dat je zo ver bent gekomen in deze blogpost, heb ik een bibliotheek aangemaakt die je vrij mag gebruiken. Het bevat de velden en eindpunten die in deze blog zijn besproken:

  • het automatisch ingevulde naamveld,
  • het automatisch ingevulde e-mailveld,
  • een aangepast Liferay eindpunt.

Bedankt voor het lezen!