001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.container;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.krad.uif.component.Component;
020import org.kuali.rice.krad.uif.component.ComponentBase;
021import org.kuali.rice.krad.uif.field.FieldGroup;
022import org.kuali.rice.krad.uif.field.InputField;
023import org.kuali.rice.krad.uif.field.ErrorsField;
024import org.kuali.rice.krad.uif.field.HeaderField;
025import org.kuali.rice.krad.uif.field.MessageField;
026import org.kuali.rice.krad.uif.layout.LayoutManager;
027import org.kuali.rice.krad.uif.util.ComponentUtils;
028import org.kuali.rice.krad.uif.view.View;
029import org.kuali.rice.krad.uif.widget.Help;
030
031import java.util.ArrayList;
032import java.util.List;
033
034/**
035 * Base <code>Container</code> implementation which container implementations
036 * can extend
037 * 
038 * <p>
039 * Provides properties for the basic <code>Container</code> functionality in
040 * addition to default implementation of the lifecycle methods including some
041 * setup of the header, items list, and layout manager
042 * </p>
043 * 
044 * @author Kuali Rice Team (rice.collab@kuali.org)
045 */
046public abstract class ContainerBase extends ComponentBase implements Container {
047        private static final long serialVersionUID = -4182226230601746657L;
048
049        private int itemOrderingSequence;
050
051        private String additionalMessageKeys;
052        private ErrorsField errorsField;
053
054        private Help help;
055        private LayoutManager layoutManager;
056
057        private HeaderField header;
058        private Group footer;
059
060        private String instructionalText;
061        private MessageField instructionalMessageField;
062
063        private boolean fieldContainer;
064
065        /**
066         * Default Constructor
067         */
068        public ContainerBase() {
069                itemOrderingSequence = 1;
070        }
071
072        /**
073         * The following initialization is performed:
074         * 
075         * <ul>
076         * <li>Sorts the containers list of components</li>
077         * <li>Initializes LayoutManager</li>
078         * </ul>
079         * 
080         * @see org.kuali.rice.krad.uif.component.ComponentBase#performInitialization(org.kuali.rice.krad.uif.view.View, java.lang.Object)
081         */
082        @SuppressWarnings("unchecked")
083        @Override
084        public void performInitialization(View view, Object model) {
085                super.performInitialization(view, model);
086
087                // sort items list by the order property
088                List<? extends Component> sortedItems = (List<? extends Component>) ComponentUtils.sort(getItems(),
089                                itemOrderingSequence);
090                setItems(sortedItems);
091
092                if (layoutManager != null) {
093                        layoutManager.performInitialization(view, model, this);
094                }
095        }
096
097        /**
098         * @see org.kuali.rice.krad.uif.component.ComponentBase#performApplyModel(org.kuali.rice.krad.uif.view.View,
099         *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
100         */
101        @Override
102        public void performApplyModel(View view, Object model, Component parent) {
103                super.performApplyModel(view, model, parent);
104
105                if (layoutManager != null) {
106                        layoutManager.performApplyModel(view, model, this);
107                }
108        }
109
110        /**
111         * The following finalization is performed:
112         * 
113         * <ul>
114         * <li>Sets the headerText of the header Group if it is blank</li>
115         * <li>Set the messageText of the summary MessageField if it is blank</li>
116         * <li>Finalizes LayoutManager</li>
117         * </ul>
118         * 
119         * @see org.kuali.rice.krad.uif.component.ComponentBase#performFinalize(org.kuali.rice.krad.uif.view.View,
120         *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
121         */
122        @Override
123        public void performFinalize(View view, Object model, Component parent) {
124                super.performFinalize(view, model, parent);
125
126                // if header title not given, use the container title
127                if (header != null && StringUtils.isBlank(header.getHeaderText())) {
128                        header.setHeaderText(this.getTitle());
129                }
130
131                // setup summary message field if necessary
132                if (instructionalMessageField != null && StringUtils.isBlank(instructionalMessageField.getMessageText())) {
133                        instructionalMessageField.setMessageText(instructionalText);
134                }
135
136                if (layoutManager != null) {
137                        layoutManager.performFinalize(view, model, this);
138                }
139        }
140
141        /**
142         * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
143         */
144        @Override
145        public List<Component> getComponentsForLifecycle() {
146                List<Component> components = super.getComponentsForLifecycle();
147
148                components.add(header);
149                components.add(footer);
150                components.add(errorsField);
151                components.add(help);
152                components.add(instructionalMessageField);
153
154                for (Component component : getItems()) {
155                        components.add(component);
156                }
157
158                if (layoutManager != null) {
159                        components.addAll(layoutManager.getComponentsForLifecycle());
160                }
161
162                return components;
163        }
164
165    /**
166     * @see org.kuali.rice.krad.uif.component.Component#getComponentPrototypes()
167     */
168    @Override
169    public List<Component> getComponentPrototypes() {
170        List<Component> components = super.getComponentPrototypes();
171
172        if (layoutManager != null) {
173            components.addAll(layoutManager.getComponentPrototypes());
174        }
175
176        return components;
177    }
178
179        /**
180         * Additional keys that should be matching on when gathering errors or other
181         * messages for the <code>Container</code>
182         * 
183         * <p>
184         * Messages associated with the container will be displayed with the
185         * container grouping in the user interface. Typically, these are a result
186         * of problems with the containers fields or some other business logic
187         * associated with the containers information. The framework will by default
188         * include all the error keys for fields in the container, and also an
189         * errors associated with the containers id. Keys given here will be matched
190         * in addition to those defaults.
191         * </p>
192         * 
193         * <p>
194         * Multple keys can be given using the comma delimiter, the * wildcard is
195         * also allowed in the message key
196         * </p>
197         * 
198         * @return String additional message key string
199         */
200        public String getAdditionalMessageKeys() {
201                return this.additionalMessageKeys;
202        }
203
204        /**
205         * Setter for the components additional message key string
206         * 
207         * @param additionalMessageKeys
208         */
209        public void setAdditionalMessageKeys(String additionalMessageKeys) {
210                this.additionalMessageKeys = additionalMessageKeys;
211        }
212
213        /**
214         * @see org.kuali.rice.krad.uif.container.Container#getErrorsField()
215         */
216        @Override
217        public ErrorsField getErrorsField() {
218                return this.errorsField;
219        }
220
221        /**
222         * @see org.kuali.rice.krad.uif.container.Container#setErrorsField(org.kuali.rice.krad.uif.field.ErrorsField)
223         */
224        @Override
225        public void setErrorsField(ErrorsField errorsField) {
226                this.errorsField = errorsField;
227        }
228
229        /**
230         * @see org.kuali.rice.krad.uif.container.Container#getHelp()
231         */
232        @Override
233        public Help getHelp() {
234                return this.help;
235        }
236
237        /**
238         * @see org.kuali.rice.krad.uif.container.Container#setHelp(org.kuali.rice.krad.uif.widget.Help)
239         */
240        @Override
241        public void setHelp(Help help) {
242                this.help = help;
243        }
244
245        /**
246         * @see org.kuali.rice.krad.uif.container.Container#getItems()
247         */
248        @Override
249        public abstract List<? extends Component> getItems();
250
251        /**
252         * Setter for the containers list of components
253         * 
254         * @param items
255         */
256        public abstract void setItems(List<? extends Component> items);
257
258        /**
259         * For <code>Component</code> instances in the container's items list that
260         * do not have an order set, a default order number will be assigned using
261         * this property. The first component found in the list without an order
262         * will be assigned the configured initial value, and incremented by one for
263         * each component (without an order) found afterwards
264         * 
265         * @return int order sequence
266         */
267        public int getItemOrderingSequence() {
268                return this.itemOrderingSequence;
269        }
270
271        /**
272         * Setter for the container's item ordering sequence number (initial value)
273         * 
274         * @param itemOrderingSequence
275         */
276        public void setItemOrderingSequence(int itemOrderingSequence) {
277                this.itemOrderingSequence = itemOrderingSequence;
278        }
279
280        /**
281         * @see org.kuali.rice.krad.uif.container.Container#getLayoutManager()
282         */
283        @Override
284        public LayoutManager getLayoutManager() {
285                return this.layoutManager;
286        }
287
288        /**
289         * @see org.kuali.rice.krad.uif.container.Container#setLayoutManager(org.kuali.rice.krad.uif.layout.LayoutManager)
290         */
291        @Override
292        public void setLayoutManager(LayoutManager layoutManager) {
293                this.layoutManager = layoutManager;
294        }
295
296        /**
297         * @see org.kuali.rice.krad.uif.container.Container#getHeader()
298         */
299        @Override
300        public HeaderField getHeader() {
301                return this.header;
302        }
303
304        /**
305         * @see org.kuali.rice.krad.uif.container.Container#setHeader(org.kuali.rice.krad.uif.field.HeaderField)
306         */
307        @Override
308        public void setHeader(HeaderField header) {
309                this.header = header;
310        }
311
312        /**
313         * @see org.kuali.rice.krad.uif.container.Container#getFooter()
314         */
315        @Override
316        public Group getFooter() {
317                return this.footer;
318        }
319
320        /**
321         * @see org.kuali.rice.krad.uif.container.Container#setFooter(org.kuali.rice.krad.uif.container.Group)
322         */
323        @Override
324        public void setFooter(Group footer) {
325                this.footer = footer;
326        }
327
328        /**
329         * Convenience setter for configuration to turn rendering of the header
330         * on/off
331         * 
332         * <p>
333         * For nested groups (like Field Groups) it is often necessary to only show
334         * the container body (the contained components). This method allows the
335         * header to not be displayed
336         * </p>
337         * 
338         * @param renderHeader
339         */
340        public void setRenderHeader(boolean renderHeader) {
341                if (header != null) {
342                        header.setRender(renderHeader);
343                }
344        }
345
346        /**
347         * Convenience setter for configuration to turn rendering of the footer
348         * on/off
349         * 
350         * <p>
351         * For nested groups it is often necessary to only show the container body
352         * (the contained components). This method allows the footer to not be
353         * displayed
354         * </p>
355         * 
356         * @param renderFooter
357         */
358        public void setRenderFooter(boolean renderFooter) {
359                if (footer != null) {
360                        footer.setRender(renderFooter);
361                }
362        }
363
364    /**
365     * Text explaining how complete the group inputs, including things like what values should be selected
366     * in certain cases, what fields should be completed and so on (instructions)
367     *
368     * @return String instructional message
369     */
370        public String getInstructionalText() {
371                return this.instructionalText;
372        }
373
374    /**
375     * Setter for the instructional message
376     *
377     * @param instructionalText
378     */
379        public void setInstructionalText(String instructionalText) {
380                this.instructionalText = instructionalText;
381        }
382
383    /**
384     * Message field that displays instructional text
385     *
386     * <p>
387     * This message field can be configured to for adjusting how the instructional text will display. Generally
388     * the styleClasses property will be of most interest
389     * </p>
390     *
391     * @return MessageField instructional message field
392     */
393        public MessageField getInstructionalMessageField() {
394                return this.instructionalMessageField;
395        }
396
397    /**
398     * Setter for the instructional text message field
399     *
400     * <p>
401     * Note this is the setter for the field that will render the instructional text. The actual text can be
402     * set on the field but can also be set using {@link #setInstructionalText(String)}
403     * </p>
404     *
405     * @param instructionalMessageField
406     */
407        public void setInstructionalMessageField(MessageField instructionalMessageField) {
408                this.instructionalMessageField = instructionalMessageField;
409        }
410
411        /**
412         * Gets only the data fields that are nested in this container.  This is a subset of
413         * what getComponentsForLifecycle() returns
414         * 
415         * @return
416         */
417        public List<InputField> getInputFields(){
418                List<InputField> inputFields = new ArrayList<InputField>();
419                for(Component c: this.getComponentsForLifecycle()){
420                        if(c instanceof InputField){
421                                inputFields.add((InputField)c);
422                        }
423                }
424                return inputFields;
425                
426        }
427
428    /**
429     * getAllInputFields gets all the input fields contained in this container, but also in
430     * every sub-container that is a child of this container.  When called from the top level
431     * View this will be every InputField across all pages.
432     * @return every InputField that is a child at any level of this container
433     */
434    public List<InputField> getAllInputFields(){
435        List<InputField> inputFields = new ArrayList<InputField>();
436        for(Component c: this.getComponentsForLifecycle()){
437            if(c instanceof InputField){
438                inputFields.add((InputField)c);
439            }
440            else if(c instanceof ContainerBase){
441                inputFields.addAll( ((ContainerBase) c).getAllInputFields());
442            }
443            else if(c instanceof FieldGroup){
444                ContainerBase cb = ((FieldGroup) c).getGroup();
445                inputFields.addAll(cb.getAllInputFields());
446            }
447        }
448        return inputFields;    
449    }
450
451        /**
452         * This property is true if the container is used to display a group of fields that is visually a single
453         * field.
454         * @return the fieldContainer
455         */
456        public boolean isFieldContainer() {
457                return this.fieldContainer;
458        }
459
460        /**
461         * @param fieldContainer the fieldContainer to set
462         */
463        public void setFieldContainer(boolean fieldContainer) {
464                this.fieldContainer = fieldContainer;
465        }
466
467}