001package com.github.gwtbootstrap.client.ui;
002
003import com.github.gwtbootstrap.client.ui.base.HasVisibility;
004import com.github.gwtbootstrap.client.ui.event.HasVisibleHandlers;
005import com.github.gwtbootstrap.client.ui.base.MarkupWidget;
006import com.github.gwtbootstrap.client.ui.constants.Constants;
007import com.github.gwtbootstrap.client.ui.constants.VisibilityChange;
008import com.github.gwtbootstrap.client.ui.event.HiddenEvent;
009import com.github.gwtbootstrap.client.ui.event.HiddenHandler;
010import com.github.gwtbootstrap.client.ui.event.HideEvent;
011import com.github.gwtbootstrap.client.ui.event.HideHandler;
012import com.github.gwtbootstrap.client.ui.event.ShowEvent;
013import com.github.gwtbootstrap.client.ui.event.ShowHandler;
014import com.github.gwtbootstrap.client.ui.event.ShownEvent;
015import com.github.gwtbootstrap.client.ui.event.ShownHandler;
016import com.google.gwt.core.client.JavaScriptObject;
017import com.google.gwt.core.client.Scheduler;
018import com.google.gwt.core.client.Scheduler.ScheduledCommand;
019import com.google.gwt.dom.client.Element;
020import com.google.gwt.event.shared.HandlerRegistration;
021import com.google.gwt.user.client.Event;
022import com.google.gwt.user.client.ui.Widget;
023
024/**
025 * Markup widget of Collapse
026 * <p>
027 * It's a markup widget (decorator widget).
028 * it's can exchange child to Collapsible widget.
029 * 
030 * It's need trigger, You have 2 ways to create trigger.<br/>
031 * <ol>
032 *   <li> Using {@link CollapseTrigger}. </li>
033 *   <li> Calling {@link #toggle()}. </li>
034 * </ol>
035 * </p>
036 * 
037 * <p>
038 * <h3>UiBinder Usage:</h3>
039 * </p>
040 * <pre>
041 * {@code
042 * <b:Collapse b:id="toggle1" defaultOpen="true">
043 *   <!-- it can be added any widget, but accept one wdiget.
044 *   <b:Label>aaa</b:Label>
045 * </b:Collapse>
046 * }
047 * </pre>
048 * 
049 * @since 2.2.1.0
050 * @author ohashi keisuke
051 * @see Accordion
052 * @see Collapse
053 * @see CollapseTrigger
054 * @see <a href="http://twitter.github.com/bootstrap/javascript.html#collapse">Twitter Bootstrap document</a>
055 * 
056 *
057 */
058public class Collapse extends MarkupWidget implements HasVisibility, HasVisibleHandlers {
059
060    private String parent;
061    
062    private boolean toggle = false;
063    
064    private boolean existTrigger = false;
065
066    private boolean dafaultOpen;
067
068    /**
069     * Get parent selector
070     * @return parent parent selector
071     */
072    public String getParent() {
073        return parent;
074    }
075
076    /**
077     * Set parent selector.
078     * 
079     * it only work with {@link AccordionGroup},
080     * Please see <a href="https://github.com/twitter/bootstrap/issues/4988">this issue</a>.
081     * 
082     * @param parent parent selector
083     */
084    public void setParent(String parent) {
085        this.parent = parent;
086    }
087
088    /**
089     * is the collapsible element toggled on invocation
090     * @return toggle true:toggled , false: un-toggled
091     */
092    public boolean isToggle() {
093        return toggle;
094    }
095
096    /**
097     * Toggles the collapsible element on invocation
098     * @param toggle true: toggled on invocation , false : not-toggled
099     */
100    public void setToggle(boolean toggle) {
101        this.toggle = toggle;
102    }
103    
104    public void setDefaultOpen(boolean dafaultOpen) {
105        this.dafaultOpen = dafaultOpen;
106        
107        if(widget != null && !widget.isAttached()) {
108            widget.setStyleName(Constants.IN , dafaultOpen);
109        }
110        
111    }
112    
113    /**
114     * {@inheritDoc}
115     */
116    @Override
117    public Widget asWidget() {
118        
119        if(widget != null) {
120            Scheduler.get().scheduleDeferred(new ScheduledCommand() {
121                
122                @Override
123                public void execute() {
124                    if(!isExistTrigger()){
125                        reconfigure();
126                    } else {
127                        configure(widget.getElement(), parent, toggle);
128                        setHandlerFunctions(widget.getElement());
129                    }
130                }
131            });
132        }
133        
134        return getWidget();
135    }
136    
137    /**
138     * {@inheritDoc}
139     */
140    @Override
141    public void setWidget(Widget w) {
142        super.setWidget(w);
143        
144        if(widget != null) {
145            widget.addStyleName(Constants.COLLAPSE);
146        }
147        
148    }
149    
150    /**
151     * {@inheritDoc}
152     */
153    @Override
154    public HandlerRegistration addHideHandler(HideHandler handler) {
155        return getWidget().addHandler(handler, HideEvent.getType());
156    }
157
158    /**
159     * {@inheritDoc}
160     */
161    @Override
162    public HandlerRegistration addHiddenHandler(HiddenHandler handler) {
163        return getWidget().addHandler(handler, HiddenEvent.getType());
164    }
165
166    /**
167     * {@inheritDoc}
168     */
169    @Override
170    public HandlerRegistration addShowHandler(ShowHandler handler) {
171        return getWidget().addHandler(handler, ShowEvent.getType());
172    }
173
174    /**
175     * {@inheritDoc}
176     */
177    @Override
178    public HandlerRegistration addShownHandler(ShownHandler handler) {
179        return getWidget().addHandler(handler, ShownEvent.getType());
180    }
181
182    /**
183     * {@inheritDoc}
184     */
185    //@Override
186    public void show(boolean autoShown) {
187        changeVisibility(VisibilityChange.SHOW, autoShown);
188    }
189
190    /**
191     * {@inheritDoc}
192     */
193    @Override
194    public void show() {
195        changeVisibility(VisibilityChange.SHOW);
196    }
197
198    /**
199     * {@inheritDoc}
200     */
201    @Override
202    public void hide() {
203        changeVisibility(VisibilityChange.HIDE);
204    }
205
206    /**
207     * {@inheritDoc}
208     */
209    //@Override
210    public void hide(boolean autoHidden) {
211        changeVisibility(VisibilityChange.HIDE, autoHidden);
212    }
213
214    /**
215     * {@inheritDoc}
216     */
217    @Override
218    public void toggle() {
219        changeVisibility(VisibilityChange.TOGGLE);
220    }
221
222    /**
223     * Change visibility
224     * @param visibilityChange call method
225     */
226    protected void changeVisibility(VisibilityChange visibilityChange) {
227        
228        if(widget == null) return;
229        
230        changeVisibility(widget.getElement() , visibilityChange.get());
231    }
232
233    protected void changeVisibility(VisibilityChange visibilityChange, boolean autoTriggered) {
234
235        if(widget == null) return;
236
237        changeVisibility(widget.getElement() , visibilityChange.get(), autoTriggered);
238    }
239    
240    /**
241     * Is exist Trigger?
242     * @return existTrigger
243     */
244    public boolean isExistTrigger() {
245        return existTrigger;
246    }
247
248    /**
249     * Is there the trigger(Collapse Trigger)
250     * @param existTrigger exists:true, none:false
251     */
252    public void setExistTrigger(boolean existTrigger) {
253        this.existTrigger = existTrigger;
254    }
255    
256    /**
257     * re configure setting
258     */
259    public void reconfigure() {
260        
261        if(widget == null) return;
262        
263        setDefaultOpen(dafaultOpen);
264        
265        removeDataIfExists(widget.getElement());
266        
267        setHandlerFunctions(widget.getElement());
268        
269        configure(widget.getElement(), parent, toggle);
270    }
271    
272    //@fomatter:off
273    /**
274     * Configure collapse settings.
275     * @param e element
276     * @param parent parent selector
277     * @param toggle is toggled on added document
278     */
279    public native void configure(Element e, String parent, boolean toggle) /*-{
280        $wnd.jQuery(e).collapse({
281            "parent" : parent || false,
282            "toggle" : toggle
283        });
284    }-*/;
285    
286    /**
287     * Configure collapse settings.
288     * @param selector selector
289     * @param parent parent selector
290     * @param toggle is toggled on added document
291     */
292    public static native void configure(String selector, String parent, boolean toggle) /*-{
293        $wnd.jQuery(selector).collapse({
294            "parent" : parent || false,
295            "toggle" : toggle
296        });
297        
298    }-*/;
299
300    /**
301     * Remove data api.
302     * @param e element
303     */
304    protected native void removeDataIfExists(Element e) /*-{
305        var $this = $wnd.jQuery(e);
306        if($this.data('collapse')) {
307            $this.removeData('parent').removeData('toggle').removeData('collapse');
308        }
309    }-*/;
310    
311    /**
312     * Links the Java functions that fire the events.
313     */
314    protected native void setHandlerFunctions(Element e) /*-{
315        var that = this;
316        var $this = $wnd.jQuery(e);
317
318        var autoTriggeredCheck = function (event, removeProperty) {
319            var collapse = $wnd.jQuery(event.target).data('collapse');
320            if (collapse && collapse.autoTriggered) {
321                event.autoTriggered = true;
322                if (removeProperty)
323                    collapse.autoTriggered = false;
324            }
325        };
326
327        $this.off('show');
328        $this.off('shown');
329        $this.off('hide');
330        $this.off('hidden');
331
332        $this.on('hide', function(e) {
333            if (e.target === this) {
334                autoTriggeredCheck(e);
335                that.@com.github.gwtbootstrap.client.ui.Collapse::onHide(Lcom/google/gwt/user/client/Event;)(e);
336                e.stopPropagation();
337            }
338        });
339        $this.on('hidden', function(e) {
340            if (e.target === this) {
341                autoTriggeredCheck(e, true);
342                that.@com.github.gwtbootstrap.client.ui.Collapse::onHidden(Lcom/google/gwt/user/client/Event;)(e);
343                e.stopPropagation();
344            }
345        });
346        $this.on('show', function(e) {
347            if (e.target === this) {
348                autoTriggeredCheck(e);
349                that.@com.github.gwtbootstrap.client.ui.Collapse::onShow(Lcom/google/gwt/user/client/Event;)(e);
350                e.stopPropagation();
351            }
352        });
353        $this.on('shown', function(e) {
354            if (e.target === this) {
355                autoTriggeredCheck(e, true);
356                that.@com.github.gwtbootstrap.client.ui.Collapse::onShown(Lcom/google/gwt/user/client/Event;)(e);
357                e.stopPropagation();
358            }
359        });
360    }-*/;
361
362    protected native void changeVisibility(Element e , String c) /*-{
363        $wnd.jQuery(e).collapse(c);
364    }-*/;
365
366    protected native void changeVisibility(Element e, String visibility, boolean autoTriggered) /*-{
367        var $e = $wnd.jQuery(e);
368
369        var collapse = $e.data('collapse');
370        if (collapse)
371            collapse.autoTriggered = autoTriggered;
372
373        $e.collapse(c);
374
375    }-*/;
376    
377    public static native void changeVisibility(String target , String c) /*-{
378        $wnd.jQuery(target).collapse(c);
379    }-*/;
380
381    public static native void changeVisibility(String target , String c, boolean autoTriggered) /*-{
382        var $e = $wnd.jQuery(e);
383
384        var collapse = $e.data('collapse');
385        if (collapse)
386            collapse.autoTriggered = autoTriggered;
387
388        $e.collapse(c);
389    }-*/;
390    //@fomatter:on
391
392    private native boolean getAutoTriggered(JavaScriptObject jso) /*-{
393        // Prevent null result
394        if (jso.autoTriggered) return true;
395        return false;
396    }-*/;
397
398    /**
399     * This method is called immediately when the widget's {@link #hide()}
400     * method is executed.
401     */
402    protected void onHide(Event e) {
403        widget.fireEvent(new HideEvent(e, getAutoTriggered(e)));
404    }
405
406    /**
407     * This method is called once the widget is completely hidden.
408     */
409    protected void onHidden(Event e) {
410        widget.fireEvent(new HiddenEvent(e, getAutoTriggered(e)));
411    }
412
413    /**
414     * This method is called immediately when the widget's {@link #show()}
415     * method is executed.
416     */
417    protected void onShow(Event e) {
418        widget.fireEvent(new ShowEvent(e, getAutoTriggered(e)));
419    }
420
421    /**
422     * This method is called once the widget is completely shown.
423     */
424    protected void onShown(Event e) {
425        widget.fireEvent(new ShownEvent(e, getAutoTriggered(e)));
426    }
427}