Grails command databinding alternativa 2
Crear un command que contenga las propiedades de todos los paneles en la vista, para evitar el error que se pierdan valores de la alternativa 1, se envolverán todos los paneles dentro del mismo form
.
Características:
constraints
para todas las propiedades sin importar el tipo de panel al que se presione el botón guardar.Desventajas de la alternativa:
Al hacer submit de un panel, se enviará la información modificada no solo de ese panel, sino de todos los paneles, lo cual no es el comportamiento esperado.
Esta alternativa es la utilizada por prestashop en su backend, está corroborado en su código fuente que todos los paneles están envueltos en un solo form
. Prestashop lo utilizan miles de personas, seguro que lo programaron así para hacer muy simple el código.
Otro problema es el principio open/close, cuando se agregue un nuevo panel es necesario modificar la clase que ya tenía la funcionalidad probada.
** Ventajas**
Esta es la alternativa MÁS SIMPLE Y CLARA DE TODAS.
Tal vez esta alternativa se podría componer si con la interfaz se hiciera un solo botón de guardar y separar la info en múltiples paneles, algo como lo que se hace en swing.
Esta alternativa sería más simple programarla con un command que tenga commands dentro? Tal vez si para paneles con lógica de validación avanzada.
Si son demasiados los paneles de la página, se podrían introducir tabs. Cada que presionen un tab mostrará el nuevo panel.
%{-- Usuario registrado --}%
<div class="form-group has-feedback">
<label class="control-label col-sm-3">
<t:labelText label="Usuario registrado" tooltip="Indica si el socio ya ha dado de alta su usuario/password en el sistema" />
</label>
<div class="col-sm-4">
<t:onoff name="onoff1" options="{onText:'Si', offText:'No', size:'small', disabled:true}"
value="${preferences?.isUsuarioRegistrado()}"/>
</div>
</div>
%{--Nombre de usuario--}%
<div class="form-group ${hasErrors(bean: preferences, field: 'username', 'has-error')} has-feedback">
<label for="username" class="control-label col-sm-3">
<t:labelText required="true" tooltip="Indique el username con el que ingresará el socio a la aplicación"
label="Nombre de usuario" error="${hasErrors(bean: preferences, field: 'username', 'true')}"/>
</label>
<div class="col-sm-4">
<g:textField name="username" value="${preferences?.username}"/>
</div>
</div>
%{--Password--}%
<div class="form-group ${hasErrors(bean: preferences, field: 'password', 'has-error')} has-feedback">
<label for="password" class="control-label col-sm-3">
<t:labelText required="true" tooltip="Indique el password con el que ingresará el socio a la aplicación"
label="Password" error="${hasErrors(bean: preferences, field: 'password', 'true')}"/>
</label>
<div class="col-sm-4">
<g:passwordField name="password" value="${preferences?.password}" autocomplete="new-password"/>
</div>
</div>
%{-- Idioma --}%
<div class="form-group ${hasErrors(bean: preferences, field: 'idioma', 'has-error')} has-feedback">
<label for="idioma.id" class="control-label col-lg-3">
<t:labelText required="true" label="Idioma de preferencia" tooltip="idioma.tooltip" error="${hasErrors(bean: preferences, field: 'idioma', 'true')}"/>
</label>
<div class="col-lg-4 ">
<g:select name="idioma.id" from="${us.incorpora.sigrem.Lenguaje.list()}" optionKey="id"
noSelection="[null:message(code: 'idioma.notselected.label')]"
optionValue="nombre" value="${preferences?.idioma?.id}" />
</div>
</div>
package us.incorpora.sigrem.commands
import grails.validation.Validateable
import us.incorpora.sigrem.AccesoService
import us.incorpora.sigrem.Lenguaje
import us.incorpora.sigrem.Membresia
import us.incorpora.sigrem.commands.config.PanelAcceso
/**
* @author iberck
*/
@Validateable
class SocioPreferences {
// PANEL PREFERENCIAS
Lenguaje idioma
// PANEL ACCESO
AccesoService accesoService
Membresia membresia
String username
String password
void build(Membresia membresia) {
buildPanelPreferencias(membresia)
buildPanelAcceso(membresia)
}
void buildPanelPreferencias(Membresia membresia) {
idioma = membresia.idioma
}
void buildPanelAcceso(Membresia membresia) {
this.membresia = membresia
username = membresia.accesoSocio?.username
password = membresia.accesoSocio?.password
}
boolean isUsuarioRegistrado() {
return membresia.accesoSocio != null
}
static constraints = {
idioma nullable: false
username blank: false, validator: { String value, SocioPreferences _this ->
// Cuando cambia el username, siempre validar que esté disponible
// Esta validación aplica para socios registrados cuando cambian el username y para socios no registrados.
String oldUserName =_this.membresia.accesoSocio?.username
if (value != oldUserName && !_this.accesoService.isUserNameAvailable(value)) {
return "acceso.usuarioRepetido"
}
}
password blank: false, minSize: 6
}
}
class SocioController {
SocioService socioService
def preferences() {
def membresia = GrailsUtils.currentUser.membresia
SocioPreferences preferences = new SocioPreferences()
preferences.build(membresia)
render view: "preferences", model: [preferences: preferences]
}
def updatePreferences(SocioPreferences preferences) {
if (preferences.hasErrors()) {
render view: "preferences", model: [preferences: preferences]
return
}
socioService.savePreferences(preferences)
flash.success = "Se actualizó correctamente la información"
redirect(action: "preferences")
}
}
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="adminlte">
<t:assetsLabelText/>
<t:assetsOnOff/>
</head>
<body>
%{--BodyHeader--}%
<header id="Header" class="page-head">
<content tag="3">active</content>
<content tag="3.1">active</content>
<h2 class="page-title">
${message(code: 'preferences.bodyheader.title')}
</h2>
<t:breadcrumbs
data="${['Configuración': createLink(action: 'preferences'), 'Preferencias': createLink(action: 'preferences')]}"/>
<div class="page-bar toolbarBox">
<g:if test="${!layout_nosecondarymenu}">
<div class="btn-toolbar">
<ul id="toolbar-nav" class="nav nav-pills pull-right collapse navbar-collapse">
</ul>
</div>
</g:if>
</div>
</header>
<g:form method="post" class="form-horizontal" role="form" action="updatePreferences">
<g:hiddenField name="membresia.id" value="${preferences.membresia.id}"/>
<g:hasErrors bean="${preferences}">
<div class="alert alert-danger">
<g:renderErrors bean="${preferences}" as="list"/>
</div>
</g:hasErrors>
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Preferencias</h3>
</div>
<div class="box-body">
<g:render template="form_preferences" model="[preferences: preferences]"/>
</div>
<div class="box-footer">
%{--Actualizar--}%
<t:onceSubmit name="submit1" class="btn btn-default pull-right">
<i class="fa fa-save fa-2x"></i> </br>
<g:message code="default.button.save.label"/>
</t:onceSubmit>
</div>
</div>
%{--Acceso--}%
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Acceso</h3>
</div>
<div class="box-body">
<g:render template="panel_config_acceso" model="[preferences: preferences]"/>
</div>
<div class="box-footer">
%{--Actualizar--}%
<t:onceSubmit name="submit2" class="btn btn-default pull-right">
<i class="fa fa-save fa-2x"></i> </br>
<g:message code="default.button.save.label"/>
</t:onceSubmit>
</div>
</div>
</g:form>
</body>
</html>
@Transactional
class SocioService {
AccesoService accesoService
def savePreferences(SocioPreferences preferences) {
def membresia = preferences.membresia
membresia.idioma = preferences.idioma
accesoService.saveAcceso(membresia, preferences.username, preferences.password)
membresia.save(failOnError: true)
}
}