Home Reference Source

src/utils/DialogManager.js

/**
 * Created by lundfall on 06/07/16.
 */
import Timer                 from 'famous/utilities/Timer';
import AnimationController   from 'famous-flex/AnimationController';
import Surface               from 'famous/core/Surface';
import FamousContext         from 'famous/core/Context.js';

import {View}                from 'arva-js/core/View.js';
import {ObjectHelper}        from 'arva-js/utils/ObjectHelper';

import {Injection}           from './Injection.js';
import {Router}              from '../core/Router.js';
import {layout}              from '../layout/decorators.js';
import Easing                from 'famous/transitions/Easing.js';

@layout.scrollable({overscroll: false})
class DialogWrapper extends View {

    /**
     * Defines the size that is appropriate for the dialog. The dialog can return undefined on its getSize function for
     * full-blown sizing instead of true sizing, and it can define a maxSize to specify a maximum that causes the margins
     * to get larger.
     * @param size
     */
    determineSizeWithMargins (size, maxSize, dimension) {
        return ~Math.min(maxSize ? maxSize[dimension] : 480, size[dimension] - 32);
    }

    @layout.size(function(...size) {return this.determineSizeWithMargins(size, this.options.dialog.maxSize, 0)},
        function(...size) {return this.determineSizeWithMargins(size, this.options.dialog.maxSize, 0)})
    @layout.stick.center()
    dialog = this.options.dialog;

    onNewParentSize(parentSize) {
        this._parentSize = parentSize;
    }

    getSize() {
        if (!this._parentSize) {
            return [undefined, undefined];
        }
        let dialogHeight = this.dialog.getSize()[1];
        return this._parentSize[1] > dialogHeight ? [undefined, this._parentSize[1]] : [undefined, dialogHeight];
    }

}

export class DialogManager extends View {

    @layout.fullSize()
    @layout.animate({showInitially: false, animation: AnimationController.Animation.Fade})
    @layout.translate(0, 0, 9000)
    background = new Surface({
        properties: {
            /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#000000+0,000000+100&0.2+0,0.6+100 */
            background: 'radial-gradient(ellipse at center, rgba(0,0,0,0.2) 0%,rgba(0,0,0,0.6) 100%)', /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
            filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#33000000', endColorstr='#99000000',GradientType=1 )" /* IE6-9 fallback on horizontal gradient */
        }
    });

    @layout.translate(0, 0, 9500)
    @layout.fullSize()
    @layout.animate({
        show: {transition: {curve: Easing.outCubic, duration: 300}, animation: AnimationController.Animation.Slide.Up},
        hide: {transition: {curve: Easing.inCubic, duration: 300}, animation: AnimationController.Animation.Slide.Down},
        showInitially: false
    })
    /* Empty content until filled */
    dialog = {};


    constructor(options = {}) {
        super(options);
        /* For ionic-plugin-keyboard */
        if (window.Keyboard) {
            /* Prevent keyboard from showing */
            window.addEventListener('native.keyboardshow', () => {
                /* Hides the keyboard when a dialog is  shown */
                if (this._hasOpenDialog) {
                    Keyboard.hide();
                }
            });
        }
        this.router = Injection.get(Router);
        let famousContext = Injection.get(FamousContext);
        famousContext.add(this);

        this.layout.on('layoutstart', ({size}) => {
            if (this.dialog.onNewParentSize) {
                this.dialog.onNewParentSize(size);
                this._savedParentSize = null;
            } else {
                this._savedParentSize = size;
            }
        });


        document.addEventListener("backbutton", this._onClose);
        this.renderables.background.on('click', this._onClose);
    }

    /**
     *
     * @param dialog
     * @param canCancel
     * @param killOldDialog
     * @returns {*}
     */
    show({dialog, canCancel = true, killOldDialog = true}) {
        if (this._hasOpenDialog) {
            /* If already open dialog we should either close that one, or just keep the current one, depending on the settings */
            if (!killOldDialog) {
                return;
            }
            this.close();
        }
        this._hasOpenDialog = true;

        /* Replace whatever non-showing dialog we have right now with the new dialog */
        this.replaceRenderable('dialog', new DialogWrapper({dialog}));
        if (this._savedParentSize) {
            this.dialog.onNewParentSize(this._savedParentSize);
        }
        this._canCancel = canCancel;
        if (canCancel) {
            /* Disable existing default behavior of backbutton going back to previous route */
            this.initialBackButtonState = this.router.isBackButtonEnabled();
            this.router.setBackButtonEnabled(false);
        }

        /* Show the dialog */
        this.showRenderable('dialog');

        this.dialog.on('closeDialog', (function () {
            /* Forward the arguments coming from the event emitter when closing */
            this.close(...arguments)
        }).bind(this));

        /* Showing the background immediately propagates user's click event that triggered the show() directly to the background,
         * closing the dialog again. Delaying showing the background circumvents this issue. */
        Timer.setTimeout(() => {
            if (this._hasOpenDialog) {
                this.showRenderable('background');
            }
        }, 10);
        return this.dialogComplete();
    }


    _onClose() {
        if (this._canCancel) {
            this.close();
        }
    }

    hasOpenDialog() {
        return this._hasOpenDialog;
    }


    dialogComplete() {
        if (!this._resolveDialogComplete) {
            return this._resolveDialogPromise = new Promise((resolve) => {
                this._resolveDialogComplete = resolve
            });
        } else {
            return this._resolveDialogPromise;
        }

    }

    close() {
        if (this._hasOpenDialog) {

            /* Restore back button state */
            if (this._canCancel) {
                this.router.setBackButtonEnabled(this.initialBackButtonState);
            }
            /* Resolve promise if necessary */
            if (this._resolveDialogComplete) {
                this._resolveDialogComplete(arguments);
                this._resolveDialogComplete = null;
            }
            this._hasOpenDialog = false;

            this.hideRenderable('dialog');
            this.hideRenderable('background');
            this._eventOutput.emit('close', ...arguments);
        }
    }
}