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.widget;
017
018import org.apache.commons.lang.ClassUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.rice.core.api.util.type.KualiDecimal;
021import org.kuali.rice.core.api.util.type.KualiInteger;
022import org.kuali.rice.core.api.util.type.KualiPercent;
023import org.kuali.rice.krad.uif.UifConstants;
024import org.kuali.rice.krad.uif.container.CollectionGroup;
025import org.kuali.rice.krad.uif.control.Control;
026import org.kuali.rice.krad.uif.field.DataField;
027import org.kuali.rice.krad.uif.field.InputField;
028import org.kuali.rice.krad.uif.view.View;
029import org.kuali.rice.krad.uif.control.CheckboxControl;
030import org.kuali.rice.krad.uif.control.CheckboxGroupControl;
031import org.kuali.rice.krad.uif.control.RadioGroupControl;
032import org.kuali.rice.krad.uif.control.SelectControl;
033import org.kuali.rice.krad.uif.component.Component;
034import org.kuali.rice.krad.uif.field.FieldGroup;
035import org.kuali.rice.krad.uif.layout.LayoutManager;
036import org.kuali.rice.krad.uif.layout.TableLayoutManager;
037import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
038import org.kuali.rice.krad.web.form.UifFormBase;
039
040import java.sql.Timestamp;
041import java.util.Set;
042
043/**
044 * Decorates a HTML Table client side with various tools
045 *
046 * <p>
047 * Decorations implemented depend on widget implementation. Examples are
048 * sorting, paging and skinning.
049 * </p>
050 *
051 * @author Kuali Rice Team (rice.collab@kuali.org)
052 */
053public class RichTable extends WidgetBase {
054    private static final long serialVersionUID = 4671589690877390070L;
055
056    private String emptyTableMessage;
057    private boolean disableTableSort;
058    /** since columns are visible by default, this set holds propertyNames for the ones meant to be hidden*/
059    private Set<String> hiddenColumns;
060    /**holds the propertyNames for columns that are to be sorted*/
061    private Set<String> sortableColumns;
062
063
064    private boolean showSearchAndExportOptions = true;
065
066    public RichTable() {
067        super();
068    }
069
070    /**
071     * The following initialization is performed:
072     *
073     * <ul>
074     * <li>Initializes component options for empty table message</li>
075     * </ul>
076     */
077    @Override
078    public void performFinalize(View view, Object model, Component component) {
079        super.performFinalize(view, model, component);
080
081        UifFormBase formBase = (UifFormBase) model;
082
083        if (isRender()) {
084            if (StringUtils.isNotBlank(getEmptyTableMessage())) {
085                getComponentOptions().put(UifConstants.TableToolsKeys.LANGUAGE,
086                        "{\"" + UifConstants.TableToolsKeys.EMPTY_TABLE + "\" : \"" + getEmptyTableMessage() + "\"}");
087            }
088
089            if (!isShowSearchAndExportOptions()) {
090                Object domOption = getComponentOptions().get(UifConstants.TableToolsKeys.SDOM);
091                if (domOption instanceof String) {
092                    String sDomOption = (String) domOption;
093                    if (StringUtils.isNotBlank(sDomOption)) {
094                        sDomOption = StringUtils.remove(sDomOption, "T"); //Removes Export option
095                        sDomOption = StringUtils.remove(sDomOption, "f"); //Removes search option
096                        getComponentOptions().put(UifConstants.TableToolsKeys.SDOM, sDomOption);
097                    }
098                }
099
100            }
101
102            // for add events, disable initial sorting
103            if (UifConstants.ActionEvents.ADD_LINE.equals(formBase.getActionEvent())) {
104                getComponentOptions().put(UifConstants.TableToolsKeys.AASORTING, "[]");
105            }
106
107            if (component instanceof CollectionGroup) {
108                buildTableOptions((CollectionGroup) component);
109            }
110
111            if (isDisableTableSort()) {
112                getComponentOptions().put(UifConstants.TableToolsKeys.TABLE_SORT, "false");
113            }
114        }
115    }
116
117    /**
118     * Builds column options for sorting
119     *
120     * @param collectionGroup
121     */
122    protected void buildTableOptions(CollectionGroup collectionGroup) {
123        LayoutManager layoutManager = collectionGroup.getLayoutManager();
124
125        // if sub collection exists, don't allow the table sortable
126        if (!collectionGroup.getSubCollections().isEmpty()) {
127            setDisableTableSort(true);
128        }
129
130       if (!isDisableTableSort()) {
131            // if rendering add line, skip that row from col sorting
132            if (collectionGroup.isRenderAddLine()
133                    && !collectionGroup.isReadOnly()
134                    && !((layoutManager instanceof TableLayoutManager) && ((TableLayoutManager) layoutManager)
135                    .isSeparateAddLine())) {
136                getComponentOptions().put(UifConstants.TableToolsKeys.SORT_SKIP_ROWS,
137                        "[" + UifConstants.TableToolsValues.ADD_ROW_DEFAULT_INDEX + "]");
138            }
139
140            StringBuffer tableToolsColumnOptions = new StringBuffer("[");
141
142            if (layoutManager instanceof TableLayoutManager && ((TableLayoutManager) layoutManager)
143                    .isRenderSequenceField()) {
144                tableToolsColumnOptions.append(" null ,");
145            }
146
147            // skip select field if enabled
148            if (collectionGroup.isRenderSelectField()) {
149                String colOptions = constructTableColumnOptions(false, null, null);
150                tableToolsColumnOptions.append(colOptions + " , ");
151            }
152
153            // if data dictionary defines aoColumns, copy here and skip default sorting/visibility behaviour
154            if (!StringUtils.isEmpty(getComponentOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS))) {
155                // get the contents of the JS array string
156                String jsArray = getComponentOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS);
157                int startBrace = StringUtils.indexOf(jsArray,"[");
158                int endBrace = StringUtils.lastIndexOf(jsArray, "]");
159                tableToolsColumnOptions.append(StringUtils.substring(jsArray, startBrace + 1, endBrace) + " , ");
160            } else {
161                    // use layout manager sortableColumns and hiddenColumns if set
162                    Set<String> currentSortableColumns =  getSortableColumns();
163                    Set<String> currentHiddenColumns =  getHiddenColumns();
164                    if (layoutManager instanceof TableLayoutManager) {
165                        TableLayoutManager tableLayoutMgr = (TableLayoutManager) layoutManager;
166                        if (tableLayoutMgr.getSortableColumns() != null && !tableLayoutMgr.getSortableColumns().isEmpty()) {
167                            currentSortableColumns = tableLayoutMgr.getSortableColumns();
168                        }
169                        if (tableLayoutMgr.getHiddenColumns() != null && !tableLayoutMgr.getHiddenColumns().isEmpty()) {
170                            currentHiddenColumns = tableLayoutMgr.getHiddenColumns();
171                        }
172                    }
173                // TODO: does this handle multiple rows correctly?
174                for (Component component : collectionGroup.getItems()) {
175                    // for FieldGroup, get the first field from that group
176                    if (component instanceof FieldGroup) {
177                        component = ((FieldGroup) component).getItems().get(0);
178                    }
179
180                    if (component instanceof DataField) {
181                        DataField field = (DataField) component;
182                        // if a field is marked as invisible in hiddenColumns, append options and skip sorting
183                        if (currentHiddenColumns != null && currentHiddenColumns.contains(field.getPropertyName())) {
184                            tableToolsColumnOptions.append("{" + UifConstants.TableToolsKeys.VISIBLE + ": " + UifConstants.TableToolsValues.FALSE + "}, ");
185                        // if sortableColumns is present and a field is marked as sortable or unspecified
186                        } else if (currentSortableColumns != null && !currentSortableColumns.isEmpty()) {
187                            if (currentSortableColumns.contains(field.getPropertyName())) {
188                                tableToolsColumnOptions.append(getDataFieldColumnOptions(collectionGroup, field) + ", ");
189                            } else {
190                                tableToolsColumnOptions.append("{'" + UifConstants.TableToolsKeys.SORTABLE + "': " + UifConstants.TableToolsValues.FALSE + "}, ");
191                            }
192                        } else {// sortable columns not defined
193                            String colOptions = getDataFieldColumnOptions(collectionGroup, field);
194                            tableToolsColumnOptions.append(colOptions + " , ");
195                        }
196                    } else {
197                        String colOptions = constructTableColumnOptions(false, null, null);
198                        tableToolsColumnOptions.append(colOptions + " , ");
199                    }
200                }
201            }
202
203            if (collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly()) {
204                String colOptions = constructTableColumnOptions(false, null, null);
205                tableToolsColumnOptions.append(colOptions);
206            } else {
207                tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
208                        ", "));
209            }
210
211            tableToolsColumnOptions.append("]");
212
213            getComponentOptions().put(UifConstants.TableToolsKeys.AO_COLUMNS, tableToolsColumnOptions.toString());
214       }
215    }
216
217    /**
218     * construct the column options for a data field
219     *
220     * @param collectionGroup - the collectionGroup in which the data field is defined
221     * @param field - the field to construction options for
222     * @return - options as valid for datatable
223     */
224    private String getDataFieldColumnOptions(CollectionGroup collectionGroup, DataField field) {
225        String sortType = null;
226        if (!collectionGroup.isReadOnly() && (field instanceof InputField)
227                && ((InputField) field).getControl() != null) {
228            Control control = ((InputField) field).getControl();
229            if (control instanceof SelectControl) {
230                sortType = UifConstants.TableToolsValues.DOM_SELECT;
231            } else if (control instanceof CheckboxControl || control instanceof CheckboxGroupControl) {
232                sortType = UifConstants.TableToolsValues.DOM_CHECK;
233            } else if (control instanceof RadioGroupControl) {
234                sortType = UifConstants.TableToolsValues.DOM_RADIO;
235            } else {
236                sortType = UifConstants.TableToolsValues.DOM_TEXT;
237            }
238        } else {
239            sortType = UifConstants.TableToolsValues.DOM_TEXT;
240        }
241
242        Class dataTypeClass = ObjectPropertyUtils.getPropertyType(collectionGroup.getCollectionObjectClass(),
243                field.getPropertyName());
244        return constructTableColumnOptions(true, dataTypeClass, sortType);
245    }
246
247    /**
248     * Constructs the sort data type for each datatable columns.
249     */
250    protected String constructTableColumnOptions(boolean isSortable, Class dataTypeClass, String sortDataType) {
251        String colOptions = "null";
252
253        String sortType = "";
254        if (!isSortable || dataTypeClass == null || sortType == null) {
255            colOptions = "\"" + UifConstants.TableToolsKeys.SORTABLE + "\" : false, \"sType\" : \"string\"";
256        } else {
257            if (ClassUtils.isAssignable(dataTypeClass, KualiPercent.class)) {
258                sortType = UifConstants.TableToolsValues.PERCENT;
259            } else if (ClassUtils.isAssignable(dataTypeClass, KualiInteger.class) || ClassUtils.isAssignable(
260                    dataTypeClass, KualiDecimal.class)) {
261                sortType = UifConstants.TableToolsValues.CURRENCY;
262            } else if (ClassUtils.isAssignable(dataTypeClass, Timestamp.class)) {
263                sortType = "date";
264            } else if (ClassUtils.isAssignable(dataTypeClass, java.sql.Date.class) || ClassUtils.isAssignable(
265                    dataTypeClass, java.util.Date.class)) {
266                sortType = UifConstants.TableToolsValues.DATE;
267            } else if (ClassUtils.isAssignable(dataTypeClass, Number.class)) {
268                sortType = UifConstants.TableToolsValues.NUMERIC;
269            }
270            else {
271                sortType = UifConstants.TableToolsValues.STRING;
272            }
273
274            colOptions = "\"" + UifConstants.TableToolsKeys.SORT_DATA_TYPE + "\" : \"" + sortDataType + "\"";
275            colOptions += " , \"" + UifConstants.TableToolsKeys.SORT_TYPE + "\" : \"" + sortType + "\"";
276        }
277
278        colOptions = "{" + colOptions + "}";
279
280        return colOptions;
281    }
282
283    /**
284     * Returns the text which is used to display text when the table is empty
285     *
286     * @return empty table message
287     */
288    public String getEmptyTableMessage() {
289        return emptyTableMessage;
290    }
291
292    /**
293     * Setter for a text to be displayed when the table is empty
294     *
295     * @param emptyTableMessage
296     */
297    public void setEmptyTableMessage(String emptyTableMessage) {
298        this.emptyTableMessage = emptyTableMessage;
299    }
300
301    /**
302     * Returns true if sorting is disabled
303     *
304     * @return the disableTableSort
305     */
306    public boolean isDisableTableSort() {
307        return this.disableTableSort;
308    }
309
310    /**
311     * Enables/disables the table sorting
312     *
313     * @param disableTableSort the disableTableSort to set
314     */
315    public void setDisableTableSort(boolean disableTableSort) {
316        this.disableTableSort = disableTableSort;
317    }
318
319    /**
320     * Returns true if search and export options are enabled
321     *
322     * @return the showSearchAndExportOptions
323     */
324    public boolean isShowSearchAndExportOptions() {
325        return this.showSearchAndExportOptions;
326    }
327
328    /**
329     * Show/Hide the search and export options in tabletools
330     *
331     * @param showSearchAndExportOptions the showSearchAndExportOptions to set
332     */
333    public void setShowSearchAndExportOptions(boolean showSearchAndExportOptions) {
334        this.showSearchAndExportOptions = showSearchAndExportOptions;
335    }
336
337    public Set<String> getHiddenColumns() {
338        return hiddenColumns;
339    }
340
341    public void setHiddenColumns(Set<String> hiddenColumns) {
342        this.hiddenColumns = hiddenColumns;
343    }
344
345    public Set<String> getSortableColumns() {
346        return sortableColumns;
347    }
348
349    public void setSortableColumns(Set<String> sortableColumns) {
350        this.sortableColumns = sortableColumns;
351    }
352}