001package gwt.material.design.client.ui; 002 003/* 004 * #%L 005 * GwtMaterial 006 * %% 007 * Copyright (C) 2015 GwtMaterialDesign 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import com.google.gwt.core.client.Scheduler; 024import com.google.gwt.core.client.Scheduler.ScheduledCommand; 025import com.google.gwt.dom.client.Document; 026import com.google.gwt.dom.client.Element; 027import com.google.gwt.dom.client.Style; 028import com.google.gwt.dom.client.Style.Unit; 029import com.google.gwt.event.dom.client.ClickEvent; 030import com.google.gwt.event.dom.client.ClickHandler; 031import com.google.gwt.uibinder.client.UiConstructor; 032import com.google.gwt.user.client.Timer; 033import com.google.gwt.user.client.Window; 034import com.google.gwt.user.client.ui.RootPanel; 035import com.google.gwt.user.client.ui.Widget; 036import com.google.web.bindery.event.shared.HandlerRegistration; 037import gwt.material.design.client.base.HasSelectables; 038import gwt.material.design.client.base.HasType; 039import gwt.material.design.client.base.HasWaves; 040import gwt.material.design.client.base.MaterialWidget; 041import gwt.material.design.client.base.helper.DOMHelper; 042import gwt.material.design.client.base.mixin.CssTypeMixin; 043import gwt.material.design.client.base.mixin.ToggleStyleMixin; 044import gwt.material.design.client.constants.Edge; 045import gwt.material.design.client.constants.SideNavType; 046import gwt.material.design.client.events.*; 047import gwt.material.design.client.events.SideNavClosedEvent.SideNavClosedHandler; 048import gwt.material.design.client.events.SideNavClosingEvent.SideNavClosingHandler; 049import gwt.material.design.client.events.SideNavOpenedEvent.SideNavOpenedHandler; 050import gwt.material.design.client.events.SideNavOpeningEvent.SideNavOpeningHandler; 051import gwt.material.design.client.ui.html.ListItem; 052 053//@formatter:off 054 055/** 056 * SideNav is a material component that gives you a lists of menus and other navigation components. 057 * 058 * <h3>UiBinder Usage:</h3> 059 * <pre> 060 * {@code 061 * <m:MaterialSideNav ui:field="sideNav" width="280" m:id="mysidebar" type="OPEN" closeOnClick="false"> 062 * <m:MaterialLink href="#about" iconPosition="LEFT" iconType="OUTLINE" text="About" textColor="blue" /> 063 * <m:MaterialLink href="#gettingStarted" iconPosition="LEFT" iconType="DOWNLOAD" text="Getting Started" textColor="blue" > 064 * </m:MaterialSideNav> 065 * } 066 * </pre> 067 * 068 * @author kevzlou7979 069 * @author Ben Dol 070 * @see <a href="http://gwt-material-demo.herokuapp.com/#sidenav">Material SideNav</a> 071 */ 072//@formatter:on 073public class MaterialSideNav extends MaterialWidget implements HasType<SideNavType>, HasSelectables { 074 075 private int width = 240; 076 private Edge edge = Edge.LEFT; 077 private boolean closeOnClick = false; 078 private boolean alwaysShowActivator = false; 079 private boolean allowBodyScroll = false; 080 private boolean showOnAttach = false; 081 private boolean open; 082 083 private Element activator; 084 085 private final CssTypeMixin<SideNavType, MaterialSideNav> typeMixin = new CssTypeMixin<>(this); 086 private final ToggleStyleMixin<MaterialSideNav> fixedMixin = new ToggleStyleMixin<>(this, "fixed"); 087 088 /** 089 * Container for App Toolbar and App Sidebar , contains Material Links, 090 * Icons or any other material components. 091 */ 092 public MaterialSideNav() { 093 super(Document.get().createULElement(), "side-nav"); 094 } 095 096 /** 097 * Creates a list and adds the given widgets. 098 */ 099 public MaterialSideNav(final Widget... widgets) { 100 this(); 101 for (final Widget w : widgets) { 102 add(w); 103 } 104 } 105 106 @UiConstructor 107 public MaterialSideNav(SideNavType type) { 108 this(); 109 setType(type); 110 } 111 112 @Override 113 public void onLoad() { 114 super.onLoad(); 115 116 // Initialize the side nav 117 initialize(); 118 119 if(showOnAttach) { 120 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 121 @Override 122 public void execute() { 123 if(Window.getClientWidth() > 960) { 124 show(); 125 } 126 } 127 }); 128 } 129 } 130 131 /** 132 * This handler will be triggered when the side nav starts opening. 133 */ 134 public HandlerRegistration addOpeningHandler(SideNavOpeningHandler handler) { 135 return addHandler(handler, SideNavOpeningEvent.TYPE); 136 } 137 138 /** 139 * This handler will be triggered when the side nav is opened. 140 */ 141 public HandlerRegistration addOpenedHandler(SideNavOpenedHandler handler) { 142 return addHandler(handler, SideNavOpenedEvent.TYPE); 143 } 144 145 /** 146 * This handler will be triggered when the side nav starts closing. 147 */ 148 public HandlerRegistration addClosingHandler(SideNavClosingHandler handler) { 149 return addHandler(handler, SideNavClosingEvent.TYPE); 150 } 151 152 /** 153 * This handler will be triggered when the side nav is closed. 154 */ 155 public HandlerRegistration addClosedHandler(SideNavClosedHandler handler) { 156 return addHandler(handler, SideNavClosedEvent.TYPE); 157 } 158 159 public Widget wrap(Widget child) { 160 if(child instanceof MaterialImage) { 161 child.getElement().getStyle().setProperty("border", "1px solid #e9e9e9"); 162 child.getElement().getStyle().setProperty("textAlign", "center"); 163 } 164 165 // Check whether the widget is not selectable by default 166 boolean isNotSelectable = false; 167 if(child instanceof MaterialWidget) { 168 MaterialWidget widget = (MaterialWidget) child; 169 if (widget.getInitialClasses() != null) { 170 if (widget.getInitialClasses().length > 0) { 171 String initialClass = widget.getInitialClasses()[0]; 172 if(initialClass.contains("side-profile") || initialClass.contains("collapsible")) { 173 isNotSelectable = true; 174 } 175 } 176 } 177 } 178 179 if(!(child instanceof ListItem)) { 180 // Direct list item not collapsible 181 final ListItem listItem = new ListItem(); 182 if(child instanceof MaterialCollapsible) { 183 listItem.getElement().getStyle().setBackgroundColor("transparent"); 184 } 185 if(child instanceof HasWaves) { 186 listItem.setWaves(((HasWaves) child).getWaves()); 187 ((HasWaves) child).setWaves(null); 188 } 189 listItem.add(child); 190 191 child = listItem; 192 } 193 194 // Collapsible and Side Porfile should not be selectable 195 final Widget finalChild = child; 196 if(!isNotSelectable) { 197 // Active click handler 198 finalChild.addDomHandler(new ClickHandler() { 199 @Override 200 public void onClick(ClickEvent event) { 201 clearActive(); 202 finalChild.addStyleName("active"); 203 } 204 }, ClickEvent.getType()); 205 } 206 child.getElement().getStyle().setDisplay(Style.Display.BLOCK); 207 return child; 208 } 209 210 @Override 211 public void add(Widget child) { 212 super.add(wrap(child)); 213 } 214 215 @Override 216 protected void insert(Widget child, com.google.gwt.user.client.Element container, int beforeIndex, boolean domInsert) { 217 super.insert(wrap(child), container, beforeIndex, domInsert); 218 } 219 220 @Override 221 public void setWidth(String width) { 222 setWidth(Integer.parseInt(width)); 223 } 224 225 /** 226 * Set the menu's width in pixels. 227 */ 228 public void setWidth(int width) { 229 this.width = width; 230 getElement().getStyle().setWidth(width, Unit.PX); 231 } 232 233 public int getWidth() { 234 return width; 235 } 236 237 public boolean isCloseOnClick() { 238 return closeOnClick; 239 } 240 241 /** 242 * Close the side nav menu when an \<a\> tag is clicked 243 * from inside it. Note that if you want this to work you 244 * must wrap your item within a {@link MaterialLink}. 245 */ 246 public void setCloseOnClick(boolean closeOnClick) { 247 this.closeOnClick = closeOnClick; 248 } 249 250 public Edge getEdge() { 251 return edge; 252 } 253 254 /** 255 * Set which edge of the window the menu should attach to. 256 */ 257 public void setEdge(Edge edge) { 258 this.edge = edge; 259 } 260 261 public boolean isFixed() { 262 return fixedMixin.isOn(); 263 } 264 265 /** 266 * Fixed determines its display state on loading 267 * (fixed being visible on load). 268 */ 269 public void setFixed(boolean fixed) { 270 fixedMixin.setOn(fixed); 271 } 272 273 /** 274 * Define the menu's type specification. 275 */ 276 public void setType(SideNavType type) { 277 typeMixin.setType(type); 278 } 279 280 @Override 281 public SideNavType getType() { 282 return typeMixin.getType(); 283 } 284 285 protected void processType(SideNavType type) { 286 if(activator != null && type != null) { 287 addStyleName(type.getCssName()); 288 switch (type) { 289 case MINI: 290 setWidth(64); 291 break; 292 case CARD: 293 new Timer() { 294 @Override 295 public void run() { 296 if(isSmall()) { show(); } 297 }}.schedule(500); 298 break; 299 case PUSH: 300 applyPushType(getElement(), activator, width); 301 break; 302 } 303 } 304 } 305 306 protected native boolean isSmall() /*-{ 307 var mq = $wnd.window.matchMedia('all and (max-width: 992px)'); 308 if(!mq.matches) { 309 return true; 310 } 311 return false; 312 }-*/; 313 314 /** 315 * Push the header, footer, and main to the right part when Close type is applied. 316 */ 317 protected native void applyPushType(Element element, Element activator, double width) /*-{ 318 var that = this; 319 320 $wnd.jQuery($wnd.window).off("resize"); 321 $wnd.jQuery($wnd.window).resize(function() { 322 var toggle = that.@gwt.material.design.client.ui.MaterialSideNav::open; 323 that.@gwt.material.design.client.ui.MaterialSideNav::pushElements(*)(toggle, width); 324 }); 325 }-*/; 326 327 protected native void pushElements(boolean toggle, int width) /*-{ 328 var _width = 0; 329 var _duration = 200; 330 331 var mq = $wnd.window.matchMedia('all and (max-width: 992px)'); 332 if(!mq.matches) { 333 if(toggle) { 334 _width = width; 335 _duration = 300; 336 } 337 338 applyTransition($wnd.jQuery('header'), _width); 339 applyTransition($wnd.jQuery('main'), _width); 340 applyTransition($wnd.jQuery('footer'), _width); 341 342 function applyTransition(elem, _width) { 343 $wnd.jQuery(elem).css('transition', _duration + 'ms'); 344 $wnd.jQuery(elem).css('-moz-transition', _duration + 'ms'); 345 $wnd.jQuery(elem).css('-webkit-transition', _duration + 'ms'); 346 $wnd.jQuery(elem).css('margin-left', _width); 347 } 348 } 349 this.@gwt.material.design.client.ui.MaterialSideNav::onPush(*)(toggle, _width, _duration); 350 }-*/; 351 352 protected void onPush(boolean toggle, int width, int duration) { 353 SideNavPushEvent.fire(this, getElement(), activator, toggle, width, duration); 354 } 355 356 @Override 357 public void clearActive() { 358 clearActiveClass(this); 359 ClearActiveEvent.fire(this); 360 } 361 362 /** 363 * reinitialize the side nav configurations when changing 364 * properties. 365 */ 366 public void reinitialize() { 367 activator = null; 368 initialize(false); 369 } 370 371 protected void initialize() { 372 initialize(true); 373 } 374 375 protected void initialize(boolean strict) { 376 if(activator == null) { 377 activator = DOMHelper.getElementByAttribute("data-activates", getId()); 378 if (activator != null) { 379 SideNavType type = getType(); 380 processType(type); 381 382 initialize(activator, width, closeOnClick, edge.getCssName()); 383 384 if(alwaysShowActivator || !isFixed()) { 385 String style = activator.getAttribute("style"); 386 activator.setAttribute("style", style + "; display: block !important"); 387 activator.removeClassName("navmenu-permanent"); 388 } 389 } else if(strict) { 390 throw new RuntimeException("Cannot find an activator for the MaterialSideNav, " + 391 "please ensure you have a MaterialNavBar with an activator setup to match " + 392 "this widgets id."); 393 } 394 } 395 } 396 397 protected native void initialize(Element e, int width, boolean closeOnClick, String edge)/*-{ 398 var that = this; 399 var $e = $wnd.jQuery(e); 400 $wnd.jQuery(e).sideNav({ 401 menuWidth: width, 402 edge: edge, 403 closeOnClick: closeOnClick 404 }); 405 406 $e.off("side-nav-closing"); 407 $e.on("side-nav-closing", function() { 408 that.@gwt.material.design.client.ui.MaterialSideNav::onClosing()(); 409 }); 410 411 $e.off("side-nav-closed"); 412 $e.on("side-nav-closed", function() { 413 that.@gwt.material.design.client.ui.MaterialSideNav::onClosed()(); 414 }); 415 416 $e.off("side-nav-opening"); 417 $e.on("side-nav-opening", function() { 418 that.@gwt.material.design.client.ui.MaterialSideNav::onOpening()(); 419 }); 420 421 $e.off("side-nav-opened"); 422 $e.on("side-nav-opened", function() { 423 that.@gwt.material.design.client.ui.MaterialSideNav::onOpened()(); 424 }); 425 }-*/; 426 427 protected void onClosing() { 428 open = false; 429 if(getType().equals(SideNavType.PUSH)) { 430 pushElements(false, width); 431 } 432 433 SideNavClosingEvent.fire(this); 434 } 435 436 protected void onClosed() { 437 SideNavClosedEvent.fire(this); 438 } 439 440 protected void onOpening() { 441 open = true; 442 if(getType().equals(SideNavType.PUSH)) { 443 pushElements(true, width); 444 } 445 446 SideNavOpeningEvent.fire(this); 447 } 448 449 protected void onOpened() { 450 if(allowBodyScroll) { 451 RootPanel.getBodyElement().getStyle().clearOverflow(); 452 } 453 454 SideNavOpenedEvent.fire(this); 455 } 456 457 /** 458 * Hide the overlay menu. 459 */ 460 public native void hideOverlay()/*-{ 461 $wnd.jQuery(document).ready(function() { 462 $wnd.jQuery('#sidenav-overlay').remove(); 463 }) 464 }-*/; 465 466 /** 467 * Show the sidenav. 468 */ 469 protected native void show(Element e)/*-{ 470 $wnd.jQuery(document).ready(function() { 471 $wnd.jQuery(e).sideNav('show'); 472 }); 473 }-*/; 474 475 /** 476 * Hide the sidenav. 477 */ 478 protected native void hide(Element e)/*-{ 479 $wnd.jQuery(document).ready(function() { 480 $wnd.jQuery(e).sideNav('hide'); 481 }); 482 }-*/; 483 484 /** 485 * Show the sidenav using the activator element 486 */ 487 public void show() { 488 show(activator); 489 } 490 491 /** 492 * Hide the sidenav using the activator element 493 */ 494 public void hide() { 495 hide(activator); 496 } 497 498 public boolean isOpen() { 499 return open; 500 } 501 502 /** 503 * Will the body have scroll capability 504 * while the menu is open. 505 */ 506 public boolean isAllowBodyScroll() { 507 return allowBodyScroll; 508 } 509 510 /** 511 * Allow the body to maintain its scroll capability 512 * while the menu is visible. 513 */ 514 public void setAllowBodyScroll(boolean allowBodyScroll) { 515 this.allowBodyScroll = allowBodyScroll; 516 } 517 518 /** 519 * Will the activator always be shown. 520 */ 521 public boolean isAlwaysShowActivator() { 522 return alwaysShowActivator; 523 } 524 525 /** 526 * Disable the hiding of your activator element. 527 */ 528 public void setAlwaysShowActivator(boolean alwaysShowActivator) { 529 this.alwaysShowActivator = alwaysShowActivator; 530 } 531 532 /** 533 * Will the menu forcefully show on attachment. 534 */ 535 public boolean isShowOnAttach() { 536 return showOnAttach; 537 } 538 539 /** 540 * Show the menu upon attachment, this isn't always required. 541 * Some menu types will automatically show themselves by default. 542 */ 543 public void setShowOnAttach(boolean showOnAttach) { 544 this.showOnAttach = showOnAttach; 545 } 546}