001/**
002 * Copyright 2005-2018 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.datadictionary.parse.BeanTagAttribute;
020import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
021import org.kuali.rice.krad.uif.UifConstants;
022import org.kuali.rice.krad.uif.component.Component;
023import org.kuali.rice.krad.uif.component.ComponentBase;
024import org.kuali.rice.krad.uif.component.DelayedCopy;
025import org.kuali.rice.krad.uif.element.Header;
026import org.kuali.rice.krad.uif.element.Message;
027import org.kuali.rice.krad.uif.element.ValidationMessages;
028import org.kuali.rice.krad.uif.layout.LayoutManager;
029import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
030import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRestriction;
031import org.kuali.rice.krad.uif.util.ComponentFactory;
032import org.kuali.rice.krad.uif.util.ComponentUtils;
033import org.kuali.rice.krad.uif.util.LifecycleElement;
034import org.kuali.rice.krad.uif.widget.Help;
035import org.kuali.rice.krad.uif.widget.Tooltip;
036
037import java.util.ArrayList;
038import java.util.List;
039
040/**
041 * Base <code>Container</code> implementation which container implementations
042 * can extend
043 *
044 * <p>
045 * Provides properties for the basic <code>Container</code> functionality in
046 * addition to default implementation of the lifecycle methods including some
047 * setup of the header, items list, and layout manager
048 * </p>
049 *
050 * @author Kuali Rice Team (rice.collab@kuali.org)
051 */
052public abstract class ContainerBase extends ComponentBase implements Container {
053    private static final long serialVersionUID = -4182226230601746657L;
054
055    private int defaultItemPosition;
056
057    private Help help;
058
059    private LayoutManager layoutManager;
060
061    private Header header;
062    private Group footer;
063
064    private String instructionalText;
065    private Message instructionalMessage;
066
067    @DelayedCopy
068    private ValidationMessages validationMessages;
069
070    private String enterKeyAction;
071
072    /**
073     * Default Constructor
074     */
075    public ContainerBase() {
076        defaultItemPosition = 1;
077    }
078
079    /**
080     * {@inheritDoc}
081     */
082    @Override
083    public boolean isProcessRemoteFieldHolders() {
084        return true;
085    }
086
087    /**
088     * The following initialization is performed:
089     *
090     * <ul>
091     * <li>Sorts the containers list of components</li>
092     * <li>Initializes the instructional field if necessary</li>
093     * <li>Initializes LayoutManager</li>
094     * </ul>
095     *
096     * {@inheritDoc}
097     */
098    @SuppressWarnings("deprecation")
099    @Override
100    public void performInitialization(Object model) {
101        super.performInitialization(model);
102
103        if ((StringUtils.isNotBlank(instructionalText) || (getPropertyExpression("instructionalText") != null)) && (
104                instructionalMessage
105                        == null)) {
106            instructionalMessage = ComponentFactory.getInstructionalMessage();
107        }
108
109        if (layoutManager != null && !this.getItems().isEmpty()) {
110            layoutManager.performInitialization(model);
111        }
112    }
113
114    /**
115     * {@inheritDoc}
116     */
117    @SuppressWarnings("deprecation")
118    @Override
119    public void performApplyModel(Object model, LifecycleElement parent) {
120        super.performApplyModel(model, parent);
121
122        // setup summary message field if necessary
123        if (instructionalMessage != null && StringUtils.isBlank(instructionalMessage.getMessageText())) {
124            instructionalMessage.setMessageText(instructionalText);
125        }
126
127        if (layoutManager != null && !this.getItems().isEmpty()) {
128            layoutManager.performApplyModel(model, this);
129        }
130    }
131
132    /**
133     * The following finalization is performed:
134     *
135     * <ul>
136     * <li>Sets the headerText of the header Group if it is blank</li>
137     * <li>Set the messageText of the summary Message if it is blank</li>
138     * <li>Finalizes LayoutManager</li>
139     * </ul>
140     *
141     * {@inheritDoc}
142     */
143    @SuppressWarnings("deprecation")
144    @Override
145    public void performFinalize(Object model, LifecycleElement parent) {
146        super.performFinalize(model, parent);
147
148        if (header != null) {
149            header.addDataAttribute(UifConstants.DataAttributes.HEADER_FOR, this.getId());
150        }
151
152        if (layoutManager != null && !this.getItems().isEmpty()) {
153            layoutManager.performFinalize(model, this);
154        }
155
156        // Generate validation messages
157        if (validationMessages != null) {
158            validationMessages.generateMessages(ViewLifecycle.getView(), model, this);
159        }
160
161        // add data attributes to help identify enter key actions
162        if (this.getEnterKeyAction() != null && StringUtils.isNotBlank(this.getEnterKeyAction())) {
163            this.addDataAttribute(UifConstants.DataAttributes.ENTER_KEY, this.getEnterKeyAction());
164        }
165    }
166
167    /**
168     * {@inheritDoc}
169     */
170    @Override
171    public List<String> getAdditionalTemplates() {
172        List<String> additionalTemplates = super.getAdditionalTemplates();
173
174        if (layoutManager != null) {
175            if (additionalTemplates.isEmpty()) {
176                additionalTemplates = new ArrayList<String>();
177            }
178            additionalTemplates.add(layoutManager.getTemplate());
179        }
180
181        return additionalTemplates;
182    }
183
184    /**
185     * Performs sorting of the container items based on the order property
186     */
187    @Override
188    public void sortItems() {
189        // sort items list by the order property
190        List<? extends Component> sortedItems = ComponentUtils.sort(getItems(), defaultItemPosition);
191        setItems(sortedItems);
192    }
193
194    /**
195     * {@inheritDoc}
196     */
197    @Override
198    @ViewLifecycleRestriction
199    @BeanTagAttribute(type= BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
200        public ValidationMessages getValidationMessages() {
201                return this.validationMessages;
202        }
203
204    /**
205     * {@inheritDoc}
206     */
207    @Override
208    public void setValidationMessages(ValidationMessages validationMessages) {
209        this.validationMessages = validationMessages;
210    }
211
212        /**
213         * {@inheritDoc}
214         */
215        @Override
216    @BeanTagAttribute(type= BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
217        public Help getHelp() {
218                return this.help;
219        }
220
221    /**
222     * {@inheritDoc}
223     */
224    @Override
225    public void setHelp(Help help) {
226        this.help = help;
227    }
228
229    /**
230     * {@inheritDoc}
231     */
232    @Override
233    public void setTooltipOfComponent(Tooltip tooltip) {
234        getHeader().setToolTip(tooltip);
235    }
236
237    /**
238     * {@inheritDoc}
239     */
240    @Override
241    public String getHelpTitle() {
242        return this.getHeader().getHeaderText();
243    }
244
245    /**
246         * {@inheritDoc}
247         */
248        @Override
249    @BeanTagAttribute
250        public abstract List<? extends Component> getItems();
251
252    /**
253     * Setter for the containers list of components
254     *
255     * @param items
256     */
257    public abstract void setItems(List<? extends Component> items);
258
259        /**
260         * For <code>Component</code> instances in the container's items list that
261         * do not have an order set, a default order number will be assigned using
262         * this property. The first component found in the list without an order
263         * will be assigned the configured initial value, and incremented by one for
264         * each component (without an order) found afterwards
265         *
266         * @return int order sequence
267         */
268    @BeanTagAttribute
269        public int getDefaultItemPosition() {
270                return this.defaultItemPosition;
271        }
272
273    /**
274     * Setter for the container's item ordering sequence number (initial value)
275     *
276     * @param defaultItemPosition
277     */
278    public void setDefaultItemPosition(int defaultItemPosition) {
279        this.defaultItemPosition = defaultItemPosition;
280    }
281
282        /**
283         * {@inheritDoc}
284         */
285        @Override
286    @BeanTagAttribute(type= BeanTagAttribute.AttributeType.BYTYPE)
287        public LayoutManager getLayoutManager() {
288                return this.layoutManager;
289        }
290
291    /**
292     * {@inheritDoc}
293     */
294    @Override
295    public void setLayoutManager(LayoutManager layoutManager) {
296        this.layoutManager = layoutManager;
297    }
298
299        /**
300         * {@inheritDoc}
301         */
302        @Override
303    @BeanTagAttribute(type= BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
304        public Header getHeader() {
305                return this.header;
306        }
307
308    /**
309     * {@inheritDoc}
310     */
311    @Override
312    public void setHeader(Header header) {
313        this.header = header;
314    }
315
316        /**
317         * {@inheritDoc}
318         */
319        @Override
320    @BeanTagAttribute(type= BeanTagAttribute.AttributeType.DIRECT)
321        public Group getFooter() {
322                return this.footer;
323        }
324
325    /**
326     * {@inheritDoc}
327     */
328    @Override
329    public void setFooter(Group footer) {
330        this.footer = footer;
331    }
332
333    /**
334     * Convenience setter for configuration to turn rendering of the header
335     * on/off
336     *
337     * <p>
338     * For nested groups (like Field Groups) it is often necessary to only show
339     * the container body (the contained components). This method allows the
340     * header to not be displayed
341     * </p>
342     *
343     * @param renderHeader
344     */
345    public void setRenderHeader(boolean renderHeader) {
346        if (header != null) {
347            header.setRender(renderHeader);
348        }
349    }
350
351    /**
352     * Convenience getter for the header text
353     *
354     * @return The text that should be displayed on the header
355     */
356    @BeanTagAttribute
357    public String getHeaderText () {
358        if (header != null && header.getHeaderText() != null) {
359            return header.getHeaderText();
360        } else {
361            return "";
362        }
363    }
364
365    /**
366     * Convenience setter for configuration to set the header text
367     *
368     * @param headerText the text that should be displayed on the header.
369     */
370    public void setHeaderText(String headerText) {
371        if (header != null) {
372            header.setHeaderText(headerText);
373        }
374    }
375
376    /**
377     * Convenience setter for configuration to turn rendering of the footer
378     * on/off
379     *
380     * <p>
381     * For nested groups it is often necessary to only show the container body
382     * (the contained components). This method allows the footer to not be
383     * displayed
384     * </p>
385     *
386     * @param renderFooter
387     */
388    public void setRenderFooter(boolean renderFooter) {
389        if (footer != null) {
390            footer.setRender(renderFooter);
391        }
392    }
393
394    /**
395     * Text explaining how complete the group inputs, including things like what values should be selected
396     * in certain cases, what fields should be completed and so on (instructions)
397     *
398     * @return instructional message
399     */
400    @BeanTagAttribute
401        public String getInstructionalText() {
402                return this.instructionalText;
403        }
404
405    /**
406     * Setter for the instructional message
407     *
408     * @param instructionalText
409     */
410    public void setInstructionalText(String instructionalText) {
411        this.instructionalText = instructionalText;
412    }
413
414    /**
415     * Message field that displays instructional text
416     *
417     * <p>
418     * This message field can be configured to for adjusting how the instructional text will display. Generally
419     * the styleClasses property will be of most interest
420     * </p>
421     *
422     * @return instructional message field
423     */
424    @BeanTagAttribute
425        public Message getInstructionalMessage() {
426                return this.instructionalMessage;
427        }
428
429    /**
430     * Setter for the instructional text message field
431     *
432     * <p>
433     * Note this is the setter for the field that will render the instructional text. The actual text can be
434     * set on the field but can also be set using {@link #setInstructionalText(String)}
435     * </p>
436     *
437     * @param instructionalMessage
438     */
439    public void setInstructionalMessage(Message instructionalMessage) {
440        this.instructionalMessage = instructionalMessage;
441    }
442    
443    /**
444     * {@inheritDoc}
445     */
446    @Override
447    @BeanTagAttribute
448    public String getEnterKeyAction() {
449        return this.enterKeyAction;
450    }
451
452    /**
453     * {@inheritDoc}
454     */
455    public void setEnterKeyAction(String enterKeyAction) {
456        this.enterKeyAction = enterKeyAction;
457    }
458
459    /**
460     * {@inheritDoc}
461     */
462    @Override
463    public void completeValidation(ValidationTrace tracer) {
464        tracer.addBean(this);
465
466        // Checks for over writing of the instructional text or message
467        if (getInstructionalText() != null && getInstructionalMessage() != null) {
468            String currentValues[] = {"instructionalMessage.text = " + getInstructionalMessage().getMessageText(),
469                    "instructionalText = " + getInstructionalText()};
470            tracer.createWarning("InstructionalMessage will override instructioanlText", currentValues);
471        }
472
473        super.completeValidation(tracer.getCopy());
474    }
475}