com.google.collide.client.ui.popup.CenterPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.google.collide.client.ui.popup.CenterPanel.java

Source

// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.collide.client.ui.popup;

import com.google.collide.client.util.Elements;
import com.google.collide.mvp.CompositeView;
import com.google.collide.mvp.UiComponent;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiTemplate;
import com.google.gwt.user.client.Timer;

import javax.annotation.Nullable;

import elemental.events.Event;
import elemental.events.EventListener;
import elemental.events.KeyboardEvent.KeyCode;
import elemental.js.events.JsKeyboardEvent;
import elemental.js.html.JsElement;

/**
 * A popup that automatically centers its content, even if the dimensions of the content change. The
 * centering is done in CSS, so performance is very good. A semi-transparent "glass" panel appears
 * behind the popup. The glass is not optional due to the way {@link CenterPanel} is implemented.
 *
 * <p>
 * {@link CenterPanel} animates into and out of view using the shrink in/expand out animation.
 * </p>
 */
public class CenterPanel extends UiComponent<CenterPanel.View> {

    /**
     * Constructs a new {@link CenterPanel} that contains the specified content
     * element.
     * 
     * @param resources the resources to apply to the popup
     * @param content the content to display in the center of the popup
     * @return a new {@link CenterPanel} instance
     */
    public static CenterPanel create(Resources resources, elemental.html.Element content) {
        View view = new View(resources, content);
        return new CenterPanel(view);
    }

    /**
     * The resources used by this UI component.
     */
    public interface Resources extends ClientBundle {
        @Source({ "com/google/collide/client/common/constants.css", "CenterPanel.css" })
        Css centerPanelCss();
    }

    /**
     * The Css Style names used by this panel.
     */
    public interface Css extends CssResource {
        /**
         * Returns duration of the popup animation in milliseconds.
         */
        int animationDuration();

        String content();

        String contentVisible();

        String glass();

        String glassVisible();

        String popup();

        String positioner();
    }

    /**
     * The events sources by the View.
     */
    private interface ViewEvents {
        void onEscapeKey();
    }

    /**
     * The view that renders the {@link CenterPanel}. The View consists of a glass
     * panel that fades out the background, and a DOM structure that positions the
     * contents in the exact center of the screen.
     */
    public static class View extends CompositeView<ViewEvents> {

        @UiTemplate("CenterPanel.ui.xml")
        interface MyBinder extends UiBinder<Element, View> {
        }

        private static MyBinder uiBinder = GWT.create(MyBinder.class);

        final Resources res;

        @UiField(provided = true)
        final Css css;

        @UiField
        DivElement contentContainer;

        @UiField
        DivElement glass;

        @UiField
        DivElement popup;

        View(Resources res, elemental.html.Element content) {
            this.res = res;
            this.css = res.centerPanelCss();
            setElement(Elements.asJsElement(uiBinder.createAndBindUi(this)));
            Elements.asJsElement(contentContainer).appendChild(content);
            handleEvents();
        }

        /**
         * Returns the duration of the popup animation in milliseconds. The return
         * value should equal the value of {@link Css#animationDuration()}.
         */
        protected int getAnimationDuration() {
            return css.animationDuration();
        }

        /**
         * Updates the View to reflect the showing state of the popup.
         * 
         * @param showing true if showing, false if not.
         */
        protected void setShowing(boolean showing) {
            if (showing) {
                glass.addClassName(css.glassVisible());
                contentContainer.addClassName(css.contentVisible());
            } else {
                glass.removeClassName(css.glassVisible());
                contentContainer.removeClassName(css.contentVisible());
            }
        }

        private void handleEvents() {
            getElement().addEventListener(Event.KEYDOWN, new EventListener() {
                @Override
                public void handleEvent(Event evt) {
                    JsKeyboardEvent keyEvt = (JsKeyboardEvent) evt;
                    int keyCode = keyEvt.getKeyCode();
                    if (KeyCode.ESC == keyCode) {
                        if (getDelegate() != null) {
                            getDelegate().onEscapeKey();
                        }
                    }
                }
            }, true);
        }
    }

    private boolean hideOnEscapeEnabled = false;
    private boolean isShowing;

    CenterPanel(View view) {
        super(view);
        handleViewEvents();
    }

    /**
     * Hides the {@link CenterPanel} popup. The popup will animate out of view.
     */
    public void hide() {
        if (!isShowing) {
            return;
        }
        isShowing = false;

        // Animate the popup out of existance.
        getView().setShowing(false);

        // Remove the popup when the animation completes.
        new Timer() {
            @Override
            public void run() {
                // The popup may have been shown before this timer executes.
                if (!isShowing) {
                    Elements.asJsElement(getView().popup).removeFromParent();
                }
            }
        }.schedule(getView().getAnimationDuration());
    }

    /**
     * Checks if the {@link CenterPanel} is showing or animating into view.
     * 
     * @return true if showing, false if hidden
     */
    public boolean isShowing() {
        return isShowing;
    }

    /**
     * Sets whether or not the popup should hide when escape is pressed. The
     * default behavior is to ignore the escape key.
     * 
     * @param isEnabled true to close on escape, false not to
     */
    // TODO: This only works if the popup has focus. We need to capture events.
    // TODO: Consider making escaping the default.
    public void setHideOnEscapeEnabled(boolean isEnabled) {
        this.hideOnEscapeEnabled = isEnabled;
    }

    /**
     * See {@link #show(InputElement)}.
     */
    public void show() {
        show(null);
    }

    /**
     * Displays the {@link CenterPanel} popup. The popup will animate into view.
     *
     * @param selectAndFocusElement an {@link InputElement} to select and focus on when the panel is
     *        shown. If null, no element will be given focus
     */
    public void show(@Nullable final InputElement selectAndFocusElement) {
        if (isShowing) {
            return;
        }
        isShowing = true;

        // Attach the popup to the body.
        final JsElement popup = getView().popup.cast();
        if (popup.getParentElement() == null) {
            // Hide the popup so it can enter its initial state without flickering.
            popup.getStyle().setVisibility("hidden");
            Elements.getBody().appendChild(popup);
        }

        // Start the animation after the element is attached.
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            @Override
            public void execute() {
                // The popup may have been hidden before this timer executes.
                if (isShowing) {
                    popup.getStyle().removeProperty("visibility");
                    getView().setShowing(true);
                    if (selectAndFocusElement != null) {
                        selectAndFocusElement.select();
                        selectAndFocusElement.focus();
                    }
                }
            }
        });
    }

    private void handleViewEvents() {
        getView().setDelegate(new ViewEvents() {
            @Override
            public void onEscapeKey() {
                if (hideOnEscapeEnabled) {
                    hide();
                }
            }
        });
    }
}