DeleteConfirmationView.js 9.17 KB
/*
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2015 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 */
define('ux/views/DeleteConfirmationView', [
    'underscore',
    'jquery',
    'util/Util',
    'views/BaseView'
], function(_, $, Util, BaseView) {
    'use strict';

    var DEFAULT_OPTIONS = {
        callback: null,
        transitionDuration: 300
    };

    var DeleteConfirmationView = BaseView.extend(/** @lends DeleteConfirmationView.prototype */{

        tagName: 'span',

        className: 'screens-DeleteConfirmation',

        sistineOptions: {
            events: {
                tap: '_handleTap'
            },

            recognizers: ['tap']
        },

        activeClassName: 'is-active',

        isSelectedClassName: 'is-selected',

        /**
         * Creates a new delete confirmation
         *
         * @class DeleteConfirmationView
         * @extends BaseView
         *
         * @param {Object} [options] An object of configurable options.
         * @param {Object} [options.data]                Additional data to bind to this dialog
         * @param {Function} [options.callback=null]      The function that is called when user taps on the deleteConfirmation bubble
         * @param {Number} [options.transitionDuration=300] The transitionDuration that show/hide this bubble
         */
        constructor: function(options) {
            this._initOptions(options, DEFAULT_OPTIONS);
            DeleteConfirmationView.__super__.constructor.apply(this, arguments);
        },

        initialize: function() {
            DeleteConfirmationView.__super__.initialize.apply(this, arguments);

            this._pointToElem = null;
            this._hideOnPointerDown = false;
            this._transitionIsInProgress = false;

            this._onPointerDownHandler = this._hideOnTouch.bind(this);
            this._onPointerUpHandler = function() {
                this._hideOnPointerDown = false;
            }.bind(this);

            $(document).on('mousedown pointerdown', this._onPointerDownHandler);
            $(document).on('mouseup pointerup', this._onPointerUpHandler);
        },

        render: function() {
            // hide the bubble by default
            this.hide(true);

            return this;
        },

        destroy: function() {
            if (this._pointToElem) {
                this._pointToElem.removeClass(this.activeClassName);
                this._pointToElem.removeClass(this.isSelectedClassName);
                this._pointToElem = null;
            }
            $(document).off('mousedown pointerdown', this._onPointerDownHandler);
            $(document).off('mouseup pointerup', this._onPointerUpHandler);

            if (DeleteConfirmationView._currentDialog === this) {
                DeleteConfirmationView._currentDialog = null;
            }

            return DeleteConfirmationView.__super__.destroy.apply(this, arguments);
        },

        /**
         * show the delete confirmation bubble with transition
         * @param {HTMLElement|jQuery} anchor The element that this DeleteConfirmation bubble is displayed on top of
         */
        show: function(anchor) {
            if (this._transitionIsInProgress && !this._hideOnPointerDown) {
                return;
            }
            this.$el.addClass('is-visible');
            this._transitionIsInProgress = true;

            /* create new confirmation button above anchor */
            var $anchor = $(anchor);

            this._pointToElem = $anchor;
            this._pointToElem.addClass(this.activeClassName);
            this._pointToElem.addClass(this.isSelectedClassName);

            var anchorOffset = $anchor.offset();
            var parentOffset = this.$el.offsetParent().offset();

            /* calculate position of the button to place it above the background img */
            var buttonTop = anchorOffset.top - parentOffset.top - this.$el.height();
            var buttonLeft = anchorOffset.left - parentOffset.left;

            /* center horizontally */
            buttonLeft -= parseInt($anchor.css('margin-left'), 10);
            buttonLeft += ($anchor.outerWidth(true) - this.$el.outerWidth(true)) / 2;

            /* position vertically */
            var verticalPosition = ($anchor.css('background-position') || '0% 0%').split(' ').pop();

            if (verticalPosition.indexOf('%') === -1) {
                buttonTop += parseInt(verticalPosition, 10);
            } else {
                var space = $anchor.height() - parseInt($anchor.css('background-size'), 10);
                buttonTop += (space * parseInt(verticalPosition, 10) / 100.0);
            }

            /* position and animate button */

            this.$el.css({
                'top': Math.ceil(buttonTop),
                'left': Math.ceil(buttonLeft),
                'transform-origin': 'bottom center'
            }).transition({scale: 1}, this.options.transitionDuration, _.bind(function onShowAnimEnd() {
                this._transitionIsInProgress = false;
            }, this));
        },

        /**
         * hide the delete confirmation bubble with transition
         * @param {Boolean} [removeImmediately] if this is true, we hide this item without transition
         * @param {function} [cb] optional callback that is invoked when hide is complete
         */
        hide: function(removeImmediately, cb) {
            if (this._transitionIsInProgress) {
                return;
            }
            this._transitionIsInProgress = true;
            var transitionDuration = removeImmediately
                ? 0 // jshint ignore:line
                : this.options.transitionDuration;

            this.$el.transition({scale: 0}, transitionDuration, _.bind(function onHideAnimEnd() {
                this._transitionIsInProgress = false;
                cb && cb.call(this);
            }, this));

            if (this._pointToElem) {
                this._pointToElem.removeClass(this.activeClassName);
                this._pointToElem.removeClass(this.isSelectedClassName);
            }
        },

        _handleTap: function(ev) {
            this.destroy();
            this.options.callback(ev, this.options);
        },

        _hideOnTouch: function(ev) {
            // if we are hitting on the DeleteConfirmation bubble,
            // the _pointToElem and _pointToElem is active, don't hide on touch
            if ((ev.target !== this.el) && (ev.target !== this._pointToElem.get(0) && this._pointToElem.hasClass(this.isSelectedClassName))) {
                this._hideOnPointerDown = true;
                this.hide(false, function() {
                    this.destroy();
                });
            }
        }
    }, {

        _currentDialog: null,

        /**
         * Creates a new delete confirmation
         *
         * @param {HTMLElement|jQuery} anchor The element to align the dialog to
         * @param {Object} [options] An object of configurable options.
         * @param {Object} [options.data]                Additional data to bind to this dialog
         * @param {String} [options.transitionDuration='0.3s'] The transitionDuration that show/hide this bubble
         * @param {Function} callback Callback function when dialog is tapped
         *
         * @returns {DeleteConfirmationView|null} The delete confirmation dialog
         */
        create: function(anchor, options, callback) {
            // if the currentDialog already existed and the anchor is the same as _pointToElem and _pointToElem is active
            // don't create a new deleteConfirmation, just hide the current one
            var currentDialog = DeleteConfirmationView._currentDialog;

            if (currentDialog && currentDialog._pointToElem && currentDialog._pointToElem.get(0) === anchor
                    && currentDialog._pointToElem.hasClass(currentDialog.isSelectedClassName)) { // jshint ignore:line
                currentDialog.hide();
                return null;
            }

            if (DeleteConfirmationView._currentDialog) {
                DeleteConfirmationView._currentDialog.hide(false, function() {
                    currentDialog.destroy();
                });
                DeleteConfirmationView._currentDialog = null;
            }
            if (_.isFunction(options)) {
                options = {
                    callback: options
                };
            } else {
                options.callback = callback;
            }
            var dlg = DeleteConfirmationView._currentDialog = new DeleteConfirmationView(options);
            var body = document.getElementsByTagName('body')[0];
            body.appendChild(dlg.render().el);
            dlg.show(anchor);
            return dlg;
        }
    });

    // return module exports
    return DeleteConfirmationView;
});