gwt.material.design.client.base.AbstractSideNav Maven / Gradle / Ivy
/*
* #%L
* GwtMaterial
* %%
* Copyright (C) 2015 - 2017 GwtMaterialDesign
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package gwt.material.design.client.base;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import gwt.material.design.client.accessibility.AccessibilityControl;
import gwt.material.design.client.base.density.Density;
import gwt.material.design.client.base.helper.DOMHelper;
import gwt.material.design.client.base.mixin.DensityMixin;
import gwt.material.design.client.base.mixin.OverlayStyleMixin;
import gwt.material.design.client.base.mixin.StyleMixin;
import gwt.material.design.client.base.viewport.ViewPort;
import gwt.material.design.client.base.viewport.WidthBoundary;
import gwt.material.design.client.constants.*;
import gwt.material.design.client.events.*;
import gwt.material.design.client.js.JsMaterialElement;
import gwt.material.design.client.js.JsSideNavOptions;
import gwt.material.design.client.ui.*;
import gwt.material.design.client.ui.html.ListItem;
import gwt.material.design.jquery.client.api.JQueryElement;
import static gwt.material.design.client.js.JsMaterialElement.$;
//@formatter:off
/**
* AbstractSideNav handles the creation and Ui logic for
* different sidenavs, you can easily setup any kind of logic for
* you sidenav behaviour in {@link AbstractSideNav#setup()}.
*
* @author kevzlou7979
*/
//@formatter:on
public abstract class AbstractSideNav extends MaterialWidget
implements JsLoader, HasSelectables, HasInOutDurationTransition, HasSideNavHandlers, HasOverlayStyle, HasOpenClose, HasDensity {
protected int width = 240;
protected int inDuration = 400;
protected int outDuration = 200;
protected boolean open;
protected boolean closeOnClick;
protected boolean alwaysShowActivator = true;
protected boolean allowBodyScroll = true;
protected Edge edge = Edge.LEFT;
protected Boolean showOnAttach;
protected Element activator;
protected WidthBoundary closingBoundary;
protected ViewPort autoHideViewport;
protected OverlayOption overlayOption = OverlayOption.create();
private StyleMixin typeMixin;
private OverlayStyleMixin overlayStyleMixin;
private DensityMixin densityMixin;
public AbstractSideNav() {
super(Document.get().createULElement(), CssName.SIDE_NAV);
}
public AbstractSideNav(final Widget... widgets) {
this();
for (final Widget w : widgets) {
add(w);
}
}
public AbstractSideNav(SideNavType type) {
this();
setType(type);
}
@Override
protected void onLoad() {
super.onLoad();
load();
setupShowOnAttach();
}
protected void setupDefaultOverlayStyle() {
overlayOption.setVisibility(Style.Visibility.HIDDEN);
setOverlayOption(overlayOption);
}
protected void setupShowOnAttach() {
if (showOnAttach != null) {
// Ensure the side nav starts closed
$(activator).trigger("menu-in", null);
if (showOnAttach) {
Scheduler.get().scheduleDeferred(() -> {
// We are ignoring cases with mobile
if (Window.getClientWidth() > 960) {
open();
}
});
}
} else {
if (Window.getClientWidth() > 960) {
$(activator).trigger("menu-out", null);
}
}
}
@Override
protected void onUnload() {
super.onUnload();
unload();
}
public Widget wrap(Widget child) {
if (child instanceof MaterialImage) {
child.getElement().getStyle().setProperty("border", "1px solid #e9e9e9");
child.getElement().getStyle().setProperty("textAlign", "center");
}
// Check whether the widget is not selectable by default
boolean isNotSelectable = false;
if (child instanceof MaterialWidget) {
MaterialWidget widget = (MaterialWidget) child;
if (widget.getInitialClasses() != null) {
if (widget.getInitialClasses().length > 0) {
if (child instanceof HasNoSideNavSelection) {
isNotSelectable = true;
}
}
}
}
if (!(child instanceof ListItem)) {
// Direct list item not collapsible
final ListItem listItem = new ListItem();
if (child instanceof MaterialCollapsible) {
listItem.getElement().getStyle().setBackgroundColor("transparent");
}
if (child instanceof HasWaves) {
listItem.setWaves(((HasWaves) child).getWaves());
((HasWaves) child).setWaves(null);
}
if (child instanceof HasNoSideNavSelection) {
super.add(child);
} else {
listItem.add(child);
child = listItem;
}
}
// Collapsible and Side Porfile should not be selectable
final Widget finalChild = child;
if (!isNotSelectable) {
// Active click handler
registerHandler(finalChild.addDomHandler(event -> {
clearActive();
finalChild.addStyleName(CssName.ACTIVE);
}, ClickEvent.getType()));
}
child.getElement().getStyle().setDisplay(Style.Display.BLOCK);
return child;
}
@Override
public void add(Widget child) {
super.add(wrap(child));
}
@Override
protected void insert(Widget child, com.google.gwt.user.client.Element container, int beforeIndex, boolean domInsert) {
super.insert(wrap(child), container, beforeIndex, domInsert);
}
protected void pushElement(Element element, int value) {
applyTransition($(element).asElement());
if (getEdge() == Edge.RIGHT) {
$(element).css("paddingRight", value + "px");
} else {
$(element).css("paddingLeft", value + "px");
}
}
protected void pushElementMargin(Element element, int value) {
applyTransition($(element).asElement());
if (getEdge() == Edge.LEFT) {
$(element).css("margin-left", value + "px");
} else {
$(element).css("margin-right", value + "px");
}
}
protected void applyBodyScroll() {
if (isAllowBodyScroll()) {
$("header").css("width", "100%");
$("header").css("position", "fixed");
$("header").css("zIndex", "997");
$(getElement()).css("position", "fixed");
}
}
protected void applyTransition(Element element) {
applyTransition(element, "all");
}
protected void applyTransition(Element element, String property) {
int duration;
if (isOpen()) {
duration = inDuration;
} else {
duration = outDuration;
}
if (element != null) {
setTransition(new TransitionConfig(element, duration, 0, property, "cubic-bezier(0, 0, 0.2, 1)"));
}
}
@Override
public void clearActive() {
clearActiveClass(this);
ClearActiveEvent.fire(this);
}
public void setActive(int index) {
clearActive();
getWidget(index).addStyleName(CssName.ACTIVE);
}
@Override
public void load() {
load(true);
}
@Override
public void unload() {
activator = null;
$(".drag-target").remove();
}
/**
* Reinitialize the side nav configurations when changing properties.
*/
@Override
public void reload() {
unload();
load(false);
}
protected void load(boolean strict) {
try {
activator = DOMHelper.getElementByAttribute("data-activates", getId());
MaterialWidget navMenu = getNavMenu();
if (navMenu != null) {
navMenu.setShowOn(ShowOn.SHOW_ON_MED_DOWN);
if (alwaysShowActivator && !getTypeMixin().getStyle().equals(SideNavType.FIXED.getCssName())) {
navMenu.setShowOn(ShowOn.SHOW_ON_LARGE);
} else {
navMenu.setHideOn(HideOn.HIDE_ON_LARGE);
}
navMenu.removeStyleName(CssName.NAVMENU_PERMANENT);
// Register Nav Menu Accessibility
AccessibilityControl.get().registerWidget(navMenu);
}
} catch (Exception ex) {
if (strict) {
throw new IllegalArgumentException(
"Could not setup MaterialSideNav please ensure you have " +
"MaterialNavBar with an activator setup to match this widgets id.", ex);
}
}
setup();
setupDefaultOverlayStyle();
JsSideNavOptions options = new JsSideNavOptions();
options.menuWidth = width;
options.edge = edge != null ? edge.getCssName() : null;
options.closeOnClick = closeOnClick;
JsMaterialElement element = $(activator);
element.sideNav(options);
element.off(SideNavEvents.SIDE_NAV_CLOSING);
element.on(SideNavEvents.SIDE_NAV_CLOSING, e1 -> {
onClosing();
return true;
});
element.off(SideNavEvents.SIDE_NAV_CLOSED);
element.on(SideNavEvents.SIDE_NAV_CLOSED, e1 -> {
onClosed();
return true;
});
element.off(SideNavEvents.SIDE_NAV_OPENING);
element.on(SideNavEvents.SIDE_NAV_OPENING, e1 -> {
onOpening();
return true;
});
element.off(SideNavEvents.SIDE_NAV_OPENED);
element.on(SideNavEvents.SIDE_NAV_OPENED, e1 -> {
onOpened();
return true;
});
element.off(SideNavEvents.SIDE_NAV_OVERLAY_ATTACHED);
element.on(SideNavEvents.SIDE_NAV_OVERLAY_ATTACHED, e1 -> {
onOverlayAttached();
return true;
});
$(".collapsible-header").on("click", (e, param1) -> {
//e.stopPropagation();
return true;
});
}
/**
* Override the type of your sidenav.
* Used by {@link MaterialSideNavDrawer}, {@link MaterialSideNavCard}, {@link MaterialSideNavMini}, {@link MaterialSideNavPush}
*/
protected abstract void setup();
@Override
protected void onDetach() {
super.onDetach();
MaterialWidget navMenu = getNavMenu();
if (navMenu != null) {
navMenu.removeStyleName(ShowOn.SHOW_ON_LARGE.getCssName());
navMenu.removeStyleName(ShowOn.SHOW_ON_MED_DOWN.getCssName());
}
pushElement(getHeader(), 0);
pushElement(getMain(), 0);
pushElementMargin(getFooter(), 0);
}
protected Element getMain() {
return $("main").asElement();
}
protected Element getHeader() {
return $("header").asElement();
}
protected Element getFooter() {
return $("footer").asElement();
}
@Override
public void setWidth(String width) {
setWidth(Integer.parseInt(width));
}
/**
* Set the menu's width in pixels.
*/
public void setWidth(int width) {
this.width = width;
getElement().getStyle().setWidth(width, Style.Unit.PX);
}
public int getWidth() {
return width;
}
public boolean isCloseOnClick() {
return closeOnClick;
}
/**
* Close the side nav menu when an \ tag is clicked
* from inside it. Note that if you want this to work you
* must wrap your item within a {@link MaterialLink}.
*/
public void setCloseOnClick(boolean closeOnClick) {
this.closeOnClick = closeOnClick;
}
public Edge getEdge() {
return edge;
}
/**
* Set which edge of the window the menu should attach to.
*/
public void setEdge(Edge edge) {
this.edge = edge;
}
protected void setType(SideNavType type) {
getTypeMixin().setStyle(type.getCssName());
}
protected boolean isSmall() {
return !gwt.material.design.client.js.Window.matchMedia("all and (max-width: 992px)");
}
protected MaterialWidget getNavMenu() {
Element navMenuElement = DOMHelper.getElementByAttribute("data-activates", getId());
if (navMenuElement != null) {
return new MaterialWidget(navMenuElement);
}
return null;
}
protected void onClosing() {
open = false;
$("#sidenav-overlay").remove();
SideNavClosingEvent.fire(this);
resetOverlayStyle();
}
protected void onClosed() {
SideNavClosedEvent.fire(this);
}
protected void onOpening() {
open = true;
$("#sidenav-overlay").each((param1, element) -> {
if (element != null) {
element.removeFromParent();
}
});
SideNavOpeningEvent.fire(this);
}
protected void onOpened() {
if (allowBodyScroll) {
RootPanel.getBodyElement().getStyle().clearOverflow();
}
String overlayZIndex = $("#sidenav-overlay").css("zIndex");
$(".drag-target").css("zIndex", (overlayZIndex != null ? Integer.parseInt(overlayZIndex) : 1) + "");
SideNavOpenedEvent.fire(this);
}
protected void onOverlayAttached() {
applyOverlayStyle(getOverlayElement());
}
/**
* Hide the overlay menu.
*/
public void hideOverlay() {
$("#sidenav-overlay").remove();
}
/**
* Replaced with {@link #open()}
*/
@Deprecated
public void show() {
open();
}
/**
* Show the sidenav using the activator element
*/
@Override
public void open() {
$("#sidenav-overlay").remove();
$(activator).sideNav("show");
}
/**
* Replaced with {@link #close()}
*/
@Deprecated
public void hide() {
close();
}
/**
* Hide the sidenav using the activator element
*/
@Override
public void close() {
$(activator).sideNav("hide");
}
@Override
public boolean isOpen() {
return open;
}
/**
* Will the body have scroll capability
* while the menu is open.
*/
public boolean isAllowBodyScroll() {
return allowBodyScroll;
}
/**
* Allow the body to maintain its scroll capability
* while the menu is visible.
*/
public void setAllowBodyScroll(boolean allowBodyScroll) {
this.allowBodyScroll = allowBodyScroll;
}
/**
* Will the activator always be shown.
*/
public boolean isAlwaysShowActivator() {
return alwaysShowActivator;
}
/**
* Disable the hiding of your activator element.
*/
public void setAlwaysShowActivator(boolean alwaysShowActivator) {
this.alwaysShowActivator = alwaysShowActivator;
}
/**
* Will the menu forcefully show on attachment.
*/
public boolean isShowOnAttach() {
return showOnAttach != null && showOnAttach;
}
/**
* Show the menu upon attachment.
* Note that you shouldn't apply this setting if you want your side nav to appear static.
* otherwise when set to true
will slide in from the left.
*/
public void setShowOnAttach(boolean showOnAttach) {
this.showOnAttach = showOnAttach;
}
public boolean isAutoHideOnResize() {
return autoHideViewport != null;
}
/**
* When enabled the sidenav will auto hide / collapse it durating browser resized.
* Default true
*/
public void setAutoHideOnResize(boolean autoHideOnResize) {
if (autoHideOnResize) {
autoHideViewport = ViewPort.when(getClosingBoundary()).then(param1 -> hide());
} else {
if (autoHideViewport != null) {
autoHideViewport.destroy();
autoHideViewport = null;
}
}
}
@Override
public void setEnabled(boolean enabled) {
getEnabledMixin().setEnabled(this, enabled);
}
@Override
public void setInDuration(int inDuration) {
this.inDuration = inDuration;
}
@Override
public int getInDuration() {
return inDuration;
}
@Override
public void setOutDuration(int outDuration) {
this.outDuration = outDuration;
}
@Override
public int getOutDuration() {
return outDuration;
}
@Override
public void setOverlayOption(OverlayOption overlayOption) {
getOverlayStyleMixin().setOverlayOption(overlayOption);
}
@Override
public OverlayOption getOverlayOption() {
return getOverlayStyleMixin().getOverlayOption();
}
@Override
public void applyOverlayStyle(JQueryElement overlayElement) {
getOverlayStyleMixin().applyOverlayStyle(getOverlayElement());
}
@Override
public void resetOverlayStyle() {
getOverlayStyleMixin().resetOverlayStyle();
}
public void setClosingBoundary(WidthBoundary closingBoundary) {
this.closingBoundary = closingBoundary;
}
public WidthBoundary getClosingBoundary() {
if (closingBoundary == null) {
closingBoundary = new WidthBoundary(0, 992);
}
return closingBoundary;
}
@Override
public void setDensity(Density density) {
getDensityMixin().setDensity(density);
}
@Override
public Density getDensity() {
return getDensityMixin().getDensity();
}
public Element getActivator() {
return activator;
}
public JQueryElement getOverlayElement() {
return $("#sidenav-overlay");
}
public JQueryElement getDragTargetElement() {
return $(".drag-target");
}
@Override
public HandlerRegistration addOpeningHandler(SideNavOpeningEvent.SideNavOpeningHandler handler) {
return addHandler(handler, SideNavOpeningEvent.TYPE);
}
@Override
public HandlerRegistration addOpenedHandler(SideNavOpenedEvent.SideNavOpenedHandler handler) {
return addHandler(handler, SideNavOpenedEvent.TYPE);
}
@Override
public HandlerRegistration addClosingHandler(SideNavClosingEvent.SideNavClosingHandler handler) {
return addHandler(handler, SideNavClosingEvent.TYPE);
}
@Override
public HandlerRegistration addClosedHandler(SideNavClosedEvent.SideNavClosedHandler handler) {
return addHandler(handler, SideNavClosedEvent.TYPE);
}
protected StyleMixin getTypeMixin() {
if (typeMixin == null) {
typeMixin = new StyleMixin(this);
}
return typeMixin;
}
protected OverlayStyleMixin getOverlayStyleMixin() {
if (overlayStyleMixin == null) {
overlayStyleMixin = new OverlayStyleMixin<>(this);
}
return overlayStyleMixin;
}
protected DensityMixin getDensityMixin() {
if (densityMixin == null) {
densityMixin = new DensityMixin<>(this);
}
return densityMixin;
}
}