Java tutorial
package com.cgxlib.core.component.tooltip; /* * #%L * CGXlib * %% * Copyright (C) 2016 CGXlib (http://www.cgxlib.com) * %% * 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% */ import com.cgxlib.core.*; import com.cgxlib.xq.client.Function; import com.cgxlib.xq.client.XQ; import com.cgxlib.xq.client.js.JsUtils; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.regexp.shared.SplitResult; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Timer; import java.util.ArrayList; import java.util.List; /** * */ public class SingleTooltip extends TooltipBase { public static final Class<SingleTooltip> CGX = CGXlib.registerPlugin(SingleTooltip.class, new CGXPlugin<SingleTooltip, TooltipView.ViewHandler>() { @Override public List<HTML5DataAPILoader> autoLoaders() { HTML5DataAPILoader selector = GWT.create(SingleTooltipHTML5Loader.class); List<HTML5DataAPILoader> selectors = new ArrayList<HTML5DataAPILoader>(); selectors.add(selector); return selectors; } @Override public SingleTooltip init(CGXComponentBase xq, ViewHandlerFactory<? extends TooltipView.ViewHandler> factory) { return new SingleTooltip(xq, factory.make()); } @Override public SingleTooltip init(XQ xq) { return new SingleTooltip(xq); } }); protected XQ $arrow; protected boolean enabled; protected String type; protected TooltipOptions options; protected XQ $viewport; protected InState inState; protected XQ $tip; protected HoverState hoverState; protected TooltipView.ViewHandler viewHandler; protected Timer timeout; protected SingleTooltip(XQ xq) { this(xq, new TooltipBSViewHandler()); } protected SingleTooltip(XQ xq, TooltipView.ViewHandler viewHandler) { super(xq, viewHandler); } protected static XQ enter(final SingleTooltip self, boolean isEvent) { if (isEvent) { if ("focusin".equals(self.type)) { self.inState.focus = true; } else { self.inState.hover = true; } } if (self.tip().hasClass("in") || self.hoverState == HoverState.IN) { self.hoverState = HoverState.IN; return self; } if (self.timeout != null) { self.timeout.cancel(); } self.hoverState = HoverState.IN; boolean willShow = self.options.delay() < 1 || self.options.showDelay() < 1; if (willShow) { return self.doShow(); } self.timeout = new Timer() { @Override public void run() { if (self.hoverState == HoverState.IN) { self.doShow(); } } }; self.timeout.schedule(self.options.showDelay()); return self; } protected static SingleTooltip leave(final SingleTooltip self, boolean isEvent) { if (isEvent) { if ("focusin".equals(self.type)) { self.inState.focus = false; } else { self.inState.hover = false; } } if (self.inState.isAnyStateTrue()) { return self; } if (self.timeout != null) { self.timeout.cancel(); } self.hoverState = HoverState.OUT; if (self.options.delay() < 1 || self.options.hideDelay() < 1) { self.tooltipHide(null); } else { self.timeout = new Timer() { @Override public void run() { if (self.hoverState == HoverState.OUT) { self.tooltipHide(null); } } }; self.timeout.schedule(self.options.hideDelay()); } return self; } @Override public SingleTooltip tooltip() { return tooltip(null); } @Override public SingleTooltip tooltip(TooltipOptions options) { initTooltip(type(), getOptions(options)); return this; } public SingleTooltip tooltipHide() { leave(this, false); return this; } public SingleTooltip tooltipShow() { enter(this, false); return this; } public SingleTooltip tooltipToggle() { tooltipToggle(null, this); return this; } public String tooltipTitle() { String title; TooltipOptions o = this.options; title = viewHandler.getTitle(); if ((title == null || title.length() < 1) && o != null) { title = o.title(); } return title; } protected SingleTooltip tooltipToggle(Event e, SingleTooltip tooltip) { SingleTooltip self = tooltip; if (e != null) { XQ $target = $(e.getCurrentEventTarget()); self = $target.data("cgx." + this.type); if (self == null) { self = $target.as(CGX); $target.data("cgx." + this.type, self); } } if (e != null) { self.inState.click = !self.inState.click; if (self.inState.isAnyStateTrue()) { self.enter(self, false); } else { self.leave(self, false); } } else if (viewHandler.isTipIn(self.tip())) { self.leave(self, false); } else { self.enter(self, false); } return this; } @Override protected void apply(XQ xq, TooltipView.ViewHandler viewHandler) { viewHandler.init(this); this.viewHandler = viewHandler; } protected String type() { return "tooltip"; } protected void initTooltip(String type, TooltipOptions pOptions) { this.enabled = true; this.type = type; final SingleTooltip $this = this; options = pOptions; this.$viewport = options.viewPortSelector() != null ? $(options.viewPortSelector()) : null; this.inState = new InState(); if (Document.get().getDocumentElement().equals($this.get(0)) && this.options.selector() == null) { throw new Error("`selector` option must be specified when initializing " + this.type + " on the window.document object!"); } SplitResult triggers = RegExp.compile(" ").split(options.trigger()); for (int i = triggers.length() - 1; i >= 0; i--) { String trigger = triggers.get(i); if ("click".equals(trigger)) { $this.on("click." + this.type, this.options.selector(), new Function() { @Override public void f() { $this.tooltipToggle(null, $this); } }); } else if (trigger != "manual") { String eventIn = trigger == "hover" ? "mouseenter" : "focusin"; String eventOut = trigger == "hover" ? "mouseleave" : "focusout"; $this.on(eventIn + '.' + this.type, options.selector(), new Function() { @Override public void f() { $this.enter($this, true); } }); $this.on(eventOut + '.' + this.type, options.selector(), new Function() { @Override public void f() { $this.leave($this, true); } }); } } if (options.selector() != null) { options.trigger("manual"); options.selector(null); } else { viewHandler.fixTitle(); } } public SingleTooltip tooltipEnable() { enabled = true; return this; } public SingleTooltip tooltipDisable() { enabled = false; return this; } public SingleTooltip tooltipToggleEnabled() { this.enabled = !this.enabled; return this; } protected SingleTooltip doShow() { NativeEvent e = CGXHelper.createNativeEvent("show", "cgx." + type, null); if (this.hasContent() && this.enabled) { this.trigger(e); boolean inDom = viewHandler.inDom(); if (JsUtils.isDefaultPrevented(e) || !inDom) { return this; } final SingleTooltip that = this; XQ $tip = tip(); String tipId = getUID(type); setContent(); viewHandler.assignId($tip, tipId); if (this.options.animation()) { viewHandler.animateTip($tip); } Placement placement = this.options.placement(); String placementStr = placement != null ? placement.name() : null; String autoToken = "/\\s?auto?\\s?/i"; RegExp regExp = RegExp.compile(autoToken); boolean autoPlace = placementStr != null && regExp.test(placementStr); if (autoPlace) { placementStr = regExp.replace(autoToken, ""); if (placementStr == null || placementStr.length() < 1) { placement = Placement.top; } } viewHandler.setupTip($tip, placement); $tip.data("cgx." + this.type, this); if (this.options.container() != null) { $tip.appendTo(this.options.container()); } else { $tip.insertAfter(this); } this.trigger("inserted.cgx." + this.type); Position pos = viewHandler.getPosition(this); int actualWidth = viewHandler.tipWidth($tip); int actualHeight = viewHandler.tipHeight($tip); if (autoPlace) { Placement orgPlacement = placement; Position viewportDim = viewHandler.getPosition($viewport); if (placement == Placement.bottom && pos.bottom + actualHeight > viewportDim.bottom) { placement = Placement.top; } else if (placement == Placement.top && pos.top - actualHeight < viewportDim.top) { placement = Placement.bottom; } else if (placement == Placement.right && pos.right + actualWidth > viewportDim.width) { placement = Placement.left; } else if (placement == Placement.left && pos.left - actualWidth < viewportDim.left) { placement = Placement.right; } viewHandler.switchPlacement($tip, orgPlacement, placement); } Offset calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight); applyPlacement(calculatedOffset, placement); Function complete = new Function() { @Override public void f() { HoverState prevHoverState = that.hoverState; that.trigger("shown.cgx." + that.type); that.hoverState = null; if (prevHoverState == HoverState.OUT) { that.leave(that, false); } } }; final boolean useTransitions = transitions() && viewHandler.useTransitions($tip); if (useTransitions) { $tip.as(CGXlib.CGX).onceWhenTransitionEnds(complete).emulateTransitionEnd(transitionDuration()); } else { complete.f(); } } return this; } protected void setContent() { viewHandler.setContent($tip, tooltipTitle(), options.html()); } protected void applyPlacement(Offset offset, Placement placement) { XQ $tip = this.tip(); int width = $tip.get(0).getOffsetWidth(); int height = $tip.get(0).getOffsetHeight(); int marginTop = viewHandler.tipMarginTop($tip); int marginLeft = viewHandler.tipMarginLeft($tip); offset.top += marginTop; offset.left += marginLeft; viewHandler.tipIn($tip); int actualWidth = $tip.get(0).getOffsetWidth(); int actualHeight = $tip.get(0).getOffsetHeight(); if (placement == Placement.top && actualHeight != height) { offset.top = offset.top + height - actualHeight; } Offset delta = getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight); if (delta.left > 0) { offset.left += delta.left; } else { offset.top += delta.top; } boolean isVertical = placement == Placement.top || placement == Placement.bottom; int arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight; $tip.offset(offset); Element tip = $tip.get(0); int offsetAmount = isVertical ? tip.getOffsetWidth() : tip.getOffsetHeight(); replaceArrow(arrowDelta, offsetAmount, isVertical); } protected void replaceArrow(int delta, int dimension, boolean isVertical) { viewHandler.replaceArrow(arrow(), delta, dimension, isVertical); } protected XQ arrow() { if ($arrow == null) { $arrow = viewHandler.arrow(tip()); } return $arrow; } protected Offset getViewportAdjustedDelta(Placement placement, Offset pos, int actualWidth, int actualHeight) { Offset delta = new Offset(0, 0); if (this.$viewport == null) { return delta; } int viewportPadding = options.viewPortPadding(); Position viewportDimensions = viewHandler.getPosition($viewport); if (placement == Placement.right || placement == Placement.left) { int topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll; int bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight; if (topEdgeOffset < viewportDimensions.top) { // top overflow delta.top = viewportDimensions.top - topEdgeOffset; } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset; } } else { int leftEdgeOffset = pos.left - viewportPadding; int rightEdgeOffset = pos.left + viewportPadding + actualWidth; if (leftEdgeOffset < viewportDimensions.left) { // left overflow delta.left = viewportDimensions.left - leftEdgeOffset; } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset; } } return delta; } protected Offset getCalculatedOffset(Placement placement, Position pos, int actualWidth, int actualHeight) { Offset offset = null; if (placement == Placement.bottom) { offset = new Offset(pos.left + pos.width / 2 - actualWidth / 2, pos.top + pos.height); } else if (placement == Placement.top) { offset = new Offset(pos.left + pos.width / 2 - actualWidth / 2, pos.top - actualHeight); } else if (placement == Placement.left) { offset = new Offset(pos.left - actualWidth, pos.top + pos.height / 2 - actualHeight / 2); } else { //right offset = new Offset(pos.left + pos.width, pos.top + pos.height / 2 - actualHeight / 2); } return offset; } public SingleTooltip tooltipDestroy() { final SingleTooltip that = this; if (this.timeout != null) { this.timeout.cancel(); } Function callback = new Function() { @Override public void f() { that.off('.' + that.type).removeData("cgx." + that.type); if (that.$tip != null) { that.$tip.detach(); } that.$tip = null; that.$arrow = null; that.$viewport = null; } }; tooltipHide(callback); return this; } protected SingleTooltip tooltipHide(final Function callback) { if ($tip == null) { return this; } final SingleTooltip that = this; NativeEvent e = CGXHelper.createNativeEvent("hide", "cgx." + type, null); Function complete = new Function() { @Override public void f() { viewHandler.hideTip($tip, that.hoverState); that.trigger("hidden.cgx." + that.type); if (callback != null) { callback.f(); } } }; this.trigger(e); if (JsUtils.isDefaultPrevented(e)) { return this; } viewHandler.preHideTip($tip); final boolean useTransitions = transitions() && viewHandler.useTransitions($tip); if (useTransitions) { $tip.as(CGXlib.CGX).onceWhenTransitionEnds(complete).emulateTransitionEnd(transitionDuration()); } else { complete.f(); } this.hoverState = null; return this; } protected String getUID(String prefix) { String UID = viewHandler.getUID(prefix); return UID; } protected boolean hasContent() { String title = tooltipTitle(); return title != null && title.length() > 0; } protected XQ tip() { if ($tip == null) { $tip = viewHandler.createTip(options.template()); if ($tip.length() != 1) { throw new Error(this.type + " `template` option must consist of exactly 1 top-level element!"); } } return $tip; } protected TooltipOptions getOptions(TooltipOptions pOptions) { TooltipOptions options = createOptions(); if (pOptions != null) { options.animation(pOptions.animation()); options.placement(pOptions.placement()); options.selector(pOptions.selector()); options.template(pOptions.template()); options.trigger(pOptions.trigger()); options.title(pOptions.title()); options.delay(pOptions.delay()); options.html(pOptions.html()); options.container(pOptions.container()); options.viewPortSelector(pOptions.viewPortSelector()); options.viewPortPadding(pOptions.viewPortPadding()); options.delay(pOptions.delay()); options.content(pOptions.content()); } options.loadData(this); return options; } protected TooltipOptions createOptions() { return new TooltipOptions(); } public enum HoverState { IN, OUT; } public static class Position { public int scroll; public int top; public int left; public int right; public int width; public int height; public int bottom; @Override public String toString() { JavaScriptObject obj = JavaScriptObject.createObject(); JsUtils.prop(obj, "scroll", scroll); JsUtils.prop(obj, "top", top); JsUtils.prop(obj, "left", left); JsUtils.prop(obj, "right", right); JsUtils.prop(obj, "width", width); JsUtils.prop(obj, "height", height); JsUtils.prop(obj, "bottom", bottom); return JsUtils.JSON2String(obj); } } protected class InState { boolean click = false; boolean hover = false; boolean focus = false; boolean isAnyStateTrue() { return click || hover || focus; } } }