001/*
002 * #%L
003 * GwtMaterial
004 * %%
005 * Copyright (C) 2015 - 2017 GwtMaterialDesign
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 * 
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 * 
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package gwt.material.design.client.ui;
021
022import com.google.gwt.dom.client.Document;
023import com.google.gwt.dom.client.Element;
024import com.google.gwt.event.logical.shared.*;
025import com.google.gwt.event.shared.HandlerRegistration;
026import gwt.material.design.client.base.*;
027import gwt.material.design.client.base.mixin.CssTypeMixin;
028import gwt.material.design.client.base.mixin.FullscreenMixin;
029import gwt.material.design.client.constants.CssName;
030import gwt.material.design.client.constants.ModalType;
031import gwt.material.design.client.js.JsModalOptions;
032
033import static gwt.material.design.client.js.JsMaterialElement.$;
034
035//@formatter:off
036
037/**
038 * Dialogs are content that are not original visible on a page but show up with
039 * extra information if needed. The transitions should make the appearance of
040 * the dialog make sense and not jarring to the user.
041 * <p>
042 * <h3>UiBinder Usage:</h3>
043 * <p>
044 * <pre>
045 * {@code
046 * <m:MaterialModal ui:field="modal" type="FIXED_FOOTER" dismissible="true" inDuration="500" outDuration="800">
047 *     <m:MaterialModalContent>
048 *         <m:MaterialTitle title="Title" description="Description" />
049 *     </m:MaterialModalContent>
050 *     <m:MaterialModalFooter>
051 *         <m:MaterialButton text="Close Modal" type="FLAT"/>
052 *     </m:MaterialModalFooter>
053 * </m:MaterialModal>
054 * }
055 * </pre>
056 * <p>
057 * *
058 * <h3>Java Usage:</h3>
059 * <p>
060 * <pre>
061 * {
062 *     &#064;code
063 *     &#064;UiField
064 *     MaterialModal modal;
065 *     modal.open();
066 * }
067 * </pre>
068 * <p>
069 * </p>
070 *
071 * @author kevzlou7979
072 * @author Ben Dol
073 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#dialogs">Material Modals</a>
074 * @see <a href="https://material.io/guidelines/components/dialogs.html#">Material Design Specification</a>
075 */
076// @formatter:on
077public class MaterialModal extends MaterialWidget implements HasType<ModalType>, HasInOutDurationTransition,
078        HasDismissible, HasCloseHandlers<MaterialModal>, HasOpenHandlers<MaterialModal>, HasFullscreen {
079
080    private JsModalOptions options = new JsModalOptions();
081
082    private CssTypeMixin<ModalType, MaterialModal> typeMixin;
083    private FullscreenMixin fullscreenMixin;
084
085    public MaterialModal() {
086        super(Document.get().createDivElement(), CssName.MODAL);
087    }
088
089
090    @Override
091    public void setType(ModalType type) {
092        getTypeMixin().setType(type);
093    }
094
095    @Override
096    public ModalType getType() {
097        return getTypeMixin().getType();
098    }
099
100    @Override
101    public void setInDuration(int inDuration) {
102        options.in_duration = inDuration;
103    }
104
105    @Override
106    public int getInDuration() {
107        return options.in_duration;
108    }
109
110    @Override
111    public void setOutDuration(int outDuration) {
112        options.out_duration = outDuration;
113    }
114
115    @Override
116    public int getOutDuration() {
117        return options.out_duration;
118    }
119
120    @Override
121    public void setDismissible(boolean dismissible) {
122        options.dismissible = dismissible;
123    }
124
125    @Override
126    public boolean isDismissible() {
127        return options.dismissible;
128    }
129
130    @Override
131    public void setOpacity(double opacity) {
132        options.opacity = opacity;
133    }
134
135    @Override
136    public double getOpacity() {
137        return options.opacity;
138    }
139
140    @Override
141    public void setEnabled(boolean enabled) {
142        getEnabledMixin().setEnabled(this, enabled);
143    }
144
145    @Override
146    public void setFullscreen(boolean value) {
147        if (getType() != ModalType.BOTTOM_SHEET) {
148            getFullscreenMixin().setFullscreen(value);
149        }
150    }
151
152    @Override
153    public boolean isFullscreen() {
154        return getFullscreenMixin().isFullscreen();
155    }
156
157    /**
158     * Open the modal programmatically
159     * <p>
160     * Note: the MaterialModal component must be added to the document before
161     * calling this method. When declaring this modal on a UiBinder file, the
162     * MaterialModal is already added, but if you call it using pure Java, you
163     * must add it to a container before opening the modal. You can do it by
164     * calling, for example:
165     * </p>
166     * <pre>
167     * MaterialModal modal = new MaterialModal();
168     * RootPanel.get().add(modal);
169     * </pre>
170     *
171     * @throws IllegalStateException If the MaterialModal is not added to the document
172     */
173    public void open() {
174        open(true);
175    }
176
177    /**
178     * Open the modal programmatically
179     * <p>
180     * Note: the MaterialModal component must be added to the document before
181     * calling this method. When declaring this modal on a UiBinder file, the
182     * MaterialModal is already added, but if you call it using pure Java, you
183     * must add it to a container before opening the modal. You can do it by
184     * calling, for example:
185     * </p>
186     * <pre>
187     * MaterialModal modal = new MaterialModal();
188     * RootPanel.get().add(modal);
189     * </pre>
190     * @param fireEvent - Flag whether this component fires Open Event
191     *
192     * @throws IllegalStateException If the MaterialModal is not added to the document
193     */
194    public void open(boolean fireEvent) {
195        // the modal must be added to the document before opening
196        if (this.getParent() == null) {
197            throw new IllegalStateException(
198                    "The MaterialModal must be added to the document before calling open().");
199        }
200        open(getElement(), fireEvent);
201    }
202
203    /**
204     * Open modal with additional properties
205     *
206     * @param e           - Modal Component
207     * @param fireEvent   - Flag whether this component fires Open Event
208     */
209    protected void open(Element e, boolean fireEvent) {
210        options.complete = () -> onNativeClose(true, true);
211        options.ready = () -> onNativeOpen(fireEvent);
212        $(e).openModal(options);
213    }
214
215    protected void onNativeOpen(boolean fireEvent) {
216        if(fireEvent) {
217            OpenEvent.fire(this, this);
218        }
219    }
220
221    protected void onNativeClose(boolean autoClosed, boolean fireEvent) {
222        if (fireEvent) {
223            CloseEvent.fire(this, this, autoClosed);
224        }
225    }
226
227    /**
228     * Close the modal programmatically. It is the same as calling
229     * {@link #close(boolean)} with <code>false</code> as parameter.
230     * <p>
231     * Note: you may need to remove it MaterialModal from the document if you
232     * are not using UiBinder. See {@link #open()}.
233     * </p>
234     */
235    public void close() {
236        close(false);
237    }
238
239    /**
240     * Close the modal programmatically.
241     * <p>
242     * Note: you may need to remove it MaterialModal from the document if you
243     * are not using UiBinder. See {@link #open()}.
244     * </p>
245     *
246     * @param autoClosed Flag indicating if the modal was automatically dismissed
247     * @see CloseEvent
248     */
249    public void close(boolean autoClosed) {
250        close(autoClosed, true);
251    }
252
253    /**
254     * Close the modal programmatically.
255     * <p>
256     * Note: you may need to remove it MaterialModal from the document if you
257     * are not using UiBinder. See {@link #open()}.
258     * </p>
259     *
260     * @param autoClosed Flag indicating if the modal was automatically dismissed
261     * @param fireEvent Flag whether this component fires Close Event
262     * @see CloseEvent
263     */
264    public void close(boolean autoClosed, boolean fireEvent) {
265        close(getElement(), autoClosed, fireEvent);
266    }
267
268    protected void close(Element e, boolean autoClosed, boolean fireEvent) {
269        if (options != null) {
270            options.complete = () -> onNativeClose(autoClosed, fireEvent);
271            $(e).closeModal(options);
272        }
273    }
274
275    @Override
276    public HandlerRegistration addCloseHandler(CloseHandler<MaterialModal> handler) {
277        return addHandler(handler, CloseEvent.getType());
278    }
279
280    @Override
281    public HandlerRegistration addOpenHandler(OpenHandler<MaterialModal> handler) {
282        return addHandler(handler, OpenEvent.getType());
283    }
284
285    protected CssTypeMixin<ModalType, MaterialModal> getTypeMixin() {
286        if (typeMixin == null) {
287            typeMixin = new CssTypeMixin<>(this);
288        }
289        return typeMixin;
290    }
291
292    protected FullscreenMixin getFullscreenMixin() {
293        if (fullscreenMixin == null) {
294            fullscreenMixin = new FullscreenMixin(this);
295        }
296        return fullscreenMixin;
297    }
298}