View Javadoc

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