View Javadoc

1   /**
2    * Copyright 2005-2014 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.widget;
17  
18  import org.apache.commons.lang.ClassUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.util.type.KualiDecimal;
21  import org.kuali.rice.core.api.util.type.KualiInteger;
22  import org.kuali.rice.core.api.util.type.KualiPercent;
23  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
24  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
25  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
26  import org.kuali.rice.krad.uif.UifConstants;
27  import org.kuali.rice.krad.uif.component.Component;
28  import org.kuali.rice.krad.uif.container.CollectionGroup;
29  import org.kuali.rice.krad.uif.control.CheckboxControl;
30  import org.kuali.rice.krad.uif.control.CheckboxGroupControl;
31  import org.kuali.rice.krad.uif.control.Control;
32  import org.kuali.rice.krad.uif.control.RadioGroupControl;
33  import org.kuali.rice.krad.uif.control.SelectControl;
34  import org.kuali.rice.krad.uif.field.DataField;
35  import org.kuali.rice.krad.uif.field.FieldGroup;
36  import org.kuali.rice.krad.uif.field.InputField;
37  import org.kuali.rice.krad.uif.field.MessageField;
38  import org.kuali.rice.krad.uif.layout.LayoutManager;
39  import org.kuali.rice.krad.uif.layout.TableLayoutManager;
40  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
41  import org.kuali.rice.krad.uif.view.View;
42  import org.kuali.rice.krad.web.form.UifFormBase;
43  
44  import java.sql.Timestamp;
45  import java.util.List;
46  import java.util.Set;
47  
48  /**
49   * Decorates a HTML Table client side with various tools
50   *
51   * <p>
52   * Decorations implemented depend on widget implementation. Examples are
53   * sorting, paging and skinning.
54   * </p>
55   *
56   * @author Kuali Rice Team (rice.collab@kuali.org)
57   */
58  @BeanTags({@BeanTag(name = "richTable-bean", parent = "Uif-RichTable"),
59          @BeanTag(name = "pagedRichTable-bean", parent = "Uif-PagedRichTable"),
60          @BeanTag(name = "scrollableRichTable-bean", parent = "Uif-ScrollableRichTable")})
61  public class RichTable extends WidgetBase {
62      private static final long serialVersionUID = 4671589690877390070L;
63  
64      private String emptyTableMessage;
65      private boolean disableTableSort;
66  
67      private boolean forceAoColumnDefsOverride;
68  
69      private Set<String> hiddenColumns;
70      private Set<String> sortableColumns;
71  
72      private String ajaxSource;
73  
74      private boolean showSearchAndExportOptions = true;
75  
76      private String groupingOptionsJSString;
77  
78      public RichTable() {
79          super();
80          groupingOptionsJSString = "null";
81      }
82  
83      /**
84       * The following initialization is performed:
85       *
86       * <ul>
87       * <li>Initializes component options for empty table message</li>
88       * </ul>
89       */
90      @Override
91      public void performFinalize(View view, Object model, Component component) {
92          super.performFinalize(view, model, component);
93  
94          UifFormBase formBase = (UifFormBase) model;
95  
96          if (isRender()) {
97              if (StringUtils.isNotBlank(getEmptyTableMessage()) && !getTemplateOptions().containsKey(
98                      UifConstants.TableToolsKeys.LANGUAGE)) {
99                  getTemplateOptions().put(UifConstants.TableToolsKeys.LANGUAGE,
100                         "{\"" + UifConstants.TableToolsKeys.EMPTY_TABLE + "\" : \"" + getEmptyTableMessage() + "\"}");
101             }
102 
103             if (!isShowSearchAndExportOptions()) {
104                 Object domOption = getTemplateOptions().get(UifConstants.TableToolsKeys.SDOM);
105                 if (domOption instanceof String) {
106                     String sDomOption = (String) domOption;
107 
108                     if (StringUtils.isNotBlank(sDomOption)) {
109                         sDomOption = StringUtils.remove(sDomOption, "T"); //Removes Export option
110                         sDomOption = StringUtils.remove(sDomOption, "f"); //Removes search option
111                         getTemplateOptions().put(UifConstants.TableToolsKeys.SDOM, sDomOption);
112                     }
113                 }
114             }
115 
116             // for add events, disable initial sorting
117             if (UifConstants.ActionEvents.ADD_LINE.equals(formBase.getActionEvent()) || UifConstants.ActionEvents
118                     .ADD_BLANK_LINE.equals(formBase.getActionEvent())) {
119                 getTemplateOptions().put(UifConstants.TableToolsKeys.AASORTING, "[]");
120             }
121 
122             if ((component instanceof CollectionGroup)) {
123                 buildTableOptions((CollectionGroup) component);
124                 setTotalOptions((CollectionGroup) component);
125             }
126 
127             if (isDisableTableSort()) {
128                 getTemplateOptions().put(UifConstants.TableToolsKeys.TABLE_SORT, "false");
129             }
130 
131             if (StringUtils.isNotBlank(ajaxSource)) {
132                 getTemplateOptions().put(UifConstants.TableToolsKeys.SAJAX_SOURCE, ajaxSource);
133             }
134         }
135     }
136 
137     /**
138      * Builds the footer callback template option for column totals
139      *
140      * @param collectionGroup - the collection group
141      */
142     private void setTotalOptions(CollectionGroup collectionGroup) {
143         LayoutManager layoutManager = collectionGroup.getLayoutManager();
144 
145         if (layoutManager instanceof TableLayoutManager) {
146             List<String> totalColumns = ((TableLayoutManager) layoutManager).getColumnsToCalculate();
147 
148             if (totalColumns.size() > 0) {
149                 String array = "[";
150 
151                 for (String i : totalColumns) {
152                     array = array + i + ",";
153                 }
154                 array = StringUtils.removeEnd(array, ",");
155                 array = array + "]";
156                 getTemplateOptions().put(UifConstants.TableToolsKeys.FOOTER_CALLBACK,
157                         "function (nRow, aaData, iStart, iEnd, aiDisplay) {initializeTotalsFooter (nRow, aaData, iStart, iEnd, aiDisplay, "
158                                 + array
159                                 + " )}");
160             }
161         }
162     }
163 
164     /**
165      * Builds column options for sorting
166      *
167      * @param collectionGroup
168      */
169     protected void buildTableOptions(CollectionGroup collectionGroup) {
170         LayoutManager layoutManager = collectionGroup.getLayoutManager();
171 
172         // if sub collection exists, don't allow the table sortable
173         if (!collectionGroup.getSubCollections().isEmpty()) {
174             setDisableTableSort(true);
175         }
176 
177         if (!isDisableTableSort()) {
178             // if rendering add line, skip that row from col sorting
179             if (collectionGroup.isRenderAddLine()
180                     && !collectionGroup.isReadOnly()
181                     && !((layoutManager instanceof TableLayoutManager) && ((TableLayoutManager) layoutManager)
182                     .isSeparateAddLine())) {
183                 getTemplateOptions().put(UifConstants.TableToolsKeys.SORT_SKIP_ROWS,
184                         "[" + UifConstants.TableToolsValues.ADD_ROW_DEFAULT_INDEX + "]");
185             }
186 
187             StringBuffer tableToolsColumnOptions = new StringBuffer("[");
188 
189             int columnIndex = 0;
190             int actionIndex = -1;
191             boolean actionFieldVisible = collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly();
192 
193             if (layoutManager instanceof TableLayoutManager) {
194                 actionIndex = ((TableLayoutManager) layoutManager).getActionColumnIndex();
195             }
196 
197             if (actionIndex == 1 && actionFieldVisible) {
198                 String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
199                 tableToolsColumnOptions.append(actionColOptions + " , ");
200                 columnIndex++;
201             }
202 
203             if (layoutManager instanceof TableLayoutManager && ((TableLayoutManager) layoutManager)
204                     .isRenderSequenceField()) {
205                 tableToolsColumnOptions.append("{\""
206                         + UifConstants.TableToolsKeys.SORT_TYPE
207                         + "\" : \""
208                         + UifConstants.TableToolsValues.NUMERIC
209                         + "\", "
210                         + "\""
211                         + UifConstants.TableToolsKeys.TARGETS
212                         + "\": ["
213                         + columnIndex
214                         + "]}, ");
215                 columnIndex++;
216                 if (actionIndex == 2 && actionFieldVisible) {
217                     String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
218                     tableToolsColumnOptions.append(actionColOptions + " , ");
219                     columnIndex++;
220                 }
221             }
222 
223             // skip select field if enabled
224             if (collectionGroup.isIncludeLineSelectionField()) {
225                 String colOptions = constructTableColumnOptions(columnIndex, false, null, null);
226                 tableToolsColumnOptions.append(colOptions + " , ");
227                 columnIndex++;
228             }
229 
230             // if data dictionary defines aoColumns, copy here and skip default sorting/visibility behaviour
231             if (!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS))) {
232                 // get the contents of the JS array string
233                 String jsArray = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS);
234                 int startBrace = StringUtils.indexOf(jsArray, "[");
235                 int endBrace = StringUtils.lastIndexOf(jsArray, "]");
236                 tableToolsColumnOptions.append(StringUtils.substring(jsArray, startBrace + 1, endBrace) + ", ");
237 
238                 if (actionFieldVisible && (actionIndex == -1 || actionIndex >= columnIndex)) {
239                     String actionColOptions = constructTableColumnOptions(actionIndex, false, null, null);
240                     tableToolsColumnOptions.append(actionColOptions);
241                 } else {
242                     tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
243                             ", "));
244                 }
245 
246                 tableToolsColumnOptions.append("]");
247                 getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMNS, tableToolsColumnOptions.toString());
248             } else if (!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS))
249                     && forceAoColumnDefsOverride) {
250                 String jsArray = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS);
251                 int startBrace = StringUtils.indexOf(jsArray, "[");
252                 int endBrace = StringUtils.lastIndexOf(jsArray, "]");
253                 tableToolsColumnOptions.append(StringUtils.substring(jsArray, startBrace + 1, endBrace) + ", ");
254 
255                 if (actionFieldVisible && (actionIndex == -1 || actionIndex >= columnIndex)) {
256                     String actionColOptions = constructTableColumnOptions(actionIndex, false, null, null);
257                     tableToolsColumnOptions.append(actionColOptions);
258                 } else {
259                     tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
260                             ", "));
261                 }
262 
263                 tableToolsColumnOptions.append("]");
264                 getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMN_DEFS,
265                         tableToolsColumnOptions.toString());
266             } else {
267 
268                 // TODO: does this handle multiple rows correctly?
269                 for (Component component : collectionGroup.getItems()) {
270                     if (actionFieldVisible && columnIndex + 1 == actionIndex) {
271                         String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
272                         tableToolsColumnOptions.append(actionColOptions + " , ");
273                         columnIndex++;
274                     }
275                     // for FieldGroup, get the first field from that group
276                     if (component instanceof FieldGroup) {
277                         component = ((FieldGroup) component).getItems().get(0);
278                     }
279 
280                     if (component instanceof DataField) {
281                         DataField field = (DataField) component;
282 
283                         // if a field is marked as invisible in hiddenColumns, append options and skip sorting
284                         if (getHiddenColumns() != null && getHiddenColumns().contains(field.getPropertyName())) {
285                             tableToolsColumnOptions.append("{"
286                                     + UifConstants.TableToolsKeys.VISIBLE
287                                     + ": "
288                                     + UifConstants.TableToolsValues.FALSE
289                                     + ", \""
290                                     + UifConstants.TableToolsKeys.TARGETS
291                                     + "\": ["
292                                     + columnIndex
293                                     + "]"
294                                     + "}, ");
295                             // if sortableColumns is present and a field is marked as sortable or unspecified
296                         } else if (getSortableColumns() != null && !getSortableColumns().isEmpty()) {
297                             if (getSortableColumns().contains(field.getPropertyName())) {
298                                 tableToolsColumnOptions.append(getDataFieldColumnOptions(columnIndex, collectionGroup,
299                                         field) + ", ");
300                             } else {
301                                 tableToolsColumnOptions.append("{'"
302                                         + UifConstants.TableToolsKeys.SORTABLE
303                                         + "': "
304                                         + UifConstants.TableToolsValues.FALSE
305                                         + ", \""
306                                         + UifConstants.TableToolsKeys.TARGETS
307                                         + "\": ["
308                                         + columnIndex
309                                         + "]"
310                                         + "}, ");
311                             }
312                         } else {// sortable columns not defined
313                             String colOptions = getDataFieldColumnOptions(columnIndex, collectionGroup, field);
314                             tableToolsColumnOptions.append(colOptions + " , ");
315                         }
316                         columnIndex++;
317                     } else if (component instanceof MessageField
318                             && component.getDataAttributes().get("role") != null
319                             && component.getDataAttributes().get("role").equals("grouping")) {
320                         //Grouping column is never shown, so skip
321                         tableToolsColumnOptions.append("{"
322                                 + UifConstants.TableToolsKeys.VISIBLE
323                                 + ": "
324                                 + UifConstants.TableToolsValues.FALSE
325                                 + ", \""
326                                 + UifConstants.TableToolsKeys.TARGETS
327                                 + "\": ["
328                                 + columnIndex
329                                 + "]"
330                                 + "}, ");
331                         columnIndex++;
332                     } else {
333                         String colOptions = constructTableColumnOptions(columnIndex, false, null, null);
334                         tableToolsColumnOptions.append(colOptions + " , ");
335                         columnIndex++;
336                     }
337 
338                 }
339 
340                 if (actionFieldVisible && (actionIndex == -1 || actionIndex >= columnIndex)) {
341                     String actionColOptions = constructTableColumnOptions(actionIndex, false, null, null);
342                     tableToolsColumnOptions.append(actionColOptions);
343                 } else {
344                     tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
345                             ", "));
346                 }
347                 //merge the aoColumnDefs passed in
348                 if (!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS))) {
349                     String origAoOptions = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS).trim();
350                     origAoOptions = StringUtils.removeStart(origAoOptions, "[");
351                     origAoOptions = StringUtils.removeEnd(origAoOptions, "]");
352                     tableToolsColumnOptions.append("," + origAoOptions);
353                 }
354                 tableToolsColumnOptions.append("]");
355                 getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMN_DEFS,
356                         tableToolsColumnOptions.toString());
357             }
358         }
359     }
360 
361     /**
362      * Construct the column options for a data field
363      *
364      * @param collectionGroup - the collectionGroup in which the data field is defined
365      * @param field - the field to construction options for
366      * @return - options as valid for datatable
367      */
368     protected String getDataFieldColumnOptions(int target, CollectionGroup collectionGroup, DataField field) {
369         String sortType = null;
370 
371         if (!collectionGroup.isReadOnly()
372                 && (field instanceof InputField)
373                 && ((InputField) field).getControl() != null) {
374             Control control = ((InputField) field).getControl();
375             if (control instanceof SelectControl) {
376                 sortType = UifConstants.TableToolsValues.DOM_SELECT;
377             } else if (control instanceof CheckboxControl || control instanceof CheckboxGroupControl) {
378                 sortType = UifConstants.TableToolsValues.DOM_CHECK;
379             } else if (control instanceof RadioGroupControl) {
380                 sortType = UifConstants.TableToolsValues.DOM_RADIO;
381             } else {
382                 sortType = UifConstants.TableToolsValues.DOM_TEXT;
383             }
384         } else {
385             sortType = UifConstants.TableToolsValues.DOM_TEXT;
386         }
387 
388         Class dataTypeClass = ObjectPropertyUtils.getPropertyType(collectionGroup.getCollectionObjectClass(),
389                 field.getPropertyName());
390 
391         return constructTableColumnOptions(target, true, dataTypeClass, sortType);
392     }
393 
394     /**
395      * Constructs the sort data type for each data table columns in a format that will be used to initialize the data
396      * table widget via javascript
397      *
398      * @param isSortable - whether a column should be marked as sortable
399      * @param dataTypeClass - the class type of the column value - used determine the sType option - which identifies
400      * the search plugin to use
401      * @param sortDataType - Defines a data source type for the sorting which can be used to read realtime information
402      * from the table
403      * @return a formatted string with data table options for one column
404      */
405     protected String constructTableColumnOptions(int target, boolean isSortable, Class dataTypeClass,
406             String sortDataType) {
407         String colOptions = "null";
408 
409         String sortType = "";
410         if (!isSortable || dataTypeClass == null || sortType == null) {
411             colOptions = "\"" + UifConstants.TableToolsKeys.SORTABLE + "\" : false, \"sType\" : \"string\"";
412         } else {
413             if (ClassUtils.isAssignable(dataTypeClass, KualiPercent.class)) {
414                 sortType = UifConstants.TableToolsValues.PERCENT;
415             } else if (ClassUtils.isAssignable(dataTypeClass, KualiInteger.class) || ClassUtils.isAssignable(
416                     dataTypeClass, KualiDecimal.class)) {
417                 sortType = UifConstants.TableToolsValues.CURRENCY;
418             } else if (ClassUtils.isAssignable(dataTypeClass, Timestamp.class)) {
419                 sortType = "date";
420             } else if (ClassUtils.isAssignable(dataTypeClass, java.sql.Date.class) || ClassUtils.isAssignable(
421                     dataTypeClass, java.util.Date.class)) {
422                 sortType = UifConstants.TableToolsValues.DATE;
423             } else if (ClassUtils.isAssignable(dataTypeClass, Number.class)) {
424                 sortType = UifConstants.TableToolsValues.NUMERIC;
425             } else {
426                 sortType = UifConstants.TableToolsValues.STRING;
427             }
428 
429             colOptions = "\"" + UifConstants.TableToolsKeys.SORT_DATA_TYPE + "\" : \"" + sortDataType + "\"";
430             colOptions += " , \"" + UifConstants.TableToolsKeys.SORT_TYPE + "\" : \"" + sortType + "\"";
431         }
432 
433         if (!colOptions.equals("null")) {
434             colOptions = "{" + colOptions + ", \"" + UifConstants.TableToolsKeys.TARGETS + "\": [" + target + "]}";
435         } else {
436             colOptions = "{" + colOptions + "}";
437         }
438 
439         return colOptions;
440     }
441 
442     /**
443      * Returns the text which is used to display text when the table is empty
444      *
445      * @return empty table message
446      */
447     @BeanTagAttribute(name = "emptyTableMessage")
448     public String getEmptyTableMessage() {
449         return emptyTableMessage;
450     }
451 
452     /**
453      * Setter for a text to be displayed when the table is empty
454      *
455      * @param emptyTableMessage
456      */
457     public void setEmptyTableMessage(String emptyTableMessage) {
458         this.emptyTableMessage = emptyTableMessage;
459     }
460 
461     /**
462      * Returns true if sorting is disabled
463      *
464      * @return the disableTableSort
465      */
466     @BeanTagAttribute(name = "disableTableSort")
467     public boolean isDisableTableSort() {
468         return this.disableTableSort;
469     }
470 
471     /**
472      * Enables/disables the table sorting
473      *
474      * @param disableTableSort the disableTableSort to set
475      */
476     public void setDisableTableSort(boolean disableTableSort) {
477         this.disableTableSort = disableTableSort;
478     }
479 
480     /**
481      * Returns true if search and export options are enabled
482      *
483      * @return the showSearchAndExportOptions
484      */
485     @BeanTagAttribute(name = "showSearchAndExportOptions")
486     public boolean isShowSearchAndExportOptions() {
487         return this.showSearchAndExportOptions;
488     }
489 
490     /**
491      * Show/Hide the search and export options in tabletools
492      *
493      * @param showSearchAndExportOptions the showSearchAndExportOptions to set
494      */
495     public void setShowSearchAndExportOptions(boolean showSearchAndExportOptions) {
496         this.showSearchAndExportOptions = showSearchAndExportOptions;
497     }
498 
499     /**
500      * Holds propertyNames for the ones meant to be hidden since columns are visible by default
501      *
502      * <p>Duplicate entries are ignored and the order of entries is not significant</p>
503      *
504      * @return a set with propertyNames of columns to be hidden
505      */
506     @BeanTagAttribute(name = "hiddenColumns", type = BeanTagAttribute.AttributeType.SETVALUE)
507     public Set<String> getHiddenColumns() {
508         return hiddenColumns;
509     }
510 
511     /**
512      * Setter for the hidden columns set
513      *
514      * @param hiddenColumns - a set containing propertyNames
515      */
516     public void setHiddenColumns(Set<String> hiddenColumns) {
517         this.hiddenColumns = hiddenColumns;
518     }
519 
520     /**
521      * Holds the propertyNames for columns that are to be sorted
522      *
523      * <p>Duplicate entries are ignored and the order of entries is not significant</p>
524      *
525      * @return a set of propertyNames with for columns that will be sorted
526      */
527     @BeanTagAttribute(name = "sortableColumns", type = BeanTagAttribute.AttributeType.SETVALUE)
528     public Set<String> getSortableColumns() {
529         return sortableColumns;
530     }
531 
532     /**
533      * Setter for sortable columns
534      *
535      * @param sortableColumns - a set containing propertyNames of columns to be sorted
536      */
537     public void setSortableColumns(Set<String> sortableColumns) {
538         this.sortableColumns = sortableColumns;
539     }
540 
541     /**
542      * Specifies a URL for acquiring the table data with ajax
543      *
544      * <p>
545      * When the ajax source URL is specified the rich table plugin will retrieve the data by invoking the URL and
546      * building the table rows from the result. This is different from the standard use of the rich table plugin
547      * with uses progressive enhancement to decorate a table that has already been rendereed
548      * </p>
549      *
550      * @return String URL for ajax source
551      */
552     @BeanTagAttribute(name = "ajaxSource")
553     public String getAjaxSource() {
554         return ajaxSource;
555     }
556 
557     /**
558      * Setter for the Ajax source URL
559      *
560      * @param ajaxSource
561      */
562     public void setAjaxSource(String ajaxSource) {
563         this.ajaxSource = ajaxSource;
564     }
565 
566     /**
567      * Get groupingOption
568      *
569      * @return
570      */
571     public String getGroupingOptionsJSString() {
572         return groupingOptionsJSString;
573     }
574 
575     /**
576      * Set the groupingOptions js data.  <b>This should not be set through XML configuration.</b>
577      *
578      * @param groupingOptionsJSString
579      */
580     public void setGroupingOptionsJSString(String groupingOptionsJSString) {
581         this.groupingOptionsJSString = groupingOptionsJSString;
582     }
583 
584     public boolean isForceAoColumnDefsOverride() {
585         return forceAoColumnDefsOverride;
586     }
587 
588     public void setForceAoColumnDefsOverride(boolean forceAoColumnDefsOverride) {
589         this.forceAoColumnDefsOverride = forceAoColumnDefsOverride;
590     }
591 }