001    package org.gwtbootstrap3.client.ui;
002    
003    /*
004     * #%L
005     * GwtBootstrap3
006     * %%
007     * Copyright (C) 2013 GwtBootstrap3
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    
023    import java.util.Iterator;
024    import java.util.NoSuchElementException;
025    
026    import org.gwtbootstrap3.client.shared.event.HiddenEvent;
027    import org.gwtbootstrap3.client.shared.event.HiddenHandler;
028    import org.gwtbootstrap3.client.shared.event.HideEvent;
029    import org.gwtbootstrap3.client.shared.event.HideHandler;
030    import org.gwtbootstrap3.client.shared.event.ShowEvent;
031    import org.gwtbootstrap3.client.shared.event.ShowHandler;
032    import org.gwtbootstrap3.client.shared.event.ShownEvent;
033    import org.gwtbootstrap3.client.shared.event.ShownHandler;
034    import org.gwtbootstrap3.client.ui.base.HasHover;
035    import org.gwtbootstrap3.client.ui.base.HasId;
036    import org.gwtbootstrap3.client.ui.constants.Placement;
037    import org.gwtbootstrap3.client.ui.constants.Trigger;
038    
039    import com.google.gwt.dom.client.Element;
040    import com.google.gwt.event.logical.shared.AttachEvent;
041    import com.google.gwt.user.client.Event;
042    import com.google.gwt.user.client.ui.HasOneWidget;
043    import com.google.gwt.user.client.ui.HasWidgets;
044    import com.google.gwt.user.client.ui.IsWidget;
045    import com.google.gwt.user.client.ui.Widget;
046    import com.google.web.bindery.event.shared.HandlerRegistration;
047    
048    /**
049     * Basic implementation for the Bootstrap tooltip
050     * <p/>
051     * <a href="http://getbootstrap.com/javascript/#tooltips">Bootstrap Documentation</a>
052     * <p/>
053     * <p/>
054     * <h3>UiBinder example</h3>
055     * <p/>
056     * <pre>
057     * {@code
058     * <b:Tooltip text="...">
059     *    ...
060     * </b:Tooltip>
061     * }
062     * </pre>
063     * <p/>
064     * ** Must call reconfigure() after altering any/all Tooltips!
065     *
066     * @author Joshua Godi
067     * @author Pontus Enmark
068     */
069    public class Tooltip implements IsWidget, HasWidgets, HasOneWidget, HasId, HasHover {
070        private static final String TOGGLE = "toggle";
071        private static final String SHOW = "show";
072        private static final String HIDE = "hide";
073        private static final String DESTROY = "destroy";
074    
075        // Defaults from http://getbootstrap.com/javascript/#tooltips
076        private boolean isAnimated = true;
077        private boolean isHTML = false;
078        private Placement placement = Placement.TOP;
079        private Trigger trigger = Trigger.HOVER;
080        private String title = "";
081        private int hideDelayMs = 0;
082        private int showDelayMs = 0;
083        private String container = null;
084        private final String selector = null;
085    
086        private Widget widget;
087        private String id;
088    
089        /**
090         * Creates the empty Tooltip
091         */
092        public Tooltip() {
093        }
094    
095        /**
096         * Creates the tooltip around this widget
097         *
098         * @param w widget for the tooltip
099         */
100        public Tooltip(final Widget w) {
101            setWidget(w);
102        }
103    
104        /**
105         * {@inheritDoc}
106         */
107        @Override
108        public void setWidget(final Widget w) {
109            // Validate
110            if (w == widget) {
111                return;
112            }
113    
114            // Detach new child
115            if (w != null) {
116                w.removeFromParent();
117            }
118    
119            // Remove old child
120            if (widget != null) {
121                remove(widget);
122            }
123    
124            // Logical attach, but don't physical attach; done by jquery.
125            widget = w;
126            if (widget == null) {
127                return;
128            }
129    
130            // Bind jquery events
131            bindJavaScriptEvents(widget.getElement());
132    
133            // When we attach it, configure the tooltip
134            widget.addAttachHandler(new AttachEvent.Handler() {
135                @Override
136                public void onAttachOrDetach(final AttachEvent event) {
137                    reconfigure();
138                }
139            });
140        }
141    
142        /**
143         * {@inheritDoc}
144         */
145        @Override
146        public void add(final Widget child) {
147            if (getWidget() != null) {
148                throw new IllegalStateException("Can only contain one child widget");
149            }
150            setWidget(child);
151        }
152    
153        /**
154         * {@inheritDoc}
155         */
156        @Override
157        public void setWidget(final IsWidget w) {
158            widget = (w == null) ? null : w.asWidget();
159        }
160    
161        /**
162         * {@inheritDoc}
163         */
164        @Override
165        public Widget getWidget() {
166            return widget;
167        }
168    
169        /**
170         * {@inheritDoc}
171         */
172        @Override
173        public void setId(final String id) {
174            this.id = id;
175            if (widget != null) {
176                widget.getElement().setId(id);
177            }
178        }
179    
180        /**
181         * {@inheritDoc}
182         */
183        @Override
184        public String getId() {
185            return (widget == null) ? id : widget.getElement().getId();
186        }
187    
188        @Override
189        public void setIsAnimated(final boolean isAnimated) {
190            this.isAnimated = isAnimated;
191        }
192    
193        /**
194         * {@inheritDoc}
195         */
196        @Override
197        public boolean isAnimated() {
198            return isAnimated;
199        }
200    
201        /**
202         * {@inheritDoc}
203         */
204        @Override
205        public void setIsHtml(final boolean isHTML) {
206            this.isHTML = isHTML;
207        }
208    
209        /**
210         * {@inheritDoc}
211         */
212        @Override
213        public boolean isHtml() {
214            return isHTML;
215        }
216    
217        /**
218         * {@inheritDoc}
219         */
220        @Override
221        public void setPlacement(final Placement placement) {
222            this.placement = placement;
223        }
224    
225        /**
226         * {@inheritDoc}
227         */
228        @Override
229        public Placement getPlacement() {
230            return placement;
231        }
232    
233        /**
234         * {@inheritDoc}
235         */
236        @Override
237        public void setTrigger(final Trigger trigger) {
238            this.trigger = trigger;
239        }
240    
241        /**
242         * {@inheritDoc}
243         */
244        @Override
245        public Trigger getTrigger() {
246            return trigger;
247        }
248    
249        @Override
250        public void setShowDelayMs(final int showDelayMs) {
251            this.showDelayMs = showDelayMs;
252        }
253    
254        /**
255         * {@inheritDoc}
256         */
257        @Override
258        public int getShowDelayMs() {
259            return showDelayMs;
260        }
261    
262        /**
263         * {@inheritDoc}
264         */
265        @Override
266        public void setHideDelayMs(final int hideDelayMs) {
267            this.hideDelayMs = hideDelayMs;
268        }
269    
270        /**
271         * {@inheritDoc}
272         */
273        @Override
274        public int getHideDelayMs() {
275            return hideDelayMs;
276        }
277    
278        /**
279         * {@inheritDoc}
280         */
281        @Override
282        public void setContainer(final String container) {
283            this.container = container;
284        }
285    
286        /**
287         * {@inheritDoc}
288         */
289        @Override
290        public String getContainer() {
291            return container;
292        }
293    
294        /**
295         * Gets the tooltip's display string
296         *
297         * @return String tooltip display string
298         */
299        public String getTitle() {
300            return title;
301        }
302    
303        /**
304         * Sets the tooltip's display string
305         *
306         * @param title String display string
307         */
308        public void setTitle(final String title) {
309            this.title = title;
310        }
311    
312        /**
313         * Reconfigures the tooltip, must be called when altering any tooltip after it has already been shown
314         */
315        public void reconfigure() {
316            // First destroy the old tooltip
317            destroy();
318    
319            // Setup the new tooltip
320            if (container != null && selector != null) {
321                tooltip(widget.getElement(), isAnimated, isHTML, placement.getCssName(), selector, title,
322                        trigger.getCssName(), showDelayMs, hideDelayMs, container);
323            } else if (container != null) {
324                tooltip(widget.getElement(), isAnimated, isHTML, placement.getCssName(), title,
325                        trigger.getCssName(), showDelayMs, hideDelayMs, container);
326            } else if (selector != null) {
327                tooltip(widget.getElement(), isAnimated, isHTML, placement.getCssName(), selector, title,
328                        trigger.getCssName(), showDelayMs, hideDelayMs);
329            } else {
330                tooltip(widget.getElement(), isAnimated, isHTML, placement.getCssName(), title,
331                        trigger.getCssName(), showDelayMs, hideDelayMs);
332            }
333        }
334    
335        /**
336         * Toggle the Tooltip to either show/hide
337         */
338        public void toggle() {
339            call(widget.getElement(), TOGGLE);
340        }
341    
342        /**
343         * Force show the Tooltip
344         */
345        public void show() {
346            call(widget.getElement(), SHOW);
347        }
348    
349        /**
350         * Force hide the Tooltip
351         */
352        public void hide() {
353            call(widget.getElement(), HIDE);
354        }
355    
356        /**
357         * Force the Tooltip to be destroyed
358         */
359        public void destroy() {
360            call(widget.getElement(), DESTROY);
361        }
362    
363        /**
364         * Can be override by subclasses to handle Tooltip's "show" event however
365         * it's recommended to add an event handler to the tooltip.
366         *
367         * @param evt Event
368         * @see org.gwtbootstrap3.client.shared.event.ShowEvent
369         */
370        protected void onShow(final Event evt) {
371            widget.fireEvent(new ShowEvent(evt));
372        }
373    
374        /**
375         * Can be override by subclasses to handle Tooltip's "shown" event however
376         * it's recommended to add an event handler to the tooltip.
377         *
378         * @param evt Event
379         * @see ShownEvent
380         */
381        protected void onShown(final Event evt) {
382            widget.fireEvent(new ShownEvent(evt));
383        }
384    
385        /**
386         * Can be override by subclasses to handle Tooltip's "hide" event however
387         * it's recommended to add an event handler to the tooltip.
388         *
389         * @param evt Event
390         * @see org.gwtbootstrap3.client.shared.event.HideEvent
391         */
392        protected void onHide(final Event evt) {
393            widget.fireEvent(new HideEvent(evt));
394        }
395    
396        /**
397         * Can be override by subclasses to handle Tooltip's "hidden" event however
398         * it's recommended to add an event handler to the tooltip.
399         *
400         * @param evt Event
401         * @see org.gwtbootstrap3.client.shared.event.HiddenEvent
402         */
403        protected void onHidden(final Event evt) {
404            widget.fireEvent(new HiddenEvent(evt));
405        }
406    
407        /**
408         * Adds a show handler to the Tooltip that will be fired when the Tooltip's show event is fired
409         *
410         * @param showHandler ShowHandler to handle the show event
411         * @return HandlerRegistration of the handler
412         */
413        public HandlerRegistration addShowHandler(final ShowHandler showHandler) {
414            return widget.addHandler(showHandler, ShowEvent.getType());
415        }
416    
417        /**
418         * Adds a shown handler to the Tooltip that will be fired when the Tooltip's shown event is fired
419         *
420         * @param shownHandler ShownHandler to handle the shown event
421         * @return HandlerRegistration of the handler
422         */
423        public HandlerRegistration addShownHandler(final ShownHandler shownHandler) {
424            return widget.addHandler(shownHandler, ShownEvent.getType());
425        }
426    
427        /**
428         * Adds a hide handler to the Tooltip that will be fired when the Tooltip's hide event is fired
429         *
430         * @param hideHandler HideHandler to handle the hide event
431         * @return HandlerRegistration of the handler
432         */
433        public HandlerRegistration addHideHandler(final HideHandler hideHandler) {
434            return widget.addHandler(hideHandler, HideEvent.getType());
435        }
436    
437        /**
438         * Adds a hidden handler to the Tooltip that will be fired when the Tooltip's hidden event is fired
439         *
440         * @param hiddenHandler HiddenHandler to handle the hidden event
441         * @return HandlerRegistration of the handler
442         */
443        public HandlerRegistration addHiddenHandler(final HiddenHandler hiddenHandler) {
444            return widget.addHandler(hiddenHandler, HiddenEvent.getType());
445        }
446    
447        /**
448         * {@inheritDoc}
449         */
450        @Override
451        public void clear() {
452            widget = null;
453        }
454    
455        /**
456         * {@inheritDoc}
457         */
458        @Override
459        public Iterator<Widget> iterator() {
460            // Simple iterator for the widget
461            return new Iterator<Widget>() {
462                boolean hasElement = widget != null;
463                Widget returned = null;
464    
465                @Override
466                public boolean hasNext() {
467                    return hasElement;
468                }
469    
470                @Override
471                public Widget next() {
472                    if (!hasElement || (widget == null)) {
473                        throw new NoSuchElementException();
474                    }
475                    hasElement = false;
476                    return (returned = widget);
477                }
478    
479                @Override
480                public void remove() {
481                    if (returned != null) {
482                        Tooltip.this.remove(returned);
483                    }
484                }
485            };
486        }
487    
488        /**
489         * {@inheritDoc}
490         */
491        @Override
492        public boolean remove(final Widget w) {
493            // Validate.
494            if (widget != w) {
495                return false;
496            }
497    
498            // Logical detach.
499            clear();
500            return true;
501        }
502    
503        /**
504         * {@inheritDoc}
505         */
506        @Override
507        public Widget asWidget() {
508            return widget;
509        }
510    
511        /**
512         * {@inheritDoc}
513         */
514        @Override
515        public String toString() {
516            return asWidget().toString();
517        }
518    
519        // @formatter:off
520        private native void bindJavaScriptEvents(final Element e) /*-{
521            var target = this;
522            var $tooltip = $wnd.jQuery(e);
523    
524            $tooltip.on('show.bs.tooltip', function (evt) {
525                target.@org.gwtbootstrap3.client.ui.Tooltip::onShow(Lcom/google/gwt/user/client/Event;)(evt);
526            });
527    
528            $tooltip.on('shown.bs.tooltip', function (evt) {
529                target.@org.gwtbootstrap3.client.ui.Tooltip::onShown(Lcom/google/gwt/user/client/Event;)(evt);
530            });
531    
532            $tooltip.on('hide.bs.tooltip', function (evt) {
533                target.@org.gwtbootstrap3.client.ui.Tooltip::onHide(Lcom/google/gwt/user/client/Event;)(evt);
534            });
535    
536            $tooltip.on('hidden.bs.tooltip', function (evt) {
537                target.@org.gwtbootstrap3.client.ui.Tooltip::onHidden(Lcom/google/gwt/user/client/Event;)(evt);
538            });
539        }-*/;
540    
541        private native void call(final Element e, final String arg) /*-{
542            $wnd.jQuery(e).tooltip(arg);
543        }-*/;
544    
545        private native void tooltip(Element e, boolean animation, boolean html, String placement, String selector,
546                                    String title, String trigger, int showDelay, int hideDelay, String container) /*-{
547            $wnd.jQuery(e).tooltip({
548                animation: animation,
549                html: html,
550                placement: placement,
551                selector: selector,
552                title: title,
553                trigger: trigger,
554                delay: {
555                    show: showDelay,
556                    hide: hideDelay
557                },
558                container: container
559            });
560        }-*/;
561    
562        private native void tooltip(Element e, boolean animation, boolean html, String placement,
563                                    String title, String trigger, int showDelay, int hideDelay, String container) /*-{
564            $wnd.jQuery(e).tooltip({
565                animation: animation,
566                html: html,
567                placement: placement,
568                title: title,
569                trigger: trigger,
570                delay: {
571                    show: showDelay,
572                    hide: hideDelay
573                },
574                container: container
575            });
576        }-*/;
577    
578        private native void tooltip(Element e, boolean animation, boolean html, String placement, String selector,
579                                    String title, String trigger, int showDelay, int hideDelay) /*-{
580            $wnd.jQuery(e).tooltip({
581                animation: animation,
582                html: html,
583                placement: placement,
584                selector: selector,
585                title: title,
586                trigger: trigger,
587                delay: {
588                    show: showDelay,
589                    hide: hideDelay
590                }
591            });
592        }-*/;
593    
594        private native void tooltip(Element e, boolean animation, boolean html, String placement,
595                                    String title, String trigger, int showDelay, int hideDelay) /*-{
596            $wnd.jQuery(e).tooltip({
597                animation: animation,
598                html: html,
599                placement: placement,
600                title: title,
601                trigger: trigger,
602                delay: {
603                    show: showDelay,
604                    hide: hideDelay
605                }
606            });
607        }-*/;
608    }