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 }