001/*
002 * #%L
003 * GwtMaterial
004 * %%
005 * Copyright (C) 2015 - 2016 GwtMaterialDesign
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 * 
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 * 
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package gwt.material.design.client.data;
021
022import com.google.gwt.cell.client.Cell.Context;
023import com.google.gwt.core.client.Scheduler;
024import com.google.gwt.dom.client.Style;
025import com.google.gwt.dom.client.Style.Cursor;
026import com.google.gwt.dom.client.Style.Display;
027import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
028import gwt.material.design.client.base.constants.StyleName;
029import gwt.material.design.client.base.constants.TableCssName;
030import gwt.material.design.client.constants.HideOn;
031import gwt.material.design.client.constants.IconSize;
032import gwt.material.design.client.constants.IconType;
033import gwt.material.design.client.constants.TextAlign;
034import gwt.material.design.client.constants.WavesType;
035import gwt.material.design.client.data.component.CategoryComponent;
036import gwt.material.design.client.data.component.Component;
037import gwt.material.design.client.data.component.RowComponent;
038import gwt.material.design.client.ui.MaterialCheckBox;
039import gwt.material.design.client.ui.MaterialIcon;
040import gwt.material.design.client.ui.html.Div;
041import gwt.material.design.client.ui.table.TableData;
042import gwt.material.design.client.ui.table.TableHeader;
043import gwt.material.design.client.ui.table.TableRow;
044import gwt.material.design.client.ui.table.TableSubHeader;
045import gwt.material.design.client.ui.table.cell.Column;
046import gwt.material.design.client.ui.table.cell.WidgetColumn;
047
048import java.util.List;
049import java.util.Map;
050import java.util.logging.Logger;
051
052/**
053 * Base Component Renderer used by {@link AbstractDataView}.
054 * <br><br>
055 * This can be extended upon or can be replaced all together.
056 *
057 * @author Ben Dol
058 */
059public class BaseRenderer<T> implements Renderer<T> {
060
061    private final Logger logger = Logger.getLogger(BaseRenderer.class.getName());
062
063    // Configurations
064    private int calculatedRowHeight = 55;
065    private int expectedRowHeight = calculatedRowHeight;
066
067    private IconType sortAscIcon = IconType.ARROW_UPWARD;
068    private IconType sortDescIcon = IconType.ARROW_DOWNWARD;
069    protected IconSize sortIconSize = IconSize.TINY;
070
071    @Override
072    public void copy(Renderer<T> renderer) {
073        expectedRowHeight = renderer.getExpectedRowHeight();
074        calculatedRowHeight = renderer.getCalculatedRowHeight();
075        // Right now we only copy the height data
076        /*sortAscIcon = renderer.getSortAscIcon();
077        sortDescIcon = renderer.getSortDescIcon();
078        sortIconSize = renderer.getSortIconSize();*/
079    }
080
081    @Override
082    public TableRow drawRow(DataView<T> dataView, RowComponent<T> rowComponent, Object valueKey,
083                            List<Column<T, ?>> columns, boolean redraw) {
084        T data = rowComponent.getData();
085        TableRow row = rowComponent.getWidget();
086        boolean draw = true;
087        if(row == null) {
088            // Create a new row element
089            row = new TableRow();
090            Style style = row.getElement().getStyle();
091            style.setDisplay(Display.NONE);
092            style.setProperty("height", getExpectedRowHeight() + "px");
093            style.setProperty("maxHeight", getExpectedRowHeight() + "px");
094            style.setProperty("minHeight", getExpectedRowHeight() + "px");
095            row.setStyleName(TableCssName.DATA_ROW);
096            rowComponent.setWidget(row);
097
098            if(!dataView.getSelectionType().equals(SelectionType.NONE)) {
099                TableData selection = drawSelectionCell();
100                row.add(selection);
101            }
102        } else if(!redraw && !rowComponent.isRedraw()) {
103            draw = false;
104        }
105
106        if(draw) {
107            // Build the columns
108            int colOffset = dataView.getColumnOffset();
109            int colSize = columns.size();
110
111            for(int c = 0; c < colSize; c++) {
112                int colIndex = c + colOffset;
113                Context context = new Context(rowComponent.getIndex(), colIndex, valueKey);
114                drawColumn(row, context, data, columns.get(c), colIndex, dataView.isHeaderVisible(colIndex));
115            }
116            rowComponent.setRedraw(false);
117        }
118
119        if(dataView.isUseRowExpansion()) {
120            if(!row.hasExpansionColumn()) {
121                TableData expand = new TableData();
122                expand.setId("colex");
123                MaterialIcon expandIcon = new MaterialIcon();
124                expandIcon.setId("expand");
125                expandIcon.setWidth("100%");
126                expandIcon.setIconType(IconType.KEYBOARD_ARROW_DOWN);
127                expandIcon.setWaves(WavesType.LIGHT);
128                expandIcon.getElement().getStyle().setCursor(Cursor.POINTER);
129                expand.add(expandIcon);
130                row.add(expand);
131            }
132        } else if(row.hasExpansionColumn()) {
133            row.removeExpansionColumn();
134        }
135
136        Scheduler.get().scheduleDeferred(() -> {
137            calculateRowHeight(rowComponent);
138        });
139        return row;
140    }
141
142    @Override
143    public TableSubHeader drawCategory(CategoryComponent category) {
144        if(category != null) {
145            TableSubHeader subHeader = category.getWidget();
146            if(subHeader == null) {
147                subHeader = category.render();
148                assert subHeader != null : "rendered category TableSubHeader cannot be null.";
149                subHeader.setHeight(category.getHeight());
150            }
151            return subHeader;
152        }
153
154        // No subheader was added
155        return null;
156    }
157
158    @Override
159    public TableRow drawCustom(Component<?> component) {
160        return new TableRow();
161    }
162
163    @Override
164    public TableData drawSelectionCell() {
165        TableData checkBox = new TableData();
166        checkBox.setId("col0");
167        checkBox.setStyleName(TableCssName.SELECTION);
168        new MaterialCheckBox(checkBox.getElement());
169        checkBox.addClickHandler(event -> {
170            event.getNativeEvent().preventDefault();
171        });
172        return checkBox;
173    }
174
175    @Override
176    @SuppressWarnings("unchecked")
177    public TableData drawColumn(TableRow row, Context context, T rowValue, Column<T, ?> column,
178                                int beforeIndex, boolean visible) {
179        TableData data = null;
180        if(row != null && rowValue != null) {
181            data = row.getColumn(beforeIndex);
182            if(data == null) {
183                data = new TableData();
184                row.insert(data, beforeIndex);
185            } else {
186                data.clear();
187            }
188
189            Div wrapper = new Div();
190
191            // Render the column cell
192            if(column instanceof WidgetColumn) {
193                wrapper.setStyleName(TableCssName.WIDGET_CELL);
194                wrapper.add(((WidgetColumn) column).render(context, rowValue));
195            } else {
196                SafeHtmlBuilder sb = new SafeHtmlBuilder();
197                column.render(context, rowValue, sb);
198                wrapper.getElement().setInnerHTML(sb.toSafeHtml().asString());
199                wrapper.setStyleName(TableCssName.CELL);
200            }
201
202            data.add(wrapper);
203
204            data.setId("col" + beforeIndex);
205            data.setDataTitle(column.getName());
206            HideOn hideOn = column.getHideOn();
207            if(hideOn != null) {
208                data.setHideOn(hideOn);
209            }
210            TextAlign textAlign = column.getTextAlign();
211            if(textAlign != null) {
212                data.setTextAlign(textAlign);
213            }
214            if(column.isNumeric()) {
215                data.addStyleName(TableCssName.NUMERIC);
216            }
217
218            // Apply the style properties
219            Style style = data.getElement().getStyle();
220            Map<StyleName, String> styleProps = column.getStyleProperties();
221            if(styleProps != null) {
222                styleProps.forEach((s, v) -> style.setProperty(s.styleName(), v));
223            }
224
225            // Hide if defined as not visible
226            // This can be the case when a header is toggled off.
227            if(!visible) {
228                data.$this().hide();
229            }
230        }
231        return data;
232    }
233
234    @Override
235    public TableHeader drawColumnHeader(Column<T, ?> column, String header, int index) {
236        MaterialIcon sortIcon = new MaterialIcon();
237        sortIcon.setIconSize(sortIconSize);
238
239        TableHeader th = new TableHeader(sortIcon);
240        th.setId("col" + index);
241        th.setHeader(header);
242        HideOn hideOn = column.getHideOn();
243        if(hideOn != null) {
244            th.setHideOn(hideOn);
245        }
246        TextAlign textAlign = column.getTextAlign();
247        if(textAlign != null) {
248            th.setTextAlign(textAlign);
249        }
250        if(column.isNumeric()) {
251            th.addStyleName(TableCssName.NUMERIC);
252        }
253
254        // Apply the style properties
255        Style style = th.getElement().getStyle();
256        Map<StyleName, String> styleProps = column.getStyleProperties();
257        if(styleProps != null) {
258            styleProps.forEach((s, v) -> style.setProperty(s.styleName(), v));
259        }
260
261        // Set the headers width
262        String width = column.getWidth();
263        if(width != null) {
264            th.setWidth(width);
265        }
266        th.setVisible(true);
267        return th;
268    }
269
270    @Override
271    public void drawSortIcon(TableHeader th, SortContext<T> sortContext) {
272        if(sortContext.getSortDir().equals(SortDir.ASC)) {
273            th.getSortIcon().setIconType(sortAscIcon);
274        } else {
275            th.getSortIcon().setIconType(sortDescIcon);
276        }
277    }
278
279    @Override
280    public int getExpectedRowHeight() {
281        return expectedRowHeight;
282    }
283
284    @Override
285    public void setExpectedRowHeight(int expectedRowHeight) {
286        if(expectedRowHeight < 33) {
287            logger.warning("Expected row height must be 33px or higher, setting row height to 33px.");
288            this.expectedRowHeight = 33;
289        } else {
290            this.expectedRowHeight = expectedRowHeight;
291        }
292    }
293
294    @Override
295    public void calculateRowHeight(RowComponent<T> row) {
296        TableRow element = row.getWidget();
297        if(element != null) {
298            int rowHeight = element.$this().outerHeight(true);
299            if (rowHeight > 0 && rowHeight != calculatedRowHeight) {
300                calculatedRowHeight = rowHeight;
301            }
302        }
303    }
304
305    @Override
306    public int getCalculatedRowHeight() {
307        return calculatedRowHeight;
308    }
309
310    @Override
311    public IconType getSortAscIcon() {
312        return sortAscIcon;
313    }
314
315    @Override
316    public void setSortAscIcon(IconType sortAscIcon) {
317        this.sortAscIcon = sortAscIcon;
318    }
319
320    @Override
321    public IconType getSortDescIcon() {
322        return sortDescIcon;
323    }
324
325    @Override
326    public void setSortDescIcon(IconType sortDescIcon) {
327        this.sortDescIcon = sortDescIcon;
328    }
329
330    @Override
331    public IconSize getSortIconSize() {
332        return sortIconSize;
333    }
334
335    @Override
336    public void setSortIconSize(IconSize sortIconSize) {
337        this.sortIconSize = sortIconSize;
338    }
339}