<commit_before>/**
 * Developer: <NAME>
 * Date: 11/26/2014
 * Copyright: 2009-2016 Comindware®
 *       All Rights Reserved
 * Published under the MIT license
 */

"use strict";

import '../../libApi';
import { helpers } from '../../utils/utilsApi';
import WindowService from '../../services/WindowService';
import BlurableBehavior from '../../views/behaviors/BlurableBehavior';
import template from '../templates/popout.hbs';

const slice = Array.prototype.slice;

const classes = {
    OPEN: 'open',
    DIRECTION_UP: 'popout__up',
    DIRECTION_DOWN: 'popout__down',
    FLOW_LEFT: 'dev-popout-flow-left',
    FLOW_RIGHT: 'dev-popout-flow-right',
    CUSTOM_ANCHOR: 'popout__action-btn',
    DEFAULT_ANCHOR: 'popout__action'
};

const popoutFlow = {
    LEFT: 'left',
    RIGHT: 'right'
};

const popoutDirection = {
    UP: 'up',
    DOWN: 'down'
};

const height = {
    AUTO: 'auto',
    BOTTOM: 'bottom'
};

const defaultOptions = {
    popoutFlow: popoutFlow.LEFT,
    customAnchor: false,
    fade: false,
    height: 'auto',
    autoOpen: true,
    direction: popoutDirection.DOWN,
    renderAfterClose: true
};

/**
 * @name PopoutView
 * @memberof module:core.dropdown.views
 * @class Composite View that may be to display a dropdown panel as a speech bubble. Unlike {@link module:core.dropdown.views.DropdownView DropdownView},
 * the panel is displayed in speech bubble and has a triangle like in comics.
 * A dropdown view contains button and panel regions that can be fully customizable by the properties <code>buttonView</code> and <code>panelView</code>.
 * <ul>
 * <li>Button View is used for displaying a button. Click on that button trigger a panel to open.</li>
 * <li>Panel View is used to display a panel that drops down.</li>
 * </ul>
 * Panel width is fully determined by its layout and the <code>popoutFlow</code> option.
 * Panel height is determined by its layout and the <code>height</code> option.
 * A place where the panel appears depends on the <code>direction</code> and <code>popoutFlow</code> options.<br/>
 * Possible events:<ul>
 * <li><code>'before:open' (popoutView)</code> - fires before the panel has opened.</li>
 * <li><code>'open' (popoutView)</code> - fires after the panel has opened.</li>
 * <li><code>'before:close' (popoutView)</code> - fires before the panel has closed.</li>
 * <li><code>'close' (popoutView, ...)</code> - fires after the panel has closed.
 * If the panel was closed via <code>close(...)</code> method, the arguments of this method are transferred into this event.</li>
 * <li><code>'button:\*' </code> - all events the buttonView triggers are repeated by this view with 'button:' prefix.</li>
 * <li><code>'panel:\*' </code> - all events the panelView triggers are repeated by this view with 'panel:' prefix.</li>
 * </ul>
 * @constructor
 * @extends Marionette.LayoutView
 * @param {Object} options Options object.
 * @param {Marionette.View} options.buttonView View class for displaying the button.
 * @param {(Object|Function)} [options.buttonViewOptions] Options passed into the view on its creation.
 * @param {Marionette.View} options.panelView View class for displaying the panel. The view is created every time the panel is triggered to open.
 * @param {(Object|Function)} [options.panelViewOptions] Options passed into the view on its creation.
 * @param {Boolean} [options.autoOpen=true] Whether click on the button should trigger the panel to open.
 * @param {Boolean} [options.customAnchor=false] Whether to attach the speech bubble triangle (anchor) to a custom element in <code>buttonView</code>.
 *                                               The View passed into the <code>buttonView</code> option must implement
 *                                               @{link module:core.dropdown.views.behaviors.CustomAnchorBehavior CustomAnchorBehavior}.
 * @param {String} [options.direction='down'] Opening direction. Can be either: <code>'up'</code>, <code>'down'</code>.
 * @param {Boolean} [options.fade=false] Whether to dim the background when the panel is open.
 * @param {String} [options.height='auto'] A way of determining the panel height.
 *                                       <ul><li><code>'auto'</code> - is determined by panel's layout only.</li>
 *                                       <li><code>'bottom'</code> - the bottom border is fixed to the bottom of the window.</li></ul>
 * @param {String} [options.popoutFlow='left'] Panel's horizontal position.
 *                                       <ul><li><code>'left'</code> - The left border of the panel is attached to the left border of the button.
 *                                       The panel grows to the right.</li>
 *                                       <li><code>'right'</code> - The right border of the panel is attached to the right border of the button.
 *                                       The panel grows to the left.</li></ul>
 * @param {Boolean} [options.renderAfterClose=true] Whether to trigger button render when the panel has closed.
 * */

