View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.layout;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.uif.UifConstants;
20  import org.kuali.rice.krad.uif.component.DataBinding;
21  import org.kuali.rice.krad.uif.container.CollectionGroup;
22  import org.kuali.rice.krad.uif.container.Container;
23  import org.kuali.rice.krad.uif.container.Group;
24  import org.kuali.rice.krad.uif.field.DataField;
25  import org.kuali.rice.krad.uif.field.FieldGroup;
26  import org.kuali.rice.krad.uif.field.InputField;
27  import org.kuali.rice.krad.uif.view.View;
28  import org.kuali.rice.krad.uif.component.Component;
29  import org.kuali.rice.krad.uif.field.ActionField;
30  import org.kuali.rice.krad.uif.field.Field;
31  import org.kuali.rice.krad.uif.field.LabelField;
32  import org.kuali.rice.krad.uif.field.MessageField;
33  import org.kuali.rice.krad.uif.util.ComponentFactory;
34  import org.kuali.rice.krad.uif.util.ComponentUtils;
35  import org.kuali.rice.krad.uif.widget.RichTable;
36  import org.kuali.rice.krad.web.form.UifFormBase;
37  
38  import java.util.ArrayList;
39  import java.util.List;
40  import java.util.Set;
41  
42  /**
43   * Layout manager that works with <code>CollectionGroup</code> components and
44   * renders the collection as a Table
45   * 
46   * <p>
47   * Based on the fields defined, the <code>TableLayoutManager</code> will
48   * dynamically create instances of the fields for each collection row. In
49   * addition, the manager can create standard fields like the action and sequence
50   * fields for each row. The manager supports options inherited from the
51   * <code>GridLayoutManager</code> such as rowSpan, colSpan, and cell width
52   * settings.
53   * </p>
54   * 
55   * @author Kuali Rice Team (rice.collab@kuali.org)
56   */
57  public class TableLayoutManager extends GridLayoutManager implements CollectionLayoutManager {
58  	private static final long serialVersionUID = 3622267585541524208L;
59  
60  	private boolean useShortLabels;
61  	private boolean repeatHeader;
62  	private LabelField headerFieldPrototype;
63  
64  	private boolean renderSequenceField;
65  	private boolean generateAutoSequence;
66  	private Field sequenceFieldPrototype;
67  
68  	private FieldGroup actionFieldPrototype;
69  	private FieldGroup subCollectionFieldGroupPrototype;
70      private Field selectFieldPrototype;
71  
72      private boolean separateAddLine;
73      private Group addLineGroup;
74  
75  	// internal counter for the data columns (not including sequence, action)
76  	private int numberOfDataColumns;
77  
78  	private List<LabelField> headerFields;
79  	private List<Field> dataFields;
80  
81  	private RichTable richTable;
82  	private boolean headerAdded = false;
83  
84      private Set<String> hiddenColumns;
85      private Set<String> sortableColumns;
86  
87  	public TableLayoutManager() {
88  		useShortLabels = true;
89  		repeatHeader = false;
90  		renderSequenceField = true;
91  		generateAutoSequence = false;
92          separateAddLine = false;
93  
94  		headerFields = new ArrayList<LabelField>();
95  		dataFields = new ArrayList<Field>();
96  	}
97  	
98  	/**
99  	 * 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 }