001    /*
002     *  Copyright 2012 GWT-Bootstrap
003     *
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     *  Unless required by applicable law or agreed to in writing, software
011     *  distributed under the License is distributed on an "AS IS" BASIS,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package com.github.gwtbootstrap.client.ui.base;
017    
018    import java.util.Iterator;
019    import java.util.NoSuchElementException;
020    
021    import com.github.gwtbootstrap.client.ui.constants.Placement;
022    import com.github.gwtbootstrap.client.ui.constants.Trigger;
023    import com.github.gwtbootstrap.client.ui.constants.VisibilityChange;
024    import com.google.gwt.dom.client.Element;
025    import com.google.gwt.user.client.DOM;
026    import com.google.gwt.user.client.ui.HasOneWidget;
027    import com.google.gwt.user.client.ui.HasText;
028    import com.google.gwt.user.client.ui.HasWidgets;
029    import com.google.gwt.user.client.ui.IsWidget;
030    import com.google.gwt.user.client.ui.Widget;
031    
032    //@formatter:off
033    /**
034    * Base class for widgets that hover above other widgets.
035    * 
036    * @since 2.0.4.0
037    * 
038    * @author Dominik Mayer
039    * 
040    * @see <a href="http://twitter.github.com/bootstrap/javascript.html#popovers">Bootstrap documentation</a>
041    */
042    //@formatter:on
043    public abstract class HoverBase extends ComplexWidget implements HasWidgets, HasOneWidget, IsAnimated, HasTrigger, HasPlacement, HasText, HasShowDelay, HasVisibility {
044    
045            Widget widget;
046    
047            /**
048             * Whether the widget is animated or not.
049             */
050            protected boolean animated = true;
051    
052            /**
053             * The placement of the widget relative to its trigger element.
054             */
055            protected Placement placement = Placement.TOP;
056    
057            /**
058             * The action that triggers the widget.
059             */
060            protected Trigger trigger = Trigger.HOVER;
061    
062            /**
063             * The delay until the widget is shown.
064             */
065            protected int showDelayInMilliseconds = 0;
066    
067            /**
068             * The delay until the widget is hidden.
069             */
070            protected int hideDelayInMilliseconds = 0;
071    
072            /**
073             * Creates a new widget based on the provided HTML tag.
074             */
075            public HoverBase() {
076                    super("span");
077            }
078    
079            /**
080             * {@inheritDoc}
081             */
082            @Override
083            protected void onLoad() {
084                    super.onLoad();
085                    
086                    removeDataIfExists();
087                    
088                    reconfigure();
089            }
090            
091            protected void removeDataIfExists() {
092                    removeDataIfExists(getWidget().getElement(), getDataName());
093            }
094            
095            protected native void removeDataIfExists(Element e, String dataName) /*-{
096                    if($wnd.jQuery(e).data(dataName)) {
097                            var data = $wnd.jQuery(e).data(dataName);
098                            var eventIn, eventOut;
099                            if (data.options.trigger != 'manual') {
100                                    eventIn  = data.options.trigger == 'hover' ? 'mouseenter' : 'focus'
101                                    eventOut = data.options.trigger == 'hover' ? 'mouseleave' : 'blur'
102                                    data.$element.off(eventIn);
103                                    data.$element.off(eventOut);
104                            }
105                            $wnd.jQuery(e).removeData(dataName);
106                    }
107            }-*/;
108    
109            /**
110             * Adds an HTML data attribute to the widget's tag.
111             * 
112             * @param e target element
113             * 
114             * @param attribute
115             *            the name of the attribute without leading <code>"data-"</code>
116             * @param value
117             *            the value to be stored
118             */
119            protected void setDataAttribute(Element e , String attribute, String value) {
120                    e.setAttribute("data-" + attribute, value);
121            }
122    
123            /**
124             * Returns the data stored in one of the widget's HTML data attributes.
125             * 
126             * @param attribute
127             *            the name of the attribute without leading <code>"data-"</code>
128             * @return the value stored in the tag
129             */
130            protected String getDataAttribute(String attribute) {
131                    return getElement().getAttribute("data-" + attribute);
132            }
133    
134            /**
135             * {@inheritDoc}
136             */
137            public void setAnimation(boolean animated) {
138                    this.animated = animated;
139            }
140    
141            /**
142             * Redraws the widget with the currently set options. This must <b>not</b>
143             * be called when a parameter is updated because it would deactivate all
144             * other parameters. No idea why...
145             */
146            public abstract void reconfigure();
147    
148            /**
149             * {@inheritDoc}
150             */
151            public boolean getAnimation() {
152                    return animated;
153            }
154    
155            /**
156             * {@inheritDoc} Relative to its trigger element.
157             */
158            public void setPlacement(Placement placement) {
159                    this.placement = placement;
160            }
161    
162            /**
163             * {@inheritDoc}
164             */
165            public Placement getPlacement() {
166                    return placement;
167            }
168    
169            /**
170             * {@inheritDoc}
171             */
172            public void setTrigger(Trigger trigger) {
173                    this.trigger = trigger;
174            }
175    
176            /**
177             * {@inheritDoc}
178             */
179            public Trigger getTrigger() {
180                    return trigger;
181            }
182    
183            /**
184             * {@inheritDoc}
185             */
186            public void setShowDelay(int delayInMilliseconds) {
187                    showDelayInMilliseconds = delayInMilliseconds;
188            }
189    
190            /**
191             * {@inheritDoc}
192             */
193            public int getShowDelay() {
194                    return showDelayInMilliseconds;
195            }
196    
197            /**
198             * {@inheritDoc}
199             */
200            public void setHideDelay(int delayInMilliseconds) {
201                    hideDelayInMilliseconds = delayInMilliseconds;
202            }
203    
204            /**
205             * {@inheritDoc}
206             */
207            public int getHideDelay() {
208                    return hideDelayInMilliseconds;
209            }
210    
211            /**
212             * {@inheritDoc}
213             */
214            public void show() {
215                    changeVisibility(VisibilityChange.SHOW);
216            }
217    
218            /**
219             * {@inheritDoc}
220             */
221            public void hide() {
222                    changeVisibility(VisibilityChange.HIDE);
223            }
224    
225            /**
226             * {@inheritDoc}
227             */
228            public void toggle() {
229                    changeVisibility(VisibilityChange.TOGGLE);
230            }
231    
232            /**
233             * Changes the visibility of the widget.
234             * 
235             * @param visibilityChange
236             *            the action to be performed
237             */
238            protected abstract void changeVisibility(VisibilityChange visibilityChange);
239    
240            /**
241             * Adds a widget to this panel.
242             * 
243             * @param w
244             *            the child widget to be added
245             */
246            @Override
247            public void add(Widget w) {
248                    // Can't add() more than one widget to a SimplePanel.
249                    if (getWidget() != null) {
250                            throw new IllegalStateException("SimplePanel can only contain one child widget");
251                    }
252                    setWidget(w);
253            }
254    
255            /**
256             * Gets the panel's child widget.
257             * 
258             * @return the child widget, or <code>null</code> if none is present
259             */
260            public Widget getWidget() {
261                    return widget;
262            }
263    
264            public Iterator<Widget> iterator() {
265                    // Return a simple iterator that enumerates the 0 or 1 elements in this
266                    // panel.
267                    return new Iterator<Widget>() {
268    
269                            boolean hasElement = widget != null;
270    
271                            Widget returned = null;
272    
273                            public boolean hasNext() {
274                                    return hasElement;
275                            }
276    
277                            public Widget next() {
278                                    if (!hasElement || (widget == null)) {
279                                            throw new NoSuchElementException();
280                                    }
281                                    hasElement = false;
282                                    return (returned = widget);
283                            }
284    
285                            public void remove() {
286                                    if (returned != null) {
287                                            HoverBase.this.remove(returned);
288                                    }
289                            }
290                    };
291            }
292    
293            @Override
294            public boolean remove(Widget w) {
295                    // Validate.
296                    if (widget != w) {
297                            return false;
298                    }
299    
300                    // Orphan.
301                    try {
302                            orphan(w);
303                    } finally {
304                            // Physical detach.
305                            getContainerElement().removeChild(w.getElement());
306    
307                            // Logical detach.
308                            widget = null;
309                    }
310                    return true;
311            }
312    
313            public void setWidget(IsWidget w) {
314                    setWidget(asWidgetOrNull(w));
315            }
316            
317            /**
318             * Sets this panel's widget. Any existing child widget will be removed.
319             * 
320             * @param w
321             *            the panel's new widget, or <code>null</code> to clear the
322             *            panel
323             */
324            public void setWidget(Widget w) {
325                    // Validate
326                    if (w == widget) {
327                            return;
328                    }
329                    
330                    if(w.getParent() != null) {
331                            if(widget != null) {
332                                    remove(widget);
333                            }
334                            widget = w;
335                            return;
336                    }
337    
338                    // Detach new child.
339                    if (w != null) {
340                            w.removeFromParent();
341                    }
342    
343                    // Remove old child.
344                    if (widget != null) {
345                            remove(widget);
346                    }
347    
348                    // Logical attach.
349                    widget = w;
350    
351                    if (w != null) {
352                            // Physical attach.
353                            DOM.appendChild(getContainerElement(), widget.getElement());
354    
355                            adopt(widget);
356                    }
357            }
358    
359            /**
360             * Override this method to specify that an element other than the root
361             * element be the container for the panel's child widget. This can be useful
362             * when you want to create a simple panel that decorates its contents.
363             * 
364             * Note that this method continues to return the
365             * {@link com.google.gwt.user.client.Element} class defined in the
366             * <code>User</code> module to maintain backwards compatibility.
367             * 
368             * @return the element to be used as the panel's container
369             */
370            protected com.google.gwt.user.client.Element getContainerElement() {
371                    return getElement();
372            }
373            
374            /**
375             * Get data name of JS Data API.
376             * @return data name
377             */
378            protected abstract String getDataName();
379    }