export default Marionette.LayoutView.extend(/** @lends module:core.dropdown.views.PopoutView.prototype */ {
    initialize: function (options) {
        _.defaults(this.options, defaultOptions);
        helpers.ensureOption(options, 'buttonView');
        helpers.ensureOption(options, 'panelView');
        _.bindAll(this, 'open', 'close', '__handleWindowResize');
    },

    template: template,

    behaviors: {
        BlurableBehavior: {
            behaviorClass: BlurableBehavior,
            onBlur: 'close'
        }
    },

    className: 'popout',

    regions: {
        buttonRegion: '.js-button-region',
        panelRegion: '.js-panel-region'
    },

    ui: {
        button: '.js-button-region',
        panel: '.js-panel-region'
    },

    events: {
        'click @ui.button': '__handleClick'
    },

    /**
     * Contains an instance of <code>options.buttonView</code> if the popout is rendered, <code>null</code> otherwise.
     * */
    buttonView: null,

    /**
     * Contains an instance of <code>options.panelView</code> if the popout is open, <code>null</code> otherwise.
     * The view is created every time (!) the panel is triggered to open.
     * */
    panelView: null,

    onRender: function () {
        //noinspection JSValidateTypes
        this.isOpen = false;
        if (this.button) {
            this.stopListening(this.button);
        }
        this.button = new this.options.buttonView(_.result(this.options, 'buttonViewOptions'));
        this.buttonView = this.button;
        this.listenTo(this.button, 'all', function () {
            var args = slice.call(arguments);
            args[0] = 'button:' + args[0];
            this.triggerMethod.apply(this, args);
        });
        this.buttonRegion.show(this.button);

        if (this.options.popoutFlow === popoutFlow.LEFT) {
            this.ui.panel.addClass(classes.FLOW_LEFT);
            this.ui.panel.removeClass(classes.FLOW_RIGHT);
        } else {
            this.ui.panel.addClass(classes.FLOW_RIGHT);
            this.ui.panel.removeClass(classes.FLOW_LEFT);
        }
        if (this.options.customAnchor) {
            this.ui.button.addClass(classes.CUSTOM_ANCHOR);
        } else {
            this.ui.button.addClass(classes.DEFAULT_ANCHOR);
        }

        this.currentDirection = this.options.direction;
        this.updateDirectionClasses();
    },

    updatePanelFlow: function () {
        let offset = 0,
            isFlowRight = this.options.popoutFlow === popoutFlow.RIGHT;

        if (this.options.customAnchor && this.button.$anchor) {
            let anchor = this.button.$anchor;
            offset = isFlowRight ?
                anchor.offset().left + anchor.width() / 2 :
                anchor.offset().left + anchor.width() / 2 - this.panelRegion.$el.width();
        } else {
            let button = this.ui.button;
            offset = isFlowRight ?
                button.offset().left :
                button.offset().left + button.width() - this.panelRegion.$el.width();
        }
        
        this.panelRegion.$el.offset({
            left: offset
        });
    },

    updateDirectionClasses: function () {
        if (this.currentDirection === popoutDirection.UP) {
            this.ui.button.addClass(classes.DIRECTION_UP);
            this.ui.button.removeClass(classes.DIRECTION_DOWN);
<commit_msg>Improving popout panel old horizontal positioning logic<commit_after>/**
 * Developer: <NAME>
 * Date: 11/26/2014
 * Copyright: 2009-2016 Comindware®
 *       All Rights Reserved
 * Published under the MIT license
 */

"use strict";

import '../../libApi';
import { helpers } from '../../utils/utilsApi';
import WindowService from '../../services/WindowService';
import BlurableBehavior from '../../views/behaviors/BlurableBehavior';
import template from '../templates/popout.hbs';

const slice = Array.prototype.slice;

const classes = {
    OPEN: 'open',
    DIRECTION_UP: 'popout__up',
    DIRECTION_DOWN: 'popout__down',
    FLOW_LEFT: 'dev-popout-flow-left',
    FLOW_RIGHT: 'dev-popout-flow-right',
    CUSTOM_ANCHOR: 'popout__action-btn',
    DEFAULT_ANCHOR: 'popout__action'
};

const popoutFlow = {
    LEFT: 'left',
    RIGHT: 'right'
};

const popoutDirection = {
    UP: 'up',
    DOWN: 'down'
};

const height = {
    AUTO: 'auto',
    BOTTOM: 'bottom'
};

const defaultOptions = {
    popoutFlow: popoutFlow.LEFT,
    customAnchor: false,
    fade: false,
    height: 'auto',
    autoOpen: true,
    direction: popoutDirection.DOWN,
    renderAfterClose: true
};

/**
 * @name PopoutView
 * @memberof module:core.dropdown.views
 * @class Composite View that may be to display a dropdown panel as a speech bubble. Unlike {@link module:core.dropdown.views.DropdownView DropdownView},
 * the panel is displayed in speech bubble and has a triangle like in comics.
 * A dropdown view contains button and panel regions that can be fully customizable by the properties <code>buttonView</code> and <code>panelView</code>.
 * <ul>
 * <li>Button View is used for displaying a button. Click on that button trigger a panel to open.</li>
 * <li>Panel View is used to display a panel that drops down.</li>
 * </ul>
 * Panel width is fully determined by its layout and the <code>popoutFlow</code> option.
 * Panel height is determined by its layout and the <code>height</code> option.
 * A place where the panel appears depends on the <code>direction</code> and <code>popoutFlow</code> options.<br/>
 * Possible events:<ul>
 * <li><code>'before:open' (popoutView)</code> - fires before the panel has opened.</li>
 * <li><code>'open' (popoutView)</code> - fires after the panel has opened.</li>
 * <li><code>'before:close' (popoutView)</code> - fires before the panel has closed.</li>
 * <li><code>'close' (popoutView, ...)</code> - fires after the panel has closed.
 * If the panel was closed via <code>close(...)</code> method, the arguments of this method are transferred into this event.</li>
 * <li><code>'button:\*' </code> - all events the buttonView triggers are repeated by this view with 'button:' prefix.</li>
 * <li><code>'panel:\*' </code> - all events the panelView triggers are repeated by this view with 'panel:' prefix.</li>
 * </ul>
 * @constructor
 * @extends Marionette.LayoutView
 * @param {Object} options Options object.
 * @param {Marionette.View} options.buttonView View class for displaying the button.
 * @param {(Object|Function)} [options.buttonViewOptions] Options passed into the view on its creation.
 * @param {Marionette.View} options.panelView View class for displaying the panel. The view is created every time the panel is triggered to open.
 * @param {(Object|Function)} [options.panelViewOptions] Options passed into the view on its creation.
 * @param {Boolean} [options.autoOpen=true] Whether click on the button should trigger the panel to open.
 * @param {Boolean} [options.customAnchor=false] Whether to attach the speech bubble triangle (anchor) to a custom element in <code>buttonView</code>.
 *                                               The View passed into the <code>buttonView</code> option must implement
 *                                               @{link module:core.dropdown.views.behaviors.CustomAnchorBehavior CustomAnchorBehavior}.
 * @param {String} [options.direction='down'] Opening direction. Can be either: <code>'up'</code>, <code>'down'</code>.
 * @param {Boolean} [options.fade=false] Whether to dim the background when the panel is open.
 * @param {String} [options.height='auto'] A way of determining the panel height.
 *                                       <ul><li><code>'auto'</code> - is determined by panel's layout only.</li>
 *                                       <li><code>'bottom'</code> - the bottom border is fixed to the bottom of the window.</li></ul>
 * @param {String} [options.popoutFlow='left'] Panel's horizontal position.
 *                                       <ul><li><code>'left'</code> - The left border of the panel is attached to the left border of the button.
 *                                       The panel grows to the right.</li>
 *                                       <li><code>'right'</code> - The right border of the panel is attached to the right border of the button.
 *                                       The panel grows to the left.</li></ul>
 * @param {Boolean} [options.renderAfterClose=true] Whether to trigger button render when the panel has closed.
 * */

export default Marionette.LayoutView.extend(/** @lends module:core.dropdown.views.PopoutView.prototype */ {
    initialize: function (options) {
        _.defaults(this.options, defaultOptions);
        helpers.ensureOption(options, 'buttonView');
        helpers.ensureOption(options, 'panelView');
        _.bindAll(this, 'open', 'close', '__handleWindowResize');
    },

    template: template,

    behaviors: {
        BlurableBehavior: {
            behaviorClass: BlurableBehavior,
            onBlur: 'close'
        }
    },

    className: 'popout',

    regions: {
        buttonRegion: '.js-button-region',
        panelRegion: '.js-panel-region'
    },

    ui: {
        button: '.js-button-region',
        panel: '.js-panel-region'
    },

    events: {
        'click @ui.button': '__handleClick'
    },

    /**
     * Contains an instance of <code>options.buttonView</code> if the popout is rendered, <code>null</code> otherwise.
     * */
    buttonView: null,

    /**
     * Contains an instance of <code>options.panelView</code> if the popout is open, <code>null</code> otherwise.
     * The view is created every time (!) the panel is triggered to open.
     * */
    panelView: null,

    onRender: function () {
        //noinspection JSValidateTypes
        this.isOpen = false;
        if (this.button) {
            this.stopListening(this.button);
        }
        this.button = new this.options.buttonView(_.result(this.options, 'buttonViewOptions'));
        this.buttonView = this.button;
        this.listenTo(this.button, 'all', function () {
            var args = slice.call(arguments);
            args[0] = 'button:' + args[0];
            this.triggerMethod.apply(this, args);
        });
        this.buttonRegion.show(this.button);

        if (this.options.popoutFlow === popoutFlow.LEFT) {
            this.ui.panel.addClass(classes.FLOW_LEFT);
            this.ui.panel.removeClass(classes.FLOW_RIGHT);
        } else {
            this.ui.panel.addClass(classes.FLOW_RIGHT);
            this.ui.panel.removeClass(classes.FLOW_LEFT);
        }
        if (this.options.customAnchor) {
            this.ui.button.addClass(classes.CUSTOM_ANCHOR);
        } else {
            this.ui.button.addClass(classes.DEFAULT_ANCHOR);
        }

        this.currentDirection = this.options.direction;
        this.updateDirectionClasses();
    },

    updatePanelFlow: function () {
        var leftPos = 0,
            rightPos = 0,
            isFlowRight = this.options.popoutFlow === popoutFlow.RIGHT;

        if (this.options.customAnchor && this.button.$anchor) {
            let anchor = this.button.$anchor;
            if (isFlowRight) {
                rightPos = this.button.$el.width() - anchor.width() + anchor.width() / 2;
            } else {
                leftPos = anchor.width() / 2;
            }
        } else {
            let button = this.ui.button;
            if (isFlowRight) {
                leftPos = button.width();
            } else {
                rightPos = 0;
            }
        }

        if (isFlowRight) {
            this.panelRegion.$el.css({
                left: leftPos
            });
        } else {
            this.panelRegion.$el.css({
                right: rightPos
            });
        }
    },

    updateDirectionClasses: function () {
        if (this.currentDirection === popoutDirection.UP) {
            this.ui.button.addClass(classes.DIRECTION_UP);
            this.ui.button.removeClass(classes.DIRECTION_DOWN);

{'fraction_non_alphanumeric': 0.12649291051362005, 'fraction_numerical': 0.002588692122139201, 'mean_word_length': 2.5405123932514058, 'pattern_counts': {'":': 0, '<': 171, '<?xml version=': 0, '>': 171, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 34, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2215541', 'n_tokens_mistral': 4799, 'n_tokens_neox': 4689, 'n_words': 1379}