2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
9 package eu
.etaxonomy
.cdm
.vaadin
.component
.common
;
11 import java
.util
.Arrays
;
12 import java
.util
.EnumSet
;
13 import java
.util
.List
;
15 import org
.vaadin
.viritin
.fields
.LazyComboBox
;
17 import com
.vaadin
.data
.Validator
.InvalidValueException
;
18 import com
.vaadin
.data
.fieldgroup
.BeanFieldGroup
;
19 import com
.vaadin
.data
.fieldgroup
.FieldGroup
;
20 import com
.vaadin
.data
.util
.BeanItem
;
21 import com
.vaadin
.server
.FontAwesome
;
22 import com
.vaadin
.server
.UserError
;
23 import com
.vaadin
.shared
.AbstractFieldState
;
24 import com
.vaadin
.ui
.Button
;
25 import com
.vaadin
.ui
.Component
;
26 import com
.vaadin
.ui
.CssLayout
;
27 import com
.vaadin
.ui
.Field
;
28 import com
.vaadin
.ui
.themes
.ValoTheme
;
30 import eu
.etaxonomy
.cdm
.api
.utility
.UserHelper
;
31 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
32 import eu
.etaxonomy
.cdm
.model
.agent
.AgentBase
;
33 import eu
.etaxonomy
.cdm
.model
.agent
.Person
;
34 import eu
.etaxonomy
.cdm
.model
.agent
.Team
;
35 import eu
.etaxonomy
.cdm
.model
.agent
.TeamOrPersonBase
;
36 import eu
.etaxonomy
.cdm
.persistence
.hibernate
.permission
.CRUD
;
37 import eu
.etaxonomy
.cdm
.service
.CdmFilterablePagingProvider
;
38 import eu
.etaxonomy
.cdm
.service
.UserHelperAccess
;
39 import eu
.etaxonomy
.cdm
.vaadin
.component
.ButtonFactory
;
40 import eu
.etaxonomy
.cdm
.vaadin
.event
.ToOneRelatedEntityReloader
;
41 import eu
.etaxonomy
.cdm
.vaadin
.util
.TeamOrPersonBaseCaptionGenerator
;
42 import eu
.etaxonomy
.cdm
.vaadin
.util
.converter
.CdmBaseDeproxyConverter
;
43 import eu
.etaxonomy
.cdm
.vaadin
.view
.name
.CachingPresenter
;
44 import eu
.etaxonomy
.vaadin
.component
.CompositeCustomField
;
45 import eu
.etaxonomy
.vaadin
.component
.EntityFieldInstantiator
;
46 import eu
.etaxonomy
.vaadin
.component
.ReloadableLazyComboBox
;
47 import eu
.etaxonomy
.vaadin
.component
.SwitchableTextField
;
48 import eu
.etaxonomy
.vaadin
.component
.ToManyRelatedEntitiesListSelect
;
51 * @author a.kohlbecker
55 public class TeamOrPersonField
extends CompositeCustomField
<TeamOrPersonBase
<?
>> {
57 private static final long serialVersionUID
= 660806402243118112L;
59 private static final String PRIMARY_STYLE
= "v-team-or-person-field";
61 private CssLayout root
= new CssLayout();
62 private CssLayout toolBar
= new CssLayout();
63 private CssLayout compositeWrapper
= new CssLayout();
65 private ReloadableLazyComboBox
<TeamOrPersonBase
> teamOrPersonSelect
= new ReloadableLazyComboBox
<TeamOrPersonBase
>(TeamOrPersonBase
.class);
67 private Button removeButton
= ButtonFactory
.REMOVE_ALL_ITEMS
.createButton();
68 private Button personButton
= new Button(FontAwesome
.USER
);
69 private Button teamButton
= new Button(FontAwesome
.USERS
);
71 // Fields for case when value is a Person
72 private PersonField personField
= new PersonField();
74 // Fields for case when value is a Team
75 private SwitchableTextField titleField
= new SwitchableTextField("Team (bibliographic)");
76 private SwitchableTextField nomenclaturalTitleField
= new SwitchableTextField("Team (nomenclatural)");
77 private ToManyRelatedEntitiesListSelect
<Person
, PersonField
> personsListEditor
= new ToManyRelatedEntitiesListSelect
<Person
, PersonField
>(Person
.class, PersonField
.class, "Team members");
79 private BeanFieldGroup
<Team
> fieldGroup
= new BeanFieldGroup
<>(Team
.class);
81 private TeamOrPersonBaseCaptionGenerator
.CacheType cacheType
;
83 protected List
<Component
> editorComponents
= Arrays
.asList(removeButton
, personButton
, teamButton
, teamOrPersonSelect
);
85 public TeamOrPersonField(String caption
, TeamOrPersonBaseCaptionGenerator
.CacheType cacheType
){
89 this.cacheType
= cacheType
;
90 teamOrPersonSelect
.setCaptionGenerator(new TeamOrPersonBaseCaptionGenerator
<TeamOrPersonBase
>(cacheType
));
93 addStyledComponent(teamOrPersonSelect
);
94 addStyledComponent(personField
);
95 addStyledComponent(titleField
);
96 addStyledComponent(nomenclaturalTitleField
);
97 addStyledComponent(personsListEditor
);
98 addStyledComponents(removeButton
, personButton
, teamButton
);
101 addSizedComponent(root
);
102 addSizedComponent(compositeWrapper
);
103 addSizedComponent(personField
);
104 addSizedComponent(titleField
);
105 addSizedComponent(nomenclaturalTitleField
);
106 addSizedComponent(personsListEditor
);
108 setConverter(new CdmBaseDeproxyConverter
<TeamOrPersonBase
<?
>>());
110 updateToolBarButtonStates();
117 protected Component
initContent() {
119 teamOrPersonSelect
.addValueChangeListener(e
-> {
120 setValue(teamOrPersonSelect
.getValue(), false, true);
122 teamOrPersonSelect
.setWidthUndefined();
124 removeButton
.addClickListener(e
-> {
125 teamOrPersonSelect
.clear();
126 setValue(null, false, true);
128 removeButton
.setDescription("Remove");
130 personButton
.addClickListener(e
-> {
131 setValue(Person
.NewInstance(), false, true);
132 resetReadOnlyComponents();
135 personButton
.setDescription("Add person");
136 teamButton
.addClickListener(e
-> {
137 setValue(Team
.NewInstance(), false, true);
138 resetReadOnlyComponents();
140 teamButton
.setDescription("Add team");
142 toolBar
.setStyleName(ValoTheme
.LAYOUT_COMPONENT_GROUP
+ " toolbar");
143 toolBar
.addComponents(teamOrPersonSelect
, removeButton
, personButton
, teamButton
);
145 compositeWrapper
.setStyleName("margin-wrapper");
146 compositeWrapper
.addComponent(toolBar
);
148 root
.setPrimaryStyleName(PRIMARY_STYLE
);
149 root
.addComponent(compositeWrapper
);
157 public Class
getType() {
158 return TeamOrPersonBase
.class;
161 private void updateToolBarButtonStates(){
163 TeamOrPersonBase
<?
> val
= getInternalValue();
164 boolean userCanCreate
= UserHelperAccess
.userHelper().userHasPermission(Person
.class, "CREATE");
166 teamOrPersonSelect
.setVisible(val
== null);
167 removeButton
.setVisible(val
!= null);
168 personButton
.setEnabled(userCanCreate
&& val
== null);
169 teamButton
.setEnabled(userCanCreate
&& val
== null);
176 protected void setInternalValue(TeamOrPersonBase
<?
> newValue
) {
178 TeamOrPersonBase
<?
> oldValue
= getValue();
179 super.setInternalValue(newValue
);
181 newValue
= HibernateProxyHelper
.deproxy(newValue
);
183 compositeWrapper
.removeAllComponents();
184 compositeWrapper
.addComponent(toolBar
);
186 if(newValue
!= null) {
188 if(Person
.class.isAssignableFrom(newValue
.getClass())){
190 compositeWrapper
.addComponent(personField
);
192 personField
.setValue((Person
) newValue
);
193 personField
.registerParentFieldGroup(fieldGroup
);
196 else if(Team
.class.isAssignableFrom(newValue
.getClass())){
197 // otherwise it a Team
198 compositeWrapper
.addComponents(titleField
, nomenclaturalTitleField
, personsListEditor
);
200 titleField
.bindTo(fieldGroup
, "titleCache", "protectedTitleCache");
201 nomenclaturalTitleField
.bindTo(fieldGroup
, "nomenclaturalTitle", "protectedNomenclaturalTitleCache");
202 fieldGroup
.setItemDataSource(new BeanItem
<Team
>((Team
)newValue
));
203 boolean readonlyState
= personsListEditor
.isReadOnly();
204 fieldGroup
.bind(personsListEditor
, "teamMembers"); // here personField is set readonly since setTeamMembers does not exist
205 personsListEditor
.setReadOnly(readonlyState
); // fixing the readonly state
207 personsListEditor
.registerParentFieldGroup(fieldGroup
);
210 setComponentError(new UserError("TeamOrPersonField Error: Unsupported value type: " + newValue
.getClass().getName()));
213 if(oldValue
!= null){
214 // value is null --> clean up all nested fields
215 // allow replacing old content in the editor by null
216 setReadOnlyComponents(false);
217 if(oldValue
instanceof Person
){
218 personField
.unregisterParentFieldGroup(fieldGroup
);
219 personField
.setReadOnly(false);
220 personField
.setValue((Person
) null);
222 titleField
.unbindFrom(fieldGroup
);
223 nomenclaturalTitleField
.unbindFrom(fieldGroup
);
224 fieldGroup
.unbind(personsListEditor
);
225 fieldGroup
.setItemDataSource((Team
)null);
226 personsListEditor
.registerParentFieldGroup(null);
227 personsListEditor
.setReadOnly(false);
228 personsListEditor
.setValue(null);
229 personsListEditor
.registerParentFieldGroup(null);
233 adaptToUserPermissions(newValue
);
234 updateToolBarButtonStates();
238 private void adaptToUserPermissions(TeamOrPersonBase teamOrPerson
) {
240 UserHelper userHelper
= UserHelperAccess
.userHelper();
241 boolean canEdit
= teamOrPerson
== null || !teamOrPerson
.isPersited() || userHelper
.userHasPermission(teamOrPerson
, CRUD
.UPDATE
);
243 getPropertyDataSource().setReadOnly(true);
244 setReadOnlyComponents(true);
253 protected void addDefaultStyles() {
254 // no default styles here
261 public FieldGroup
getFieldGroup() {
265 public Component
[] getCachFields(){
266 return new Component
[]{titleField
, nomenclaturalTitleField
};
270 * @return the teamOrPersonSelect
272 public LazyComboBox
<TeamOrPersonBase
> getTeamOrPersonSelect() {
273 return teamOrPersonSelect
;
279 @SuppressWarnings("unchecked")
281 public void commit() throws SourceException
, InvalidValueException
{
283 //need to commit the subfields propagation through the fielGroups is not enough
284 personField
.commit();
285 personsListEditor
.commit();
286 if(!getState(false).readOnly
&& getPropertyDataSource().isReadOnly()){
287 // the TeamOrPersonBase Editor (remove, addPerson, addTeam) is not readonly
288 // thus removing the TeamOrPerson is allowed. In case the datasource is readonly
289 // due to missing user grants for the TeamOrPerson it must be set to readWrite to
290 // make it possible to change the property of the parent
291 getPropertyDataSource().setReadOnly(false);
296 if(hasNullContent()){
297 getPropertyDataSource().setValue(null);
301 TeamOrPersonBase
<?
> bean
= getValue();
302 if(bean
!= null && bean
instanceof Team
){
303 boolean isUnsaved
= bean
.getId() == 0;
305 UserHelperAccess
.userHelper().createAuthorityForCurrentUser(bean
, EnumSet
.of(CRUD
.UPDATE
, CRUD
.DELETE
), null);
314 protected List
<Field
> nullValueCheckIgnoreFields() {
316 List
<Field
> ignoreFields
= super.nullValueCheckIgnoreFields();
317 ignoreFields
.add(personField
);
318 ignoreFields
.add(nomenclaturalTitleField
.getUnlockSwitch());
319 if(nomenclaturalTitleField
.getUnlockSwitch().getValue().booleanValue() == false){
320 ignoreFields
.add(nomenclaturalTitleField
.getTextField());
322 ignoreFields
.add(titleField
.getUnlockSwitch());
323 if(titleField
.getUnlockSwitch().getValue().booleanValue() == false){
324 ignoreFields
.add(titleField
.getTextField());
333 public boolean hasNullContent() {
335 TeamOrPersonBase
<?
> bean
= getValue();
339 if(bean
instanceof Team
){
341 return super.hasNullContent();
344 return personField
.hasNullContent();
348 public void setFilterableTeamPagingProvider(CdmFilterablePagingProvider
<AgentBase
, TeamOrPersonBase
> pagingProvider
, CachingPresenter cachingPresenter
){
349 teamOrPersonSelect
.loadFrom(pagingProvider
, pagingProvider
, pagingProvider
.getPageSize());
351 // it is important to add the ToOneRelatedEntityReloader to the TeamOrPersonField directly
352 // since the value of the select will be immediately passed to the TeamOrPersonField
353 ToOneRelatedEntityReloader
<TeamOrPersonBase
<?
>> teamOrPersonReloader
= new ToOneRelatedEntityReloader
<TeamOrPersonBase
<?
>>(this, cachingPresenter
);
354 this.addValueChangeListener(teamOrPersonReloader
);
357 public void setFilterablePersonPagingProvider(CdmFilterablePagingProvider
<AgentBase
, Person
> pagingProvider
, CachingPresenter cachingPresenter
){
359 personsListEditor
.setEntityFieldInstantiator(new EntityFieldInstantiator
<PersonField
>() {
362 public PersonField
createNewInstance() {
363 PersonField f
= new PersonField();
364 f
.setAllowNewEmptyEntity(true); // otherwise new entities can not be added to the personsListEditor
365 f
.getPersonSelect().loadFrom(pagingProvider
, pagingProvider
, pagingProvider
.getPageSize());
366 f
.getPersonSelect().setCaptionGenerator(new TeamOrPersonBaseCaptionGenerator
<Person
>(cacheType
));
368 // it is important to add the ToOneRelatedEntityReloader to the PersonField directly
369 // since the value of the select will be immediately passed to the PersonField:
370 f
.addValueChangeListener(new ToOneRelatedEntityReloader
<Person
>(f
, cachingPresenter
));
377 public void setValue(TeamOrPersonBase
<?
> newFieldValue
) {
378 // ignore readonly states of the datasource
379 setValue(newFieldValue
, false, !hasSharedStateReadOnly());
382 protected boolean hasSharedStateReadOnly(){
383 AbstractFieldState sharedState
= getState(false);
384 return sharedState
.readOnly
;
392 public void setReadOnly(boolean readOnly
) {
393 // super.setReadOnly(readOnly); // moved into setEditorReadOnly()
394 setReadOnlyComponents(readOnly
);
397 public void setEditorReadOnly(boolean readOnly
) {
398 super.setReadOnly(readOnly
);
399 for(Component c
: editorComponents
){
400 applyReadOnlyState(c
, readOnly
);
406 * Reset the readonly state of nested components to <code>false</code>.
408 protected void resetReadOnlyComponents() {
410 setReadOnlyComponents(false);
415 * Set the nested components (team or person fields) to read only but
416 * keep the state of the <code>TeamOrPersonField</code> untouched so
417 * that the <code>teamOrPersonSelect</code>, <code>removeButton</code>,
418 * <code>personButton</code> and <code>teamButton</code> stay operational.
422 protected void setReadOnlyComponents(boolean readOnly
) {
423 setDeepReadOnly(readOnly
, getContent(), editorComponents
);
424 updateCaptionReadonlyNotice(readOnly
);