001/* 002 * #%L 003 * GwtMaterial 004 * %% 005 * Copyright (C) 2015 - 2017 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.ui.table; 021 022import com.google.gwt.dom.client.Style; 023import com.google.gwt.event.logical.shared.AttachEvent; 024import com.google.gwt.event.shared.HandlerRegistration; 025import com.google.gwt.user.client.ui.Panel; 026import gwt.material.design.client.base.constants.TableCssName; 027import gwt.material.design.client.data.events.InsertColumnEvent; 028import gwt.material.design.client.data.events.InsertColumnHandler; 029import gwt.material.design.client.data.events.RemoveColumnEvent; 030import gwt.material.design.client.data.events.RemoveColumnHandler; 031import gwt.material.design.client.data.events.SetupHandler; 032import gwt.material.design.client.ui.table.events.StretchEvent; 033import gwt.material.design.client.ui.table.events.StretchHandler; 034import gwt.material.design.jquery.client.api.JQueryElement; 035import gwt.material.design.client.constants.Alignment; 036import gwt.material.design.client.constants.HideOn; 037import gwt.material.design.client.constants.IconType; 038import gwt.material.design.client.constants.WavesType; 039import gwt.material.design.client.data.DataView; 040import gwt.material.design.client.js.Js; 041import gwt.material.design.client.js.JsTableElement; 042import gwt.material.design.client.ui.MaterialCheckBox; 043import gwt.material.design.client.ui.MaterialDropDown; 044import gwt.material.design.client.ui.MaterialIcon; 045import gwt.material.design.client.ui.html.ListItem; 046import gwt.material.design.client.ui.html.Span; 047import gwt.material.design.client.ui.table.cell.Column; 048 049import static gwt.material.design.jquery.client.api.JQuery.$; 050 051/** 052 * The standard Material data table with custom "outer" components such as, 053 * table icon, table title, stretch functionality, column toggling, etc. 054 * 055 * @author Ben Dol 056 * 057 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#datatable">Material Data Table</a> 058 * @see <a href="https://material.io/guidelines/components/data-tables.html">Material Design Specification</a> 059 */ 060public class MaterialDataTable<T> extends AbstractDataTable<T> implements InsertColumnHandler<T>, RemoveColumnHandler { 061 062 private JQueryElement stretchContainer; 063 064 // Interface 065 private MaterialIcon tableIcon; 066 private Span tableTitle; 067 private MaterialIcon stretchIcon; 068 private MaterialIcon columnMenuIcon; 069 private MaterialDropDown menu; 070 071 public MaterialDataTable() { 072 loadInternalEvents(); 073 } 074 075 public MaterialDataTable(DataView<T> dataView) { 076 super(dataView); 077 loadInternalEvents(); 078 } 079 080 public MaterialDataTable(TableScaffolding scaffolding) { 081 super(scaffolding); 082 loadInternalEvents(); 083 } 084 085 public MaterialDataTable(DataView<T> dataView, TableScaffolding scaffolding) { 086 super(dataView, scaffolding); 087 loadInternalEvents(); 088 } 089 090 @Override 091 protected void onLoad() { 092 super.onLoad(); 093 094 // Attempt to rebuild encase the widgets have been unloaded. 095 // In some use cases (like GWTP) the child widgets aren't unloaded. 096 // So we make sure we check the state of each Widget. 097 build(); 098 } 099 100 @Override 101 protected void build() { 102 super.build(); 103 104 Panel infoPanel = scaffolding.getInfoPanel(); 105 Panel toolPanel = scaffolding.getToolPanel(); 106 107 if(tableIcon == null || !tableIcon.isAttached()) { 108 // table icon 109 tableIcon = new MaterialIcon(IconType.VIEW_LIST); 110 infoPanel.add(tableIcon); 111 } 112 113 if(tableTitle == null || !tableTitle.isAttached()) { 114 // table title 115 tableTitle = new Span("Table Title"); 116 tableTitle.addStyleName(TableCssName.TITLE); 117 infoPanel.add(tableTitle); 118 } 119 120 if(stretchIcon == null || !stretchIcon.isAttached()) { 121 // stretch icon 122 stretchIcon = new MaterialIcon(IconType.FULLSCREEN); 123 stretchIcon.setWaves(WavesType.LIGHT); 124 stretchIcon.setCircle(true); 125 stretchIcon.setId("stretch"); 126 stretchIcon.getElement().getStyle().setCursor(Style.Cursor.POINTER); 127 toolPanel.add(stretchIcon); 128 } 129 130 if(columnMenuIcon == null || !columnMenuIcon.isAttached()) { 131 // menu icon 132 columnMenuIcon = new MaterialIcon(IconType.MORE_VERT); 133 columnMenuIcon.setHideOn(HideOn.HIDE_ON_SMALL_DOWN); 134 columnMenuIcon.setWaves(WavesType.LIGHT); 135 columnMenuIcon.setCircle(true); 136 columnMenuIcon.setId("columnToggle"); 137 columnMenuIcon.getElement().getStyle().setCursor(Style.Cursor.POINTER); 138 toolPanel.add(columnMenuIcon); 139 } 140 141 if(stretchContainer == null) { 142 // stretch container 143 stretchContainer = $("body"); 144 } 145 146 setupToolPanel(); 147 setupMenu(); 148 } 149 150 protected void setupToolPanel() { 151 // Stretch click handler 152 $(scaffolding.getToolPanel()).find("i#stretch").off("click").on("click", e -> { 153 stretch(); 154 155 e.preventDefault(); 156 return true; 157 }); 158 } 159 160 protected void setupMenu() { 161 // Setup menu checkboxes 162 // This will allow the user to toggle columns 163 164 if(menu == null) { 165 // dropdown structure 166 menu = new MaterialDropDown(columnMenuIcon); 167 scaffolding.getToolPanel().add(menu); 168 169 // Menu initialization 170 menu.setInDuration(300); 171 menu.setOutDuration(225); 172 menu.setConstrainWidth(false); 173 menu.setHover(false); 174 menu.setGutter(0); 175 menu.setBelowOrigin(false); 176 menu.setAlignment(Alignment.LEFT); 177 menu.setHideOn(HideOn.HIDE_ON_SMALL_DOWN); 178 menu.getElement().getStyle().setProperty("minWidth", "200px"); 179 } 180 181 JQueryElement $menu = $(menu); 182 $menu.find("li label").off("tap click"); 183 $menu.find("li label").on("tap click", e -> { 184 JQueryElement $this = $(e.getCurrentTarget()); 185 186 String forBox = ((String) $this.attr("for")).replace(getView().getId() + "-", ""); 187 if(Js.isTrue(forBox)) { 188 JQueryElement thd = $("th#" + forBox + ",td#" + forBox, this); 189 boolean checked = $this.prev().is(":checked"); 190 191 thd.each((index, el) -> { 192 JQueryElement cell = $(el); 193 if(checked) { 194 cell.hide(); 195 } else { 196 cell.show(); 197 } 198 }); 199 200 // Update the sticky table header widths 201 scaffolding.getTable().getJsElement().stickyTableHeaders("updateWidth"); 202 203 // Recalculate the subheader 204 getView().getSubheaderLib().recalculate(true); 205 } 206 return true; 207 }); 208 209 // Stop each menu item from closing the dropdown 210 $menu.find("li").off("touchstart click"); 211 $menu.find("li").on("touchstart click", e -> { 212 e.stopPropagation(); 213 return true; 214 }); 215 } 216 217 @Override 218 public void onInsertColumn(InsertColumnEvent<T> event) { 219 int beforeIndex = event.getBeforeIndex(); 220 Column<T, ?> column = event.getColumn(); 221 String header = event.getHeader(); 222 223 SetupHandler handler = e -> { 224 int index = beforeIndex + getView().getColumnOffset(); 225 String ref = getView().getId() + "-col" + index; 226 227 MaterialCheckBox toggleBox = new MaterialCheckBox(new ListItem().getElement()); 228 JQueryElement input = $(toggleBox).find("input"); 229 input.attr("id", ref); 230 231 JQueryElement label = $(toggleBox).find("label"); 232 label.text(column.getName()); 233 label.attr("for", ref); 234 235 toggleBox.setValue(true); 236 menu.add(toggleBox); 237 238 // We will hide the empty header menu items 239 if (header.isEmpty()) { 240 toggleBox.setVisible(false); 241 } 242 243 setupMenu(); 244 reindexToggles(); 245 }; 246 247 if(getView().isSetup()) { 248 handler.onSetup(null); 249 } else { 250 addSetupHandler(handler); 251 } 252 } 253 254 @Override 255 public void onRemoveColumn(RemoveColumnEvent event) { 256 SetupHandler handler = e -> { 257 int index = event.getIndex() + getView().getColumnOffset(); 258 $(menu).find("li input#col" + index).parent().remove(); 259 reindexToggles(); 260 }; 261 262 if(getView().isSetup()) { 263 handler.onSetup(null); 264 } else { 265 addSetupHandler(handler); 266 } 267 } 268 269 private void reindexToggles() { 270 int colOffset = getView().getColumnOffset(); 271 $("li", menu).each((index, e) -> { 272 String ref = getView().getId() + "-col" + ((Double)index + colOffset); 273 274 JQueryElement input = $(e).find("input"); 275 input.attr("id", ref); 276 277 JQueryElement label = $(e).find("label"); 278 label.attr("for", ref); 279 }); 280 } 281 282 public void stretch() { 283 stretch(true); 284 } 285 286 public void stretch(boolean fireEvent) { 287 $this().toggleClass(TableCssName.STRETCH); 288 289 // Make sure the body doesn't display scrollbar 290 body().toggleClass(TableCssName.OVERFLOW_HIDDEN); 291 292 // Update table header widths 293 JsTableElement tableJs = scaffolding.getTable().getJsElement(); 294 tableJs.stickyTableHeaders("updateWidth"); 295 tableJs.stickyTableHeaders("toggleHeaders"); 296 297 // Recalculate subheaders 298 getView().getSubheaderLib().recalculate(true); 299 300 if(fireEvent) { 301 // Fire table stretch event 302 StretchEvent.fire(this, $this().hasClass(TableCssName.STRETCH)); 303 } 304 } 305 306 public MaterialIcon getStretchIcon() { 307 return stretchIcon; 308 } 309 310 public MaterialIcon getColumnMenuIcon() { 311 return columnMenuIcon; 312 } 313 314 public MaterialIcon getTableIcon() { 315 return tableIcon; 316 } 317 318 public MaterialDropDown getMenu() { 319 return menu; 320 } 321 322 public Span getTableTitle() { 323 return tableTitle; 324 } 325 326 /** 327 * Add a handler that is triggered when the table is stretched. 328 */ 329 public HandlerRegistration addStretchHandler(StretchHandler handler) { 330 return addHandler(handler, StretchEvent.TYPE); 331 } 332 333 /** 334 * Load events 335 */ 336 protected void loadInternalEvents() { 337 // Register data view events, these are removed onUnload. 338 addInsertColumnHandler(this); 339 addRemoveColumnHandler(this); 340 } 341}