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.layout;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.krad.uif.UifConstants;
020import org.kuali.rice.krad.uif.component.DataBinding;
021import org.kuali.rice.krad.uif.container.CollectionGroup;
022import org.kuali.rice.krad.uif.container.Container;
023import org.kuali.rice.krad.uif.container.Group;
024import org.kuali.rice.krad.uif.field.DataField;
025import org.kuali.rice.krad.uif.field.FieldGroup;
026import org.kuali.rice.krad.uif.field.InputField;
027import org.kuali.rice.krad.uif.view.View;
028import org.kuali.rice.krad.uif.component.Component;
029import org.kuali.rice.krad.uif.field.ActionField;
030import org.kuali.rice.krad.uif.field.Field;
031import org.kuali.rice.krad.uif.field.LabelField;
032import org.kuali.rice.krad.uif.field.MessageField;
033import org.kuali.rice.krad.uif.util.ComponentFactory;
034import org.kuali.rice.krad.uif.util.ComponentUtils;
035import org.kuali.rice.krad.uif.widget.RichTable;
036import org.kuali.rice.krad.web.form.UifFormBase;
037
038import java.util.ArrayList;
039import java.util.List;
040import java.util.Set;
041
042/**
043 * Layout manager that works with <code>CollectionGroup</code> components and
044 * renders the collection as a Table
045 * 
046 * <p>
047 * Based on the fields defined, the <code>TableLayoutManager</code> will
048 * dynamically create instances of the fields for each collection row. In
049 * addition, the manager can create standard fields like the action and sequence
050 * fields for each row. The manager supports options inherited from the
051 * <code>GridLayoutManager</code> such as rowSpan, colSpan, and cell width
052 * settings.
053 * </p>
054 * 
055 * @author Kuali Rice Team (rice.collab@kuali.org)
056 */
057public class TableLayoutManager extends GridLayoutManager implements CollectionLayoutManager {
058        private static final long serialVersionUID = 3622267585541524208L;
059
060        private boolean useShortLabels;
061        private boolean repeatHeader;
062        private LabelField headerFieldPrototype;
063
064        private boolean renderSequenceField;
065        private boolean generateAutoSequence;
066        private Field sequenceFieldPrototype;
067
068        private FieldGroup actionFieldPrototype;
069        private FieldGroup subCollectionFieldGroupPrototype;
070    private Field selectFieldPrototype;
071
072    private boolean separateAddLine;
073    private Group addLineGroup;
074
075        // internal counter for the data columns (not including sequence, action)
076        private int numberOfDataColumns;
077
078        private List<LabelField> headerFields;
079        private List<Field> dataFields;
080
081        private RichTable richTable;
082        private boolean headerAdded = false;
083
084    private Set<String> hiddenColumns;
085    private Set<String> sortableColumns;
086
087        public TableLayoutManager() {
088                useShortLabels = true;
089                repeatHeader = false;
090                renderSequenceField = true;
091                generateAutoSequence = false;
092        separateAddLine = false;
093
094                headerFields = new ArrayList<LabelField>();
095                dataFields = new ArrayList<Field>();
096        }
097        
098        /**
099         * The following actions are performed:
100         * 
101         * <ul>
102         * <li>Sets sequence field prototype if auto sequence is true</li>
103         * <li>Initializes the prototypes</li>
104         * </ul>
105         * 
106         * @see org.kuali.rice.krad.uif.layout.BoxLayoutManager#performInitialization(org.kuali.rice.krad.uif.view.View,
107         *      java.lang.Object, org.kuali.rice.krad.uif.container.Container)
108         */
109        @Override
110        public void performInitialization(View view, Object model, Container container) {
111                super.performInitialization(view, model, container);
112                
113        if (generateAutoSequence && !(sequenceFieldPrototype instanceof MessageField)) {
114            sequenceFieldPrototype = ComponentFactory.getMessageField();
115            view.assignComponentIds(sequenceFieldPrototype);
116        }
117
118                view.getViewHelperService().performComponentInitialization(view, model, headerFieldPrototype);
119                view.getViewHelperService().performComponentInitialization(view, model, sequenceFieldPrototype);
120                view.getViewHelperService().performComponentInitialization(view, model, actionFieldPrototype);
121                view.getViewHelperService().performComponentInitialization(view, model, subCollectionFieldGroupPrototype);
122        view.getViewHelperService().performComponentInitialization(view, model, selectFieldPrototype);
123        }
124
125        /**
126         * Sets up the final column count for rendering based on whether the
127         * sequence and action fields have been generated
128         * 
129         * @see org.kuali.rice.krad.uif.layout.LayoutManagerBase#performFinalize(org.kuali.rice.krad.uif.view.View,
130         *      java.lang.Object, org.kuali.rice.krad.uif.container.Container)
131         */
132        @Override
133        public void performFinalize(View view, Object model, Container container) {
134                super.performFinalize(view, model, container);
135
136        UifFormBase formBase = (UifFormBase) model;
137
138                CollectionGroup collectionGroup = (CollectionGroup) container;
139
140                int totalColumns = getNumberOfDataColumns();
141                if (renderSequenceField) {
142                        totalColumns++;
143                }
144
145        if (collectionGroup.isRenderSelectField()) {
146            totalColumns++;
147        }
148
149                if (collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly()) {
150                        totalColumns++;
151                }
152
153        if (collectionGroup.isRenderAddLine()){
154            if(StringUtils.isBlank(this.getFirstLineStyle()) && !isSeparateAddLine()){
155                this.setFirstLineStyle("kr-addLine");
156            }
157        }
158
159        // if add line event, add highlighting for added row
160        if (UifConstants.ActionEvents.ADD_LINE.equals(formBase.getActionEvent())) {
161            String highlightScript =
162                    "jq(\"#" + container.getId() + "_div > tr:first\").effect(\"highlight\",{}, 6000);";
163            String onReadyScript = collectionGroup.getOnDocumentReadyScript();
164            if (StringUtils.isNotBlank(onReadyScript)) {
165                highlightScript = onReadyScript + highlightScript;
166            }
167            collectionGroup.setOnDocumentReadyScript(highlightScript);
168        }
169                setNumberOfColumns(totalColumns);
170        }
171
172        /**
173         * Assembles the field instances for the collection line. The given sequence
174         * field prototype is copied for the line sequence field. Likewise a copy of
175         * the actionFieldPrototype is made and the given actions are set as the
176         * items for the action field. Finally the generated items are assembled
177         * together into the dataFields list with the given lineFields.
178         * 
179         * @see org.kuali.rice.krad.uif.layout.CollectionLayoutManager#buildLine(org.kuali.rice.krad.uif.view.View,
180         *      java.lang.Object, org.kuali.rice.krad.uif.container.CollectionGroup,
181         *      java.util.List, java.util.List, java.lang.String, java.util.List,
182         *      java.lang.String, java.lang.Object, int)
183         */
184        public void buildLine(View view, Object model, CollectionGroup collectionGroup, List<Field> lineFields,
185                        List<FieldGroup> subCollectionFields, String bindingPath, List<ActionField> actions, String idSuffix,
186                        Object currentLine, int lineIndex) {
187                boolean isAddLine = lineIndex == -1;
188
189        // if separate add line prepare the add line group
190        if (isAddLine && separateAddLine) {
191            if (StringUtils.isBlank(addLineGroup.getTitle()) && StringUtils.isBlank(
192                    addLineGroup.getHeader().getHeaderText())) {
193               addLineGroup.getHeader().setHeaderText(collectionGroup.getAddLineLabel());
194            }
195
196            addLineGroup.setItems(lineFields);
197
198            List<Component> footerItems = new ArrayList<Component>(actions);
199            footerItems.addAll(addLineGroup.getFooter().getItems());
200            addLineGroup.getFooter().setItems(footerItems);
201
202            return;
203        }
204
205        // if first line for table set number of data columns
206        if (dataFields.isEmpty()) {
207            if (isSuppressLineWrapping()) {
208                setNumberOfDataColumns(lineFields.size());
209            } else {
210                setNumberOfDataColumns(getNumberOfColumns());
211            }
212        }
213
214                // TODO: implement repeat header
215                if (!headerAdded) {
216                        headerFields = new ArrayList<LabelField>();
217                        dataFields = new ArrayList<Field>();
218
219                        buildTableHeaderRows(collectionGroup, lineFields);
220                        ComponentUtils.pushObjectToContext(headerFields, UifConstants.ContextVariableNames.LINE, currentLine);
221                        ComponentUtils.pushObjectToContext(headerFields, UifConstants.ContextVariableNames.INDEX, new Integer(
222                                        lineIndex));
223                        headerAdded = true;
224                }
225
226                // set label field rendered to true on line fields
227                for (Field field : lineFields) {
228                        field.setLabelFieldRendered(true);
229
230                        // don't display summary message
231                        // TODO: remove once we have modifier
232                        ComponentUtils.setComponentPropertyDeep(field, "summaryMessageField.render", new Boolean(false));
233                }
234
235                int rowCount = calculateNumberOfRows(lineFields);
236                int rowSpan = rowCount + subCollectionFields.size();
237
238                // sequence field is always first and should span all rows for the line
239                if (renderSequenceField) {
240                        Field sequenceField = null;
241            if (!isAddLine) {
242                sequenceField = ComponentUtils.copy(sequenceFieldPrototype, idSuffix);
243
244                if (generateAutoSequence && (sequenceField instanceof MessageField)) {
245                    ((MessageField) sequenceField).setMessageText(Integer.toString(lineIndex + 1));
246                }
247            }
248                        else {
249                                sequenceField = ComponentUtils.copy(collectionGroup.getAddLineLabelField(), idSuffix);
250                        }
251                        sequenceField.setRowSpan(rowSpan);
252
253                        if (sequenceField instanceof DataBinding) {
254                                ((DataBinding) sequenceField).getBindingInfo().setBindByNamePrefix(bindingPath);
255                        }
256
257                        ComponentUtils.updateContextForLine(sequenceField, currentLine, lineIndex);
258                        dataFields.add(sequenceField);
259                }
260
261        // select field will come after sequence field (if enabled) or be first column
262        if (collectionGroup.isRenderSelectField()) {
263            Field selectField = ComponentUtils.copy(selectFieldPrototype, idSuffix);
264            CollectionLayoutUtils.prepareSelectFieldForLine(selectField, collectionGroup, bindingPath, currentLine);
265
266            ComponentUtils.updateContextForLine(selectField, currentLine, lineIndex);
267            dataFields.add(selectField);
268        }
269
270                // now add the fields in the correct position
271                int cellPosition = 0;
272                for (Field lineField : lineFields) {
273                        dataFields.add(lineField);
274
275                        cellPosition += lineField.getColSpan();
276
277                        // action field should be in last column
278                        if ((cellPosition == getNumberOfDataColumns()) && collectionGroup.isRenderLineActions()
279                                        && !collectionGroup.isReadOnly()) {
280                                FieldGroup lineActionsField = ComponentUtils.copy(actionFieldPrototype, idSuffix);
281
282                                ComponentUtils.updateContextForLine(lineActionsField, currentLine, lineIndex);
283                                lineActionsField.setRowSpan(rowSpan);
284                                lineActionsField.setItems(actions);
285
286                                dataFields.add(lineActionsField);
287                        }
288                }
289
290                // update colspan on sub-collection fields
291                for (FieldGroup subCollectionField : subCollectionFields) {
292                        subCollectionField.setColSpan(numberOfDataColumns);
293                }
294
295                // add sub-collection fields to end of data fields
296                dataFields.addAll(subCollectionFields);
297        }
298
299        /**
300         * Create the <code>LabelField</code> instances that will be used to render
301         * the table header
302         * 
303         * <p>
304         * For each column, a copy of headerFieldPrototype is made that determines
305         * the label configuration. The actual label text comes from the field for
306         * which the header applies to. The first column is always the sequence (if
307         * enabled) and the last column contains the actions. Both the sequence and
308         * action header fields will span all rows for the header.
309         * </p>
310         * 
311         * <p>
312         * The headerFields list will contain the final list of header fields built
313         * </p>
314         * 
315         * @param collectionGroup
316         *            - CollectionGroup container the table applies to
317         * @param lineFields - fields for the data columns from which the headers are pulled
318         */
319        protected void buildTableHeaderRows(CollectionGroup collectionGroup, List<Field> lineFields) {
320                // row count needed to determine the row span for the sequence and
321                // action fields, since they should span all rows for the line
322                int rowCount = calculateNumberOfRows(lineFields);
323
324                // first column is sequence label
325                if (renderSequenceField) {
326                        sequenceFieldPrototype.setLabelFieldRendered(true);
327                        sequenceFieldPrototype.setRowSpan(rowCount);
328                        addHeaderField(sequenceFieldPrototype, 1);
329                }
330
331        // next is select field
332        if (collectionGroup.isRenderSelectField()) {
333            selectFieldPrototype.setLabelFieldRendered(true);
334            selectFieldPrototype.setRowSpan(rowCount);
335            addHeaderField(selectFieldPrototype, 1);
336        }
337
338                // pull out label fields from the container's items
339                int cellPosition = 0;
340                for (Field field : lineFields) {
341                    if (!field.isRender() && StringUtils.isEmpty(field.getProgressiveRender())) {
342                        continue;
343                    }
344                    
345                        cellPosition += field.getColSpan();
346                        addHeaderField(field, cellPosition);
347
348                        // add action header as last column in row
349                        if ((cellPosition == getNumberOfDataColumns()) && collectionGroup.isRenderLineActions()
350                                        && !collectionGroup.isReadOnly()) {
351                                actionFieldPrototype.setLabelFieldRendered(true);
352                                actionFieldPrototype.setRowSpan(rowCount);
353                                addHeaderField(actionFieldPrototype, cellPosition);
354                        }
355                }
356        }
357
358        /**
359         * Creates a new instance of the header field prototype and then sets the
360         * label to the short (if useShortLabels is set to true) or long label of
361         * the given component. After created the header field is added to the list
362         * making up the table header
363         * 
364         * @param field
365         *            - field instance the header field is being created for
366         * @param column
367         *            - column number for the header, used for setting the id
368         */
369        protected void addHeaderField(Field field, int column) {
370                LabelField headerField = ComponentUtils.copy(headerFieldPrototype, "_c" + column);
371                if (useShortLabels) {
372                        headerField.setLabelText(field.getLabel());
373                }
374                else {
375                        headerField.setLabelText(field.getLabel());
376                }
377
378                headerField.setRowSpan(field.getRowSpan());
379                headerField.setColSpan(field.getColSpan());
380
381                if ((field.getRequired() != null) && field.getRequired().booleanValue()) {
382                        headerField.getRequiredMessageField().setRender(true);
383                }
384                else {
385                        headerField.getRequiredMessageField().setRender(false);
386                }
387
388                headerFields.add(headerField);
389        }
390
391        /**
392         * Calculates how many rows will be needed per collection line to display
393         * the list of fields. Assumption is made that the total number of cells the
394         * fields take up is evenly divisible by the configured number of columns
395         * 
396         * @param items
397         *            - list of items that make up one collection line
398         * @return int number of rows
399         */
400        protected int calculateNumberOfRows(List<? extends Field> items) {
401                int rowCount = 0;
402                
403                // check flag that indicates only one row should be created
404                if (isSuppressLineWrapping()) {
405                    return 1;
406                }
407
408                int cellCount = 0;
409                for (Field field : items) {
410                        cellCount += field.getColSpan() + field.getRowSpan() - 1;
411                }
412
413                if (cellCount != 0) {
414                        rowCount = cellCount / getNumberOfDataColumns();
415                }
416
417                return rowCount;
418        }
419
420        /**
421         * @see org.kuali.rice.krad.uif.layout.ContainerAware#getSupportedContainer()
422         */
423        @Override
424        public Class<? extends Container> getSupportedContainer() {
425                return CollectionGroup.class;
426        }
427
428        /**
429         * @see org.kuali.rice.krad.uif.layout.LayoutManagerBase#getComponentsForLifecycle()
430         */
431        @Override
432        public List<Component> getComponentsForLifecycle() {
433                List<Component> components = super.getComponentsForLifecycle();
434
435                components.add(richTable);
436        components.add(addLineGroup);
437                components.addAll(headerFields);
438                components.addAll(dataFields);
439
440                return components;
441        }
442
443    /**
444     * @see org.kuali.rice.krad.uif.layout.LayoutManager#getComponentPrototypes()
445     */
446    @Override
447    public List<Component> getComponentPrototypes() {
448        List<Component> components = super.getComponentPrototypes();
449
450        components.add(headerFieldPrototype);
451        components.add(sequenceFieldPrototype);
452        components.add(actionFieldPrototype);
453        components.add(subCollectionFieldGroupPrototype);
454        components.add(selectFieldPrototype);
455
456        return components;
457    }
458
459        /**
460         * Indicates whether the short label for the collection field should be used
461         * as the table header or the regular label
462         * 
463         * @return boolean true if short label should be used, false if long label
464         *         should be used
465         */
466        public boolean isUseShortLabels() {
467                return this.useShortLabels;
468        }
469
470        /**
471         * Setter for the use short label indicator
472         * 
473         * @param useShortLabels
474         */
475        public void setUseShortLabels(boolean useShortLabels) {
476                this.useShortLabels = useShortLabels;
477        }
478
479        /**
480         * Indicates whether the header should be repeated before each collection
481         * row. If false the header is only rendered at the beginning of the table
482         * 
483         * @return boolean true if header should be repeated, false if it should
484         *         only be rendered once
485         */
486        public boolean isRepeatHeader() {
487                return this.repeatHeader;
488        }
489
490        /**
491         * Setter for the repeat header indicator
492         * 
493         * @param repeatHeader
494         */
495        public void setRepeatHeader(boolean repeatHeader) {
496                this.repeatHeader = repeatHeader;
497        }
498
499        /**
500         * <code>LabelField</code> instance to use as a prototype for creating the
501         * tables header fields. For each header field the prototype will be copied
502         * and adjusted as necessary
503         * 
504         * @return LabelField instance to serve as prototype
505         */
506        public LabelField getHeaderFieldPrototype() {
507                return this.headerFieldPrototype;
508        }
509
510        /**
511         * Setter for the header field prototype
512         * 
513         * @param headerFieldPrototype
514         */
515        public void setHeaderFieldPrototype(LabelField headerFieldPrototype) {
516                this.headerFieldPrototype = headerFieldPrototype;
517        }
518
519        /**
520         * List of <code>LabelField</code> instances that should be rendered to make
521         * up the tables header
522         * 
523         * @return List of label field instances
524         */
525        public List<LabelField> getHeaderFields() {
526                return this.headerFields;
527        }
528
529        /**
530         * Indicates whether the sequence field should be rendered for the
531         * collection
532         * 
533         * @return boolean true if sequence field should be rendered, false if not
534         */
535        public boolean isRenderSequenceField() {
536                return this.renderSequenceField;
537        }
538
539        /**
540         * Setter for the render sequence field indicator
541         * 
542         * @param renderSequenceField
543         */
544        public void setRenderSequenceField(boolean renderSequenceField) {
545                this.renderSequenceField = renderSequenceField;
546        }
547
548        /**
549         * Attribute name to use as sequence value. For each collection line the
550         * value of this field on the line will be retrieved and used as the
551         * sequence value
552         * 
553         * @return String sequence property name
554         */
555    public String getSequencePropertyName() {
556        if ((sequenceFieldPrototype != null) && (sequenceFieldPrototype instanceof DataField)) {
557            return ((DataField) sequenceFieldPrototype).getPropertyName();
558        }
559
560        return null;
561    }
562
563    /**
564     * Setter for the sequence property name
565     * 
566     * @param sequencePropertyName
567     */
568    public void setSequencePropertyName(String sequencePropertyName) {
569        if ((sequenceFieldPrototype != null) && (sequenceFieldPrototype instanceof DataField)) {
570            ((DataField) sequenceFieldPrototype).setPropertyName(sequencePropertyName);
571        }
572    }
573        
574    /**
575     * Indicates whether the sequence field should be generated with the current
576     * line number
577     * 
578     * <p>
579     * If set to true the sequence field prototype will be changed to a message
580     * field (if not already a message field) and the text will be set to the
581     * current line number
582     * </p>
583     * 
584     * @return boolean true if the sequence field should be generated from the
585     *         line number, false if not
586     */
587    public boolean isGenerateAutoSequence() {
588        return this.generateAutoSequence;
589    }
590
591    /**
592     * Setter for the generate auto sequence field
593     * 
594     * @param generateAutoSequence
595     */
596    public void setGenerateAutoSequence(boolean generateAutoSequence) {
597        this.generateAutoSequence = generateAutoSequence;
598    }
599
600    /**
601         * <code>Field</code> instance to serve as a prototype for the
602         * sequence field. For each collection line this instance is copied and
603         * adjusted as necessary
604         * 
605         * @return Attribute field instance
606         */
607        public Field getSequenceFieldPrototype() {
608                return this.sequenceFieldPrototype;
609        }
610
611        /**
612         * Setter for the sequence field prototype
613         * 
614         * @param sequenceFieldPrototype
615         */
616        public void setSequenceFieldPrototype(Field sequenceFieldPrototype) {
617                this.sequenceFieldPrototype = sequenceFieldPrototype;
618        }
619
620        /**
621         * <code>FieldGroup</code> instance to serve as a prototype for the actions
622         * column. For each collection line this instance is copied and adjusted as
623         * necessary. Note the actual actions for the group come from the collection
624         * groups actions List
625         * (org.kuali.rice.krad.uif.container.CollectionGroup.getActionFields()). The
626         * FieldGroup prototype is useful for setting styling of the actions column
627         * and for the layout of the action fields. Note also the label associated
628         * with the prototype is used for the action column header
629         * 
630         * @return GroupField instance
631         */
632        public FieldGroup getActionFieldPrototype() {
633                return this.actionFieldPrototype;
634        }
635
636        /**
637         * Setter for the action field prototype
638         * 
639         * @param actionFieldPrototype
640         */
641        public void setActionFieldPrototype(FieldGroup actionFieldPrototype) {
642                this.actionFieldPrototype = actionFieldPrototype;
643        }
644
645        /**
646         * @see org.kuali.rice.krad.uif.layout.CollectionLayoutManager#getSubCollectionFieldGroupPrototype()
647         */
648        public FieldGroup getSubCollectionFieldGroupPrototype() {
649                return this.subCollectionFieldGroupPrototype;
650        }
651
652        /**
653         * Setter for the sub-collection field group prototype
654         * 
655         * @param subCollectionFieldGroupPrototype
656         */
657        public void setSubCollectionFieldGroupPrototype(FieldGroup subCollectionFieldGroupPrototype) {
658                this.subCollectionFieldGroupPrototype = subCollectionFieldGroupPrototype;
659        }
660
661    /**
662     * Field instance that serves as a prototype for creating the select field on each line when
663     * {@link org.kuali.rice.krad.uif.container.CollectionGroup#isRenderSelectField()} is true
664     *
665     * <p>
666     * This prototype can be used to set the control used for the select field (generally will be a checkbox control)
667     * in addition to styling and other setting. The binding path will be formed with using the
668     * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getSelectPropertyName()} or if not set the framework
669     * will use {@link org.kuali.rice.krad.web.form.UifFormBase#getSelectedCollectionLines()}
670     * </p>
671     *
672     * @return Field select field prototype instance
673     */
674    public Field getSelectFieldPrototype() {
675        return selectFieldPrototype;
676    }
677
678    /**
679     * Setter for the prototype instance for select fields
680     *
681     * @param selectFieldPrototype
682     */
683    public void setSelectFieldPrototype(Field selectFieldPrototype) {
684        this.selectFieldPrototype = selectFieldPrototype;
685    }
686
687    /**
688     * Indicates whether the add line should be rendered in a separate group, or as part of the table (first line)
689     *
690     * <p>
691     * When separate add line is enabled, the fields for the add line will be placed in the {@link #getAddLineGroup()}.
692     * This group can be used to configure the add line presentation. In addition to the fields, the header on the
693     * group (unless already set) will be set to
694     * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getAddLineLabel()} and the add line actions will
695     * be placed into the group's footer.
696     * </p>
697     *
698     * @return boolean true if add line should be separated, false if it should be placed into the table
699     */
700    public boolean isSeparateAddLine() {
701        return separateAddLine;
702    }
703
704    /**
705     * Setter for the separate add line indicator
706     *
707     * @param separateAddLine
708     */
709    public void setSeparateAddLine(boolean separateAddLine) {
710        this.separateAddLine = separateAddLine;
711    }
712
713    /**
714     * When {@link #isSeparateAddLine()} is true, this group will be used to render the add line
715     *
716     * <p>
717     * This group can be used to configure how the add line will be rendered. For example the layout manager configured
718     * on the group will be used to rendered the add line fields. If the header (title) is not set on the group, it
719     * will be set from
720     * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getAddLineLabel()}. In addition,
721     * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getAddLineActionFields()} will be added to the group
722     * footer items.
723     * </p>
724     *
725     * @return Group instance for the collection add line
726     */
727    public Group getAddLineGroup() {
728        return addLineGroup;
729    }
730
731    /**
732     * Setter for the add line Group
733     *
734     * @param addLineGroup
735     */
736    public void setAddLineGroup(Group addLineGroup) {
737        this.addLineGroup = addLineGroup;
738    }
739
740    /**
741         * List of <code>Field</code> instances that make up the tables body. Pulled
742         * by the layout manager template to send through the Grid layout
743         * 
744         * @return List<Field> table body fields
745         */
746        public List<Field> getDataFields() {
747                return this.dataFields;
748        }
749
750        /**
751         * Widget associated with the table to add functionality such as sorting,
752         * paging, and export
753         * 
754         * @return TableTools instance
755         */
756        public RichTable getRichTable() {
757                return this.richTable;
758        }
759
760        /**
761         * Setter for the table tools widget
762         * 
763         * @param richTable
764         */
765        public void setRichTable(RichTable richTable) {
766                this.richTable = richTable;
767        }
768
769        /**
770     * @return the numberOfDataColumns
771     */
772    public int getNumberOfDataColumns() {
773        return this.numberOfDataColumns;
774    }
775
776        /**
777     * @param numberOfDataColumns the numberOfDataColumns to set
778     */
779    public void setNumberOfDataColumns(int numberOfDataColumns) {
780        this.numberOfDataColumns = numberOfDataColumns;
781    }
782
783    /** since columns are visible by default, this set holds propertyNames for the ones meant to be hidden*/
784    public Set<String> getHiddenColumns() {
785        return hiddenColumns;
786    }
787
788    public void setHiddenColumns(Set<String> hiddenColumns) {
789        this.hiddenColumns = hiddenColumns;
790    }
791
792    /**holds the propertyNames for columns that are to be sorted*/
793    public Set<String> getSortableColumns() {
794        return sortableColumns;
795    }
796
797    public void setSortableColumns(Set<String> sortableColumns) {
798        this.sortableColumns = sortableColumns;
799    }
800}