001    /**
002     * Copyright 2005-2012 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     */
016    package org.kuali.rice.krad.uif.widget;
017    
018    import org.apache.commons.lang.ClassUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.kuali.rice.core.api.util.type.KualiDecimal;
021    import org.kuali.rice.core.api.util.type.KualiInteger;
022    import org.kuali.rice.core.api.util.type.KualiPercent;
023    import org.kuali.rice.krad.uif.UifConstants;
024    import org.kuali.rice.krad.uif.container.CollectionGroup;
025    import org.kuali.rice.krad.uif.control.Control;
026    import org.kuali.rice.krad.uif.field.DataField;
027    import org.kuali.rice.krad.uif.field.InputField;
028    import org.kuali.rice.krad.uif.view.View;
029    import org.kuali.rice.krad.uif.control.CheckboxControl;
030    import org.kuali.rice.krad.uif.control.CheckboxGroupControl;
031    import org.kuali.rice.krad.uif.control.RadioGroupControl;
032    import org.kuali.rice.krad.uif.control.SelectControl;
033    import org.kuali.rice.krad.uif.component.Component;
034    import org.kuali.rice.krad.uif.field.FieldGroup;
035    import org.kuali.rice.krad.uif.layout.LayoutManager;
036    import org.kuali.rice.krad.uif.layout.TableLayoutManager;
037    import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
038    import org.kuali.rice.krad.web.form.UifFormBase;
039    
040    import java.sql.Timestamp;
041    import 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     */
053    public 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    }