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.addins.client.popupmenu;
021
022import com.google.gwt.core.client.Scheduler;
023import com.google.gwt.dom.client.Element;
024import com.google.gwt.event.logical.shared.*;
025import com.google.gwt.event.shared.HandlerRegistration;
026import com.google.gwt.user.client.DOM;
027import gwt.material.design.addins.client.MaterialAddins;
028import gwt.material.design.addins.client.base.constants.AddinsCssName;
029import gwt.material.design.client.MaterialDesignBase;
030import gwt.material.design.client.base.JsLoader;
031import gwt.material.design.client.constants.CssName;
032import gwt.material.design.client.ui.html.UnorderedList;
033import gwt.material.design.jquery.client.api.JQueryElement;
034
035import static gwt.material.design.jquery.client.api.JQuery.$;
036
037/**
038 * Popup Menu.
039 *
040 * @author Mark Kevin
041 * @author Ben Dol
042 */
043public class MaterialPopupMenu extends UnorderedList implements JsLoader, HasSelectionHandlers<Element>, HasOpenHandlers<MaterialPopupMenu>,
044        HasCloseHandlers<MaterialPopupMenu> {
045
046    static {
047        if (MaterialAddins.isDebug()) {
048            MaterialDesignBase.injectCss(MaterialPopupMenuDebugClientBundle.INSTANCE.menuCssDebug());
049        } else {
050            MaterialDesignBase.injectCss(MaterialPopupMenuClientBundle.INSTANCE.menuCss());
051        }
052    }
053
054    private int popupX;
055    private int popupY;
056    private String id;
057    private Object selected;
058
059    public MaterialPopupMenu() {
060        id = DOM.createUniqueId();
061        setInitialClasses(AddinsCssName.POPUP_MENU, AddinsCssName.MENU_BAR, CssName.Z_DEPTH_3);
062    }
063
064    @Override
065    protected void onLoad() {
066        super.onLoad();
067
068        load();
069    }
070
071    @Override
072    public void load() {
073        $(this).attr("tabindex", "0");
074        $(this).on("blur", e -> {
075            close();
076            return true;
077        });
078
079        $("*").on("scroll." + id, e -> {
080            close();
081            return true;
082        });
083
084        initializeSelectionEvent();
085
086        close();
087    }
088
089    @Override
090    protected void onUnload() {
091        super.onUnload();
092
093       unload();
094    }
095
096    @Override
097    public void unload() {
098        $(".popup-menu li").off("mouseleave");
099        $(".popup-menu li").off("click");
100        $(".popup-menu li").off("mouseover");
101        $(this).off("." + id);
102        $("*").off("." + id);
103    }
104
105    @Override
106    public void reload() {
107        unload();
108        load();
109    }
110
111
112    private void initializeSelectionEvent() {
113        // Initialization of Selection event
114        $(".popup-menu li").on("click", e -> {
115            e.stopPropagation();
116            SelectionEvent.fire(MaterialPopupMenu.this, $(e.getCurrentTarget()).asElement());
117            $(this).hide();
118            return true;
119        });
120
121        // Check if the dropdown is not visible anymore into it's container either left / bottom side
122        $(".popup-menu li").on("mouseover", (e, param1) -> {
123            JQueryElement item = $(e.getCurrentTarget()).find("a");
124            if (item.attr("data-activates") != null) {
125                JQueryElement dp = $("#" + item.attr("data-activates"));
126
127                double dpWidth = dp.width();
128                double dpLeft = dp.offset().left;
129                double conWidth = body().width();
130
131                double dpHeight = 200;
132                double dpTop = dp.offset().top;
133                double conHeight = body().height();
134
135                if (dpWidth + dpLeft > conWidth) {
136                    dp.addClass(AddinsCssName.EDGE_LEFT);
137                }
138
139                if (dpHeight + dpTop > conHeight) {
140                    dp.addClass(AddinsCssName.EDGE_BOTTOM);
141                }
142            }
143            return true;
144        });
145
146        $(".popup-menu li").on("mouseleave", (e, param) -> {
147            JQueryElement item = $(e.getCurrentTarget()).find("a");
148            if (item.attr("data-activates") != null) {
149                JQueryElement dp = $("#" + item.attr("data-activates"));
150                dp.removeClass(AddinsCssName.EDGE_LEFT);
151                dp.removeClass(AddinsCssName.EDGE_BOTTOM);
152            }
153            return true;
154        });
155    }
156
157    /**
158     * Set the popup position of the context menu
159     *
160     * @param popupX window x position
161     * @param popupY window y position
162     */
163    public void setPopupPosition(int popupX, int popupY) {
164        // Will check if the popup is out of container
165        this.popupX = popupX;
166        this.popupY = popupY;
167        setLeft(popupX);
168        setTop(popupY);
169    }
170
171    @Override
172    public HandlerRegistration addSelectionHandler(SelectionHandler<Element> selectionHandler) {
173        return addHandler(selectionHandler, SelectionEvent.getType());
174    }
175
176    public void open() {
177        setVisible(true);
178        Scheduler.get().scheduleDeferred(() -> setFocus(true));
179
180        // Check if dropdown is out of the container (Left)
181        if ($(this).width() + $(this).offset().left > body().width()) {
182            setLeft(body().width() - $(this).width());
183        }
184        OpenEvent.fire(this, this);
185    }
186
187    public void close() {
188        setVisible(false);
189        CloseEvent.fire(this, this);
190    }
191
192    public Object getSelected() {
193        return selected;
194    }
195
196    public void setSelected(Object selected) {
197        this.selected = selected;
198    }
199
200    @Override
201    public HandlerRegistration addCloseHandler(CloseHandler<MaterialPopupMenu> closeHandler) {
202        return addHandler(new CloseHandler<MaterialPopupMenu>() {
203            @Override
204            public void onClose(CloseEvent<MaterialPopupMenu> closeEvent) {
205                if (isEnabled()) {
206                    closeHandler.onClose(closeEvent);
207                }
208            }
209        }, CloseEvent.getType());
210    }
211
212    @Override
213    public HandlerRegistration addOpenHandler(OpenHandler<MaterialPopupMenu> openHandler) {
214        return addHandler(new OpenHandler<MaterialPopupMenu>() {
215            @Override
216            public void onOpen(OpenEvent<MaterialPopupMenu> openEvent) {
217                if (isEnabled()) {
218                    openHandler.onOpen(openEvent);
219                }
220            }
221        }, OpenEvent.getType());
222    }
223
224    public int getPopupX() {
225        return popupX;
226    }
227
228    public int getPopupY() {
229        return popupY;
230    }
231}