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     * @author Joshua Godi
050     */
051    public class Popover implements IsWidget, HasWidgets, HasOneWidget, HasId, HasHover {
052        private static final String TOGGLE = "toggle";
053        private static final String SHOW = "show";
054        private static final String HIDE = "hide";
055        private static final String DESTROY = "destroy";
056    
057        // Defaults from http://getbootstrap.com/javascript/#popovers
058        private boolean isAnimated = true;
059        private boolean isHTML = false;
060        private Placement placement = Placement.TOP;
061        private Trigger trigger = Trigger.HOVER;
062        private String title = "";
063        private String content = "";
064        private int hideDelayMs = 0;
065        private int showDelayMs = 0;
066        private String container = null;
067        private final String selector = null;
068    
069        private Widget widget;
070        private String id;
071    
072        public Popover() {
073        }
074    
075        public Popover(final Widget w) {
076            setWidget(w);
077        }
078    
079        @Override
080        public void setWidget(final Widget w) {
081            // Validate
082            if (w == widget) {
083                return;
084            }
085    
086            // Detach new child
087            if (w != null) {
088                w.removeFromParent();
089            }
090    
091            // Remove old child
092            if (widget != null) {
093                remove(widget);
094            }
095    
096            // Logical attach, but don't physical attach; done by jquery.
097            widget = w;
098            if (widget == null) {
099                return;
100            }
101    
102            // Bind jquery events
103            bindJavaScriptEvents(widget.getElement());
104    
105            // When we attach it, configure the tooltip
106            widget.addAttachHandler(new AttachEvent.Handler() {
107                @Override
108                public void onAttachOrDetach(final AttachEvent event) {
109                    reconfigure();
110                }
111            });
112        }
113    
114        @Override
115        public void add(final Widget child) {
116            if (getWidget() != null) {
117                throw new IllegalStateException("Can only contain one child widget");
118            }
119            setWidget(child);
120        }
121    
122        @Override
123        public void setWidget(final IsWidget w) {
124            widget = (w == null) ? null : w.asWidget();
125        }
126    
127        @Override
128        public Widget getWidget() {
129            return widget;
130        }
131    
132        @Override
133        public void setId(final String id) {
134            this.id = id;
135            if (widget != null) {
136                widget.getElement().setId(id);
137            }
138        }
139    
140        @Override
141        public String getId() {
142            return (widget == null) ? id : widget.getElement().getId();
143        }
144    
145        @Override
146        public void setIsAnimated(final boolean isAnimated) {
147            this.isAnimated = isAnimated;
148        }
149    
150        @Override
151        public boolean isAnimated() {
152            return isAnimated;
153        }
154    
155        @Override
156        public void setIsHtml(final boolean isHTML) {
157            this.isHTML = isHTML;
158        }
159    
160        @Override
161        public boolean isHtml() {
162            return isHTML;
163        }
164    
165        @Override
166        public void setPlacement(final Placement placement) {
167            this.placement = placement;
168        }
169    
170        @Override
171        public Placement getPlacement() {
172            return placement;
173        }
174    
175        @Override
176        public void setTrigger(final Trigger trigger) {
177            this.trigger = trigger;
178        }
179    
180        @Override
181        public Trigger getTrigger() {
182            return trigger;
183        }
184    
185        @Override
186        public void setShowDelayMs(final int showDelayMs) {
187            this.showDelayMs = showDelayMs;
188        }
189    
190        @Override
191        public int getShowDelayMs() {
192            return showDelayMs;
193        }
194    
195        @Override
196        public void setHideDelayMs(final int hideDelayMs) {
197            this.hideDelayMs = hideDelayMs;
198        }
199    
200        @Override
201        public int getHideDelayMs() {
202            return hideDelayMs;
203        }
204    
205        @Override
206        public void setContainer(final String container) {
207            this.container = container;
208        }
209    
210        @Override
211        public String getContainer() {
212            return container;
213        }
214    
215        public String getTitle() {
216            return title;
217        }
218    
219        public void setContent(final String content) {
220            this.content = content;
221        }
222    
223        public void setTitle(final String title) {
224            this.title = title;
225        }
226    
227        public void reconfigure() {
228            // First destroy the old tooltip
229            destroy();
230    
231            // Setup the new tooltip
232            if (container != null && selector != null) {
233                popover(widget.getElement(), isAnimated, isHTML, placement.getCssName(), selector, title, content,
234                        trigger.getCssName(), showDelayMs, hideDelayMs, container);
235            } else if (container != null) {
236                popover(widget.getElement(), isAnimated, isHTML, placement.getCssName(), title, content,
237                        trigger.getCssName(), showDelayMs, hideDelayMs, container);
238            } else if (selector != null) {
239                popover(widget.getElement(), isAnimated, isHTML, placement.getCssName(), selector, title, content,
240                        trigger.getCssName(), showDelayMs, hideDelayMs);
241            } else {
242                popover(widget.getElement(), isAnimated, isHTML, placement.getCssName(), title, content,
243                        trigger.getCssName(), showDelayMs, hideDelayMs);
244            }
245        }
246    
247        public void toggle() {
248            call(widget.getElement(), TOGGLE);
249        }
250    
251        public void show() {
252            call(widget.getElement(), SHOW);
253        }
254    
255        public void hide() {
256            call(widget.getElement(), HIDE);
257        }
258    
259        public void destroy() {
260            call(widget.getElement(), DESTROY);
261        }
262    
263        /**
264         * Can be override by subclasses to handle Tooltip's "show" event however
265         * it's recommended to add an event handler to the tooltip.
266         *
267         * @param evt Event
268         * @see org.gwtbootstrap3.client.shared.event.ShowEvent
269         */
270        protected void onShow(final Event evt) {
271            widget.fireEvent(new ShowEvent(evt));
272        }
273    
274        /**
275         * Can be override by subclasses to handle Tooltip's "shown" event however
276         * it's recommended to add an event handler to the tooltip.
277         *
278         * @param evt Event
279         * @see org.gwtbootstrap3.client.shared.event.ShownEvent
280         */
281        protected void onShown(final Event evt) {
282            widget.fireEvent(new ShownEvent(evt));
283        }
284    
285        /**
286         * Can be override by subclasses to handle Tooltip's "hide" event however
287         * it's recommended to add an event handler to the tooltip.
288         *
289         * @param evt Event
290         * @see org.gwtbootstrap3.client.shared.event.HideEvent
291         */
292        protected void onHide(final Event evt) {
293            widget.fireEvent(new HideEvent(evt));
294        }
295    
296        /**
297         * Can be override by subclasses to handle Tooltip's "hidden" event however
298         * it's recommended to add an event handler to the tooltip.
299         *
300         * @param evt Event
301         * @see org.gwtbootstrap3.client.shared.event.HiddenEvent
302         */
303        protected void onHidden(final Event evt) {
304            widget.fireEvent(new HiddenEvent(evt));
305        }
306    
307        public HandlerRegistration addShowHandler(final ShowHandler showHandler) {
308            return widget.addHandler(showHandler, ShowEvent.getType());
309        }
310    
311        public HandlerRegistration addShownHandler(final ShownHandler shownHandler) {
312            return widget.addHandler(shownHandler, ShownEvent.getType());
313        }
314    
315        public HandlerRegistration addHideHandler(final HideHandler hideHandler) {
316            return widget.addHandler(hideHandler, HideEvent.getType());
317        }
318    
319        public HandlerRegistration addHiddenHandler(final HiddenHandler hiddenHandler) {
320            return widget.addHandler(hiddenHandler, HiddenEvent.getType());
321        }
322    
323        @Override
324        public void clear() {
325            widget = null;
326        }
327    
328        @Override
329        public Iterator<Widget> iterator() {
330            // Simple iterator for the widget
331            return new Iterator<Widget>() {
332                boolean hasElement = widget != null;
333                Widget returned = null;
334    
335                @Override
336                public boolean hasNext() {
337                    return hasElement;
338                }
339    
340                @Override
341                public Widget next() {
342                    if (!hasElement || (widget == null)) {
343                        throw new NoSuchElementException();
344                    }
345                    hasElement = false;
346                    return (returned = widget);
347                }
348    
349                @Override
350                public void remove() {
351                    if (returned != null) {
352                        Popover.this.remove(returned);
353                    }
354                }
355            };
356        }
357    
358        @Override
359        public boolean remove(final Widget w) {
360            // Validate.
361            if (widget != w) {
362                return false;
363            }
364    
365            // Logical detach.
366            clear();
367            return true;
368        }
369    
370        @Override
371        public Widget asWidget() {
372            return widget;
373        }
374    
375        // @formatter:off
376        private native void bindJavaScriptEvents(final Element e) /*-{
377            var target = this;
378            var $popover = $wnd.jQuery(e);
379    
380            $popover.on('show.bs.popover', function (evt) {
381                target.@org.gwtbootstrap3.client.ui.Popover::onShow(Lcom/google/gwt/user/client/Event;)(evt);
382            });
383    
384            $popover.on('shown.bs.popover', function (evt) {
385                target.@org.gwtbootstrap3.client.ui.Popover::onShown(Lcom/google/gwt/user/client/Event;)(evt);
386            });
387    
388            $popover.on('hide.bs.popover', function (evt) {
389                target.@org.gwtbootstrap3.client.ui.Popover::onHide(Lcom/google/gwt/user/client/Event;)(evt);
390            });
391    
392            $popover.on('hidden.bs.popover', function (evt) {
393                target.@org.gwtbootstrap3.client.ui.Popover::onHidden(Lcom/google/gwt/user/client/Event;)(evt);
394            });
395        }-*/;
396    
397        private native void call(final Element e, final String arg) /*-{
398            $wnd.jQuery(e).popover(arg);
399        }-*/;
400    
401        private native void popover(Element e, boolean animation, boolean html, String placement, String selector,
402                                    String title, String content, String trigger, int showDelay, int hideDelay, String container) /*-{
403            $wnd.jQuery(e).popover({
404                animation: animation,
405                html: html,
406                placement: placement,
407                selector: selector,
408                title: title,
409                content: content,
410                trigger: trigger,
411                delay: {
412                    show: showDelay,
413                    hide: hideDelay
414                },
415                container: container
416            });
417        }-*/;
418    
419        private native void popover(Element e, boolean animation, boolean html, String placement,
420                                    String title, String content, String trigger, int showDelay, int hideDelay, String container) /*-{
421            $wnd.jQuery(e).popover({
422                animation: animation,
423                html: html,
424                placement: placement,
425                title: title,
426                content: content,
427                trigger: trigger,
428                delay: {
429                    show: showDelay,
430                    hide: hideDelay
431                },
432                container: container
433            });
434        }-*/;
435    
436        private native void popover(Element e, boolean animation, boolean html, String placement, String selector,
437                                    String title, String content, String trigger, int showDelay, int hideDelay) /*-{
438            $wnd.jQuery(e).popover({
439                animation: animation,
440                html: html,
441                placement: placement,
442                selector: selector,
443                title: title,
444                content: content,
445                trigger: trigger,
446                delay: {
447                    show: showDelay,
448                    hide: hideDelay
449                }
450            });
451        }-*/;
452    
453        private native void popover(Element e, boolean animation, boolean html, String placement,
454                                    String title, String content, String trigger, int showDelay, int hideDelay) /*-{
455            $wnd.jQuery(e).popover({
456                animation: animation,
457                html: html,
458                placement: placement,
459                title: title,
460                content: content,
461                trigger: trigger,
462                delay: {
463                    show: showDelay,
464                    hide: hideDelay
465                }
466            });
467        }-*/;
468    }