<commit_before>var navigation = navigation || {};
navigation.pinbar = navigation.pinbar || {};

navigation.pinbar.MainView = navigation.MainViewAbstract.extend({
    options: {
        maxItems: 10,
        tabTitle: 'Pinbar',
        tabIcon: 'icon-folder-close',
        el: '.pin-bar',
        listBar: '.list-bar',
        minimizeButton: '.top-action-box .minimize-button',
        closeButton: '.top-action-box .close-button',
        history: [],
        defaultUrl: '/',
        tabId: 'pinbar',
        collection: navigation.pinbar.Items
    },

    requireCleanup: true,
    massAdd: false,

    templates: {
        noItemsMessage: _.template($("#template-no-pins-message").html())
    },

    initialize: function() {
        this.$listBar = this.getBackboneElement(this.options.listBar);
        this.$minimizeButton = Backbone.$(this.options.minimizeButton);
        this.$closeButton = Backbone.$(this.options.closeButton);

        this.listenTo(this.options.collection, 'add', function(item) {this.setItemPosition(item)});
        this.listenTo(this.options.collection, 'remove', this.onPageClose);
        this.listenTo(this.options.collection, 'reset', this.addAll);
        this.listenTo(this.options.collection, 'all', this.render);

        this.listenTo(this.options.collection, 'positionChange', this.renderItem);
        this.listenTo(this.options.collection, 'stateChange', this.handleItemStateChange);
        this.listenTo(this.options.collection, 'urlChange', this.renderItem);

        this.$minimizeButton.click(_.bind(this.minimizePage, this));
        this.$closeButton.click(_.bind(this.closePage, this));

        this.registerTab();
        this.cleanup();
        this.render();
    },

    /**
     * Get previous maximized URL
     *
     * @return {*}
     */
    getLatestUrl: function() {
        if (this.options.history.length) {
            return _.last(this.options.history);
        } else {
            return this.options.defaultUrl;
        }
    },

    /**
     * Get backbone DOM element
     *
     * @param el
     * @return {*}
     */
    getBackboneElement: function(el) {
        return el instanceof Backbone.$ ? el : this.$(el);
    },

    /**
     * Handle item minimize/maximize state change
     *
     * @param item
     */
    handleItemStateChange: function(item) {
        if (!this.massAdd) {
            var url = null;
            if (item.get('maximized')) {
                url = item.get('url');
                this.removeFromHistory(item);
                this.options.history.push(url);
            } else {
                url = this.getLatestUrl();
            }
            /**
             * Add restore param to the url
             */
            if (Oro.hashNavigationEnabled() && !_.isUndefined(item.changed) && item.changed.restore) {
                var itemUrl = item.get('url');
                if (itemUrl.indexOf('?') !== -1) {
                    itemUrl += '&restore=1';
                } else {
                    itemUrl += '?restore=1';
                }
                item.set('url', itemUrl);
            }
            if (url != this.getCurrentPageItemData().url) {
                item.save(null, {success: function() {
                    Oro.Navigation.prototype.setLocation(url);
                }});
            }
        }
    },

    /**
     * Remove item from history
     *
     * @param item
     */
    removeFromHistory: function(item) {
        var idx = this.options.history.indexOf(item.get('url'));
        if (idx > -1) {
            this.options.history.splice(idx, 1);
        }
    },

    /**
     * Handle page close
     */
    onPageClose: function(item) {
        this.removeFromHistory(item);
        if (item.get('url') == this.getCurrentPageItemData().url) {
            this.goToLatestOpenedPage();
        } else {
            this.reorder();
        }
    },

    /**
     * Go to latest maximized page
     */
    goToLatestOpenedPage: function()
    {
        if (Oro.hashNavigationEnabled()) {
            Oro.Navigation.prototype.setLocation(this.getLatestUrl());
        } else {
            window.location.href = this.getLatestUrl();
        }
    },

    /**
     * Handle minimize page.
     *
     * @param e
     */
    minimizePage: function(e) {
        var pinnedItem = this.getItemForCurrentPage();
        if (pinnedItem.length) {
            _.each(pinnedItem, function(item) {
                this.removeFromHistory(item);
                item.set('maximized', false);
            }, this);
        } else {
            var currentItem = new navigation.pinbar.Item(this.getNewItemData(Backbone.$(e.currentTarget)));
            this.options.collection.unshift(currentItem);
            currentItem.save(null, {success: _.bind(this.handleItemStateChange, this)});
        }
    },

    /**
     * Handle click on page close button
     */
    closePage: function()
    {
        var pinnedItem = this.getItemForCurrentPage();
        if (pinnedItem.length) {
            _.each(pinnedItem, function(item) {item.destroy({wait: true});});
        } else {
            this.goToLatestOpenedPage();
        }
    },

    /**
     * Mass add items
     */
    addAll: function() {
        this.massAdd = true;
        this.markCurrentPageMaximized();
        this.options.collection.each(this.setItemPosition, this);
        this.massAdd = false;
    },

    /**
     * Mark current page as maximized to be able to minimize.
     */
    markCurrentPageMaximized: function()
    {
        var currentPageItems = this.getItemForCurrentPage();
        if (currentPageItems.length) {
            _.each(currentPageItems, function(item) {
                item.set('maximized', new Date().toISOString());
            });
        }
    },

    /**
     * Set item position if given or reorder items.
     *
     * @param {navigation.pinbar.Item} item
     * @param {Integer} position
     */
    setItemPosition: function(item, position) {
        if (_.isUndefined(position)) {
            this.reorder();
        } else {
            item.set({position: position});
        }
    },

    /**
     * Change position property of model based on current order
     */
    reorder: function() {
        this.options.collection.each(function(item, position) {
            item.set({position: position});
        });
    },

    /**
     * Choose container and add item to it.
     *
     * @param {navigation.pinbar.Item} item
     */
    renderItem: function(item) {
        var position = item.get('position');
        var type = position >= this.options.maxItems ? 'tab': 'list';

        if (item.get('display_type') != type) {
            this.cleanup();
            item.set('display_type', type);

            var view = new navigation.pinbar.ItemView({
                type: type,
                model: item
            });

            if (type == 'tab') {
                this.addItemToTab(view, !this.massAdd);
                /**
                 * Backbone event. Fired when tab is changed
                 * @event tab:changed
                 */
                Oro.Events.trigger("tab:changed", this.options.tabId);
            } else {
                var rowEl = view.render().el;
                if (this.massAdd || position > 0) {
                    this.$listBar.append(rowEl);
                } else {
                    this.$listBar.prepend(rowEl);
                }
            }
        }
    },

    /**
     * Checks if pinbar tab in 3 dots menu is used
     *
     * @return {Boolean}
     */
    needPinbarTab: function() {
        return (this.options.collection.length > this.options.maxItems);
    },

    /**
     * Clean up all pinbar items from menus
     */
    cleanup: function()
    {
        if (this.requireCleanup) {
            this.$listBar.empty();
            this.cleanupTab();
            this.requireCleanup = false;
        }
    },

    /**
     * Renders pinbar empty message if no items
     * Show/hide tabs section in ... menu on each event
     */
    render: function() {
        if (!this.massAdd) {
            if (this.options.collection.length == 0) {
                this.requireCleanup = true;
                this.$listBar.html(this.templates.noItemsMessage());
                /**
                 * Backbone event. Fired when pinbar help link is shown
                 * @event pinbar_help:shown
                 */
                Oro.Events.trigger("pinbar_help:shown");
            }

            this.checkTabContent();
            /**
             * Backbone event. Fired when tab is changed
             * @event tab:changed
             */
            Oro.Events.trigger("tab:changed", this.options.tabId);
        }
    }
});
<commit_msg>BAP-708: Pinbars reorder on page close<commit_after>var navigation = navigation || {};
navigation.pinbar = navigation.pinbar || {};

navigation.pinbar.MainView = navigation.MainViewAbstract.extend({
    options: {
        maxItems: 10,
        tabTitle: 'Pinbar',
        tabIcon: 'icon-folder-close',
        el: '.pin-bar',
        listBar: '.list-bar',
        minimizeButton: '.top-action-box .minimize-button',
        closeButton: '.top-action-box .close-button',
        history: [],
        defaultUrl: '/',
        tabId: 'pinbar',
        collection: navigation.pinbar.Items
    },

    requireCleanup: true,
    massAdd: false,

    templates: {
        noItemsMessage: _.template($("#template-no-pins-message").html())
    },

    initialize: function() {
        this.$listBar = this.getBackboneElement(this.options.listBar);
        this.$minimizeButton = Backbone.$(this.options.minimizeButton);
        this.$closeButton = Backbone.$(this.options.closeButton);

        this.listenTo(this.options.collection, 'add', function(item) {this.setItemPosition(item)});
        this.listenTo(this.options.collection, 'remove', this.onPageClose);
        this.listenTo(this.options.collection, 'reset', this.addAll);
        this.listenTo(this.options.collection, 'all', this.render);

        this.listenTo(this.options.collection, 'positionChange', this.renderItem);
        this.listenTo(this.options.collection, 'stateChange', this.handleItemStateChange);
        this.listenTo(this.options.collection, 'urlChange', this.renderItem);

        this.$minimizeButton.click(_.bind(this.minimizePage, this));
        this.$closeButton.click(_.bind(this.closePage, this));

        this.registerTab();
        this.cleanup();
        this.render();
    },

    /**
     * Get previous maximized URL
     *
     * @return {*}
     */
    getLatestUrl: function() {
        if (this.options.history.length) {
            return _.last(this.options.history);
        } else {
            return this.options.defaultUrl;
        }
    },

    /**
     * Get backbone DOM element
     *
     * @param el
     * @return {*}
     */
    getBackboneElement: function(el) {
        return el instanceof Backbone.$ ? el : this.$(el);
    },

    /**
     * Handle item minimize/maximize state change
     *
     * @param item
     */
    handleItemStateChange: function(item) {
        if (!this.massAdd) {
            var url = null;
            if (item.get('maximized')) {
                url = item.get('url');
                this.removeFromHistory(item);
                this.options.history.push(url);
            } else {
                url = this.getLatestUrl();
            }
            /**
             * Add restore param to the url
             */
            if (Oro.hashNavigationEnabled() && !_.isUndefined(item.changed) && item.changed.restore) {
                var itemUrl = item.get('url');
                if (itemUrl.indexOf('?') !== -1) {
                    itemUrl += '&restore=1';
                } else {
                    itemUrl += '?restore=1';
                }
                item.set('url', itemUrl);
            }
            if (url != this.getCurrentPageItemData().url) {
                item.save(null, {success: function() {
                    Oro.Navigation.prototype.setLocation(url);
                }});
            }
        }
    },

    /**
     * Remove item from history
     *
     * @param item
     */
    removeFromHistory: function(item) {
        var idx = this.options.history.indexOf(item.get('url'));
        if (idx > -1) {
            this.options.history.splice(idx, 1);
        }
    },

    /**
     * Handle page close
     */
    onPageClose: function(item) {
        this.removeFromHistory(item);
        if (item.get('url') == this.getCurrentPageItemData().url) {
            this.goToLatestOpenedPage();
        }
        this.reorder();
    },

    /**
     * Go to latest maximized page
     */
    goToLatestOpenedPage: function()
    {
        if (Oro.hashNavigationEnabled()) {
            Oro.Navigation.prototype.setLocation(this.getLatestUrl());
        } else {
            window.location.href = this.getLatestUrl();
        }
    },

    /**
     * Handle minimize page.
     *
     * @param e
     */
    minimizePage: function(e) {
        var pinnedItem = this.getItemForCurrentPage();
        if (pinnedItem.length) {
            _.each(pinnedItem, function(item) {
                this.removeFromHistory(item);
                item.set('maximized', false);
            }, this);
        } else {
            var currentItem = new navigation.pinbar.Item(this.getNewItemData(Backbone.$(e.currentTarget)));
            this.options.collection.unshift(currentItem);
            currentItem.save(null, {success: _.bind(this.handleItemStateChange, this)});
        }
    },

    /**
     * Handle click on page close button
     */
    closePage: function()
    {
        var pinnedItem = this.getItemForCurrentPage();
        if (pinnedItem.length) {
            _.each(pinnedItem, function(item) {item.destroy({wait: true});});
        } else {
            this.goToLatestOpenedPage();
        }
    },

    /**
     * Mass add items
     */
    addAll: function() {
        this.massAdd = true;
        this.markCurrentPageMaximized();
        this.options.collection.each(this.setItemPosition, this);
        this.massAdd = false;
    },

    /**
     * Mark current page as maximized to be able to minimize.
     */
    markCurrentPageMaximized: function()
    {
        var currentPageItems = this.getItemForCurrentPage();
        if (currentPageItems.length) {
            _.each(currentPageItems, function(item) {
                item.set('maximized', new Date().toISOString());
            });
        }
    },

    /**
     * Set item position if given or reorder items.
     *
     * @param {navigation.pinbar.Item} item
     * @param {Integer} position
     */
    setItemPosition: function(item, position) {
        if (_.isUndefined(position)) {
            this.reorder();
        } else {
            item.set({position: position});
        }
    },

    /**
     * Change position property of model based on current order
     */
    reorder: function() {
        this.options.collection.each(function(item, position) {
            item.set({position: position});
        });
    },

    /**
     * Choose container and add item to it.
     *
     * @param {navigation.pinbar.Item} item
     */
    renderItem: function(item) {
        var position = item.get('position');
        var type = position >= this.options.maxItems ? 'tab': 'list';

        if (item.get('display_type') != type) {
            this.cleanup();
            item.set('display_type', type);

            var view = new navigation.pinbar.ItemView({
                type: type,
                model: item
            });

            if (type == 'tab') {
                this.addItemToTab(view, !this.massAdd);
                /**
                 * Backbone event. Fired when tab is changed
                 * @event tab:changed
                 */
                Oro.Events.trigger("tab:changed", this.options.tabId);
            } else {
                var rowEl = view.render().el;
                if (this.massAdd || position > 0) {
                    this.$listBar.append(rowEl);
                } else {
                    this.$listBar.prepend(rowEl);
                }
            }
        }
    },

    /**
     * Checks if pinbar tab in 3 dots menu is used
     *
     * @return {Boolean}
     */
    needPinbarTab: function() {
        return (this.options.collection.length > this.options.maxItems);
    },

    /**
     * Clean up all pinbar items from menus
     */
    cleanup: function()
    {
        if (this.requireCleanup) {
            this.$listBar.empty();
            this.cleanupTab();
            this.requireCleanup = false;
        }
    },

    /**
     * Renders pinbar empty message if no items
     * Show/hide tabs section in ... menu on each event
     */
    render: function() {
        if (!this.massAdd) {
            if (this.options.collection.length == 0) {
                this.requireCleanup = true;
                this.$listBar.html(this.templates.noItemsMessage());
                /**
                 * Backbone event. Fired when pinbar help link is shown
                 * @event pinbar_help:shown
                 */
                Oro.Events.trigger("pinbar_help:shown");
            }

            this.checkTabContent();
            /**
             * Backbone event. Fired when tab is changed
             * @event tab:changed
             */
            Oro.Events.trigger("tab:changed", this.options.tabId);
        }
    }
});
<|endoftext|>


<commit_before>const chalk = require('chalk')
const inquirer = require('inquirer')
const cp = require('child_process')

const CLIENT_DIR = 'examples/react-client'

console.log(
  `You ran ${chalk.bold(
    'yarn start'
  )} from the project root. Maybe you meant to run it from ${chalk.bold(CLIENT_DIR)}?`
)

console.log()

const question = {
  type: 'confirm',
  name: 'shouldRunExample',
  message: chalk.yellow(`Would you like to run ${CLIENT_DIR} now?`),
  default: true
}

inquirer.prompt(question).then(answer => {
  console.log()

  if (answer.shouldRunExample) {
    console.log("Alright, I'll start the example for you now!")
    console.log()
    console.log(
      `Running: ${chalk.cyan(
        `yarn bootstrap ${chalk.gray('# set up dependencies for packages and examples')}`
      )}`
    )
    console.log()

    spawn('yarn', ['bootstrap']).then(() => {
      console.log()
      console.log(`Running: ${chalk.cyan(`cd ${CLIENT_DIR} && yarn start`)}`)
      console.log()
      console.log(`The server should be up momentarily. Remember you can always run the example with:
${chalk.cyan(`
  yarn bootstrap ${chalk.gray('# set up dependencies for packages and examples')}
  cd ${CLIENT_DIR}
  yarn start`)}
`)
      fork('./scripts/run-example', [], {})
        .then(() => console.log('Done!'))
        .catch(e => console.error('oops'))
    }).catch(e => console.error(e))
  } else {
    console.log(`Okay got it! Remember that you can run the example with:
${chalk.cyan(`
  yarn bootstrap ${chalk.gray('# set up dependencies for packages and examples')}
  cd ${CLIENT_DIR}
  yarn start`)}

Learn more about the examples here:

  https://github.com/mobx-little-router/mobx-little-router/tree/master/examples
`)
  }
})

function spawn(cmd, args) {
  return new Promise((res, rej) => {
    const spawned = cp.spawn(cmd, args)
    spawned.stdout.on('data', data => {
      process.stdout.write(chalk.gray(data))
    })

    spawned.stderr.on('data', data => {
      process.stdout.write(chalk.gray(data))
    })

    spawned.on('close', code => {
      if (code === 0) {
        spawned.stdin.end()
        spawned.kill()
        res()
      } else {
        rej()
      }
    })
  })
}

function fork(cmd, args, opts) {
  return new Promise((res, rej) => {
    const proc = cp.fork(cmd, args, opts)
    proc.on('close', code => {
      res()
    })
  })
}
<commit_msg>updates start script<commit_after>const chalk = require('chalk')
const inquirer = require('inquirer')
const cp = require('child_process')

const CLIENT_DIR = 'examples/react-client'

console.log(
  `You ran ${chalk.bold(
    'yarn start'
  )} from the project root. Maybe you meant to run it from ${chalk.bold(CLIENT_DIR)}?`
)

console.log()

inquirer.prompt({
  type: 'confirm',
  name: 'shouldRunExample',
  message: chalk.yellow(`Would you like to run ${CLIENT_DIR} now?`),
  default: true
}).then(answer => {
  console.log()

  if (answer.shouldRunExample) {
    console.log("Alright, I'll start the example for you now!")
    console.log()
    console.log(
      `Running: ${chalk.cyan(
        `yarn bootstrap ${chalk.gray('# set up dependencies for packages and examples')}`
      )}`
    )
    console.log()

    spawn('yarn', ['bootstrap']).then(() => {
      console.log()
      console.log(`Running: ${chalk.cyan(`cd ${CLIENT_DIR} && yarn start`)}`)
      console.log()
      console.log(`The server should be up momentarily. Remember you can always run the example with:
${chalk.cyan(`
  yarn bootstrap ${chalk.gray('# set up dependencies for packages and examples')}
  cd ${CLIENT_DIR}
  yarn start`)}
`)
      fork('./scripts/run-example', [], {})
        .then(() => console.log('Done!'))
        .catch(e => console.error('oops'))
    }).catch(e => console.error(e))
  } else {
    console.log(`Okay got it! Remember that you can run the example with:
${chalk.cyan(`
  yarn bootstrap ${chalk.gray('# set up dependencies for packages and examples')}
  cd ${CLIENT_DIR}
  yarn start`)}

Learn more about the examples here:

  https://github.com/mobx-little-router/mobx-little-router/tree/master/examples
`)
  }
})

function spawn(cmd, args) {
  return new Promise((res, rej) => {
    const spawned = cp.spawn(cmd, args)
    spawned.stdout.on('data', data => {
      process.stdout.write(chalk.gray(data))
    })

    spawned.stderr.on('data', data => {
      process.stdout.write(chalk.gray(data))
    })

    spawned.on('close', code => {
      if (code === 0) {
        spawned.stdin.end()
        spawned.kill()
        res()
      } else {
        rej()
      }
    })
  })
}

function fork(cmd, args, opts) {
  return new Promise((res, rej) => {
    const proc = cp.fork(cmd, args, opts)
    proc.on('close', code => {
      res()
    })
  })
}
<|endoftext|>


<commit_before>/*
 * Implements a user-facing modal confirmation when link has a
 * "data-confirm" attribute using bootstrap's modals. MIT license.
 *
 *   - vjt@openssl.it  Tue Jul  2 18:45:15 CEST 2013
 */
(function ($) {

  /**
   * Builds the markup for a [Bootstrap modal](http://twitter.github.io/bootstrap/javascript.html#modals)
   * for the given `element`. Uses the following `data-` parameters to
   * customize it:
   *
   *  * `data-confirm`: Contains the modal body text. HTML is allowed.
   *                    Separate multiple paragraphs using \n\n.
   *  * `data-commit`:  The 'confirm' button text. "Confirm" by default.
   *  * `data-cancel`:  The 'cancel' button text. "Cancel" by default.
   *  * `data-verify`:  Adds a text input in which the user has to input
   *                    the text in this attribute value for the 'confirm'
   *                    button to be clickable. Optional.
   *  * `data-verify-text`:  Adds a label for the data-verify input. Optional
   *  * `data-focus`:   Define focused input. Supported values are
   *                    'cancel' or 'commit', 'cancel' is default for
   *                    data-method DELETE, 'commit' for all others.
   *
   * You can set global setting using `dataConfirmModal.setDefaults`, for example:
   *
   *    dataConfirmModal.setDefaults({
   *      title: 'Confirm your action',
   *      commit: 'Continue',
   *      cancel: 'Cancel',
   *      fade:   false,
   *      verifyClass: 'form-control',
   *    });
   *
   */

  var defaults = {
    title: 'Are you sure?',
    commit: 'Confirm',
    commitClass: 'btn-danger',
    cancel: 'Cancel',
    cancelClass: 'btn-default',
    fade: true,
    verifyClass: 'form-control',
    elements: ['a[data-confirm]', 'button[data-confirm]', 'input[type=submit][data-confirm]'],
    focus: 'commit',
    zIndex: 1050,
    modalClass: false,
    modalCloseContent: '×',
    show: true
  };

  var settings;

  window.dataConfirmModal = {
    setDefaults: function (newSettings) {
      settings = $.extend(settings, newSettings);
    },

    restoreDefaults: function () {
      settings = $.extend({}, defaults);
    },

    confirm: function (options) {
      // Build an ephemeral modal
      //
      var modal = buildModal(options);

      modal.spawn();
      modal.on('hidden.bs.modal', function () {
        modal.remove();
      });

      modal.find('.commit').on('click', function () {
        if (options.onConfirm && options.onConfirm.call)
          options.onConfirm.call();

        modal.modal('hide');
      });

      modal.find('.cancel').on('click', function () {
        if (options.onCancel && options.onCancel.call)
          options.onCancel.call();

        modal.modal('hide');
      });
    }
  };

  dataConfirmModal.restoreDefaults();

  // Detect bootstrap version, or bail out.
  //
  if ($.fn.modal == undefined) {
    throw new Error("The bootstrap modal plugin does not appear to be loaded.");
  }

  if ($.fn.modal.Constructor == undefined) {
    throw new Error("The bootstrap modal plugin does not have a Constructor ?!?");
  }

  if ($.fn.modal.Constructor.VERSION == undefined) {
    throw new Error("The bootstrap modal plugin does not have its version defined ?!?");
  }

  var versionString = $.fn.modal.Constructor.VERSION;
  var match = versionString.match(/^(\d)\./);
  if (!match) {
    throw new Error("Cannot identify Bootstrap version. Version string: " + versionString);
  }

  var bootstrapVersion = parseInt(match[1]);
  if (bootstrapVersion != 3 && bootstrapVersion != 4) {
    throw new Error("Unsupported bootstrap version: " + bootstrapVersion + ". data-confirm-modal supports version 3 and 4.");
  }

  var buildElementModal = function (element) {
    var options = {
      title:             element.data('title') || element.attr('title') || element.data('original-title'),
      text:              element.data('confirm'),
      focus:             element.data('focus'),
      method:            element.data('method'),
      modalClass:        element.data('modal-class'),
      modalCloseContent: element.data('modal-close-content'),
      commit:            element.data('commit'),
      commitClass:       element.data('commit-class'),
      cancel:            element.data('cancel'),
      cancelClass:       element.data('cancel-class'),
      remote:            element.data('remote'),
      verify:            element.data('verify'),
      verifyRegexp:      element.data('verify-regexp'),
      verifyLabel:       element.data('verify-text'),
      verifyRegexpCaseInsensitive: element.data('verify-regexp-caseinsensitive'),
      backdrop:          element.data('backdrop'),
      keyboard:          element.data('keyboard'),
      show:              element.data('show')
    };

    var modal = buildModal(options);

    modal.find('.commit').on('click', function () {
      // Call the original event handler chain
      element.get(0).click();

      modal.modal('hide');
    });

    return modal;
  }

  var buildModal = function (options) {
    var id = 'confirm-modal-' + String(Math.random()).slice(2, -1);
    var fade = settings.fade ? 'fade' : '';
    var modalClass = options.modalClass ? options.modalClass : settings.modalClass;

    var modalCloseContent = options.modalCloseContent ? options.modalCloseContent : settings.modalCloseContent;
    var modalClose = '<button type="button" class="close" data-dismiss="modal" aria-hidden="true">'+modalCloseContent+'</button>'

    var modalTitle = '

 '
    var modalHeader;

    // Bootstrap 3 and 4 have different DOMs and different CSS. In B4, the
    // modalHeader is display:flex and the modalClose uses negative margins,
    // so it can stay after the modalTitle.
    //
    // In B3, the close button floats to the right, so it must stay before
    // the modalTitle.
    //
    switch (bootstrapVersion) {
    case 3:
      modalHeader = modalClose + modalTitle;
      break;
    case 4:
      modalHeader = modalTitle + modalClose;
      break;
    }

    var modal = $(
      '
' +
        '
' +
          '
' +
            '
' +
              modalHeader +
            '
' +
            '

' +
            '
' +
              '<button class="btn cancel" data-dismiss="modal" aria-hidden="true"></button>' +
              '<button class="btn commit"></button>' +
            '
'+
          '
'+
        '
'+
      '
'
    );

    // Make sure it's always the top zindex
    var highest, current;
    highest = current = settings.zIndex;
    $('.modal.in').not('#'+id).each(function() {
      current = parseInt($(this).css('z-index'), 10);
      if(current > highest) {
        highest = current
      }
    });
    modal.css('z-index', parseInt(highest) + 1);

    modal.find('.modal-title').text(options.title || settings.title);

    var body = modal.find('.modal-body');

    $.each((options.text||'').split(/\n{2}/), function (i, piece) {
      body.append($('
').html(piece));
    });

    var commit = modal.find('.commit');
    commit.text(options.commit || settings.commit);
    commit.addClass(options.commitClass || settings.commitClass);

    var cancel = modal.find('.cancel');
    cancel.text(options.cancel || settings.cancel);
    cancel.addClass(options.cancelClass || settings.cancelClass);

    if (options.remote) {
      commit.attr('data-dismiss', 'modal');
    }

    if (options.verify || options.verifyRegexp) {
      commit.prop('disabled', true);

      var isMatch;
      if (options.verifyRegexp) {
        var caseInsensitive = options.verifyRegexpCaseInsensitive;
        var re = new RegExp(options.verifyRegexp, caseInsensitive ? 'i' : '');

        isMatch = function (input) { return input.match(re) };
      } else if(options.stripAlpha) {
        isMatch = function (input) { return options.verify == input.replace(/[^\w\s]/gi, '') };
      } else {
        isMatch = function (input) { return options.verify == input };
      }

      var verification = $('<input/>', {"type": 'text', "class": settings.verifyClass}).on('keyup', function () {
        commit.prop('disabled', !isMatch($(this).val()));
      });

      modal.on('shown.bs.modal', function () {
        verification.focus();
      });

      modal.on('hidden.bs.modal', function () {
        verification.val('').trigger('keyup');
        modals = $(".modal.in");
        if(modals.length > 0 && !$("body").hasClass("modal-open")) {
          $('body').addClass('modal-open');
        }
      });

      if (options.verifyLabel)
        body.append($('
', {text: options.verifyLabel}))

      body.append(verification);
    }

    var focus_element;
    if (options.focus) {
      focus_element = options.focus;
    } else if (options.method == 'delete') {
      focus_element = 'cancel'
    } else {
      focus_element = settings.focus;
    }
    focus_element = modal.find('.' + focus_element);

    modal.on('shown.bs.modal', function () {
      focus_element.focus();
    });

    $('body').append(modal);

    modal.spawn = function() {
      return modal.modal($.extend({}, {
        backdrop: options.backdrop,
        keyboard: options.keyboard,
        show:     options.show
      }));
    };

    return modal;
  };

  /**
   * Returns a modal already built for the given element or builds a new one,
   * caching it into the element's `confirm-modal` data attribute.
   */
  $.fn.getConfirmModal = function () {
    var element = $(this), modal = element.data('confirm-modal');

    if (!modal) {
      modal = buildElementModal(element);
      element.data('confirm-modal', modal);
    }

    return modal;
  };

  $.fn.confirmModal = function () {
    var modal = $(this).getConfirmModal();

    modal.spawn();

    return modal;
  };

  if (window.Rails || $.rails) {
    /**
     * Attaches to Rails' UJS adapter's 'confirm' event, triggered on elements
     * having a `data-confirm` attribute set.
     *
     * If the modal is not visible, then it is spawned and the default Rails
     * confirmation dialog is canceled.
     *
     * If the modal is visible, it means the handler is being called by the
     * modal commit button click handler, as such the user has successfully
     * clicked on the confirm button. In this case Rails' confirm function
     * is briefly overriden, and afterwards reset when the modal is closed.
     *
     */
    var window_confirm = window.confirm;

    $(document).delegate(settings.elements.join(', '), 'confirm', function() {
      var modal = $(this).getConfirmModal();

      if (!modal.is(':visible')) {
        modal.spawn();

        // Cancel Rails' confirmation
        return false;

      } else {
        // Modal has been confirmed. Override Rails' handler
        window.confirm = function () {
          return true;
        }

        modal.one('hidden.bs.modal', function() {
          // Reset it after modal is closed.
          window.confirm = window_confirm;
          modals = $(".modal.in");
          if(modals.length > 0 && !$("body").hasClass("modal-open")) {
            $('body').addClass('modal-open');
          }
        });

        // Proceed with Rails' handlers
        return true;
      }
    });
  }

})(jQuery);
<commit_msg>Update data-confirm-modal.js<commit_after>/*
 * Implements a user-facing modal confirmation when link has a
 * "data-confirm" attribute using bootstrap's modals. MIT license.
 *
 *   - vjt@openssl.it  Tue Jul  2 18:45:15 CEST 2013
 */
(function ($) {

  /**
   * Builds the markup for a [Bootstrap modal](http://twitter.github.io/bootstrap/javascript.html#modals)
   * for the given `element`. Uses the following `data-` parameters to
   * customize it:
   *
   *  * `data-confirm`: Contains the modal body text. HTML is allowed.
   *                    Separate multiple paragraphs using \n\n.
   *  * `data-commit`:  The 'confirm' button text. "Confirm" by default.
   *  * `data-cancel`:  The 'cancel' button text. "Cancel" by default.
   *  * `data-verify`:  Adds a text input in which the user has to input
   *                    the text in this attribute value for the 'confirm'
   *                    button to be clickable. Optional.
   *  * `data-verify-text`:  Adds a label for the data-verify input. Optional
   *  * `data-focus`:   Define focused input. Supported values are
   *                    'cancel' or 'commit', 'cancel' is default for
   *                    data-method DELETE, 'commit' for all others.
   *
   * You can set global setting using `dataConfirmModal.setDefaults`, for example:
   *
   *    dataConfirmModal.setDefaults({
   *      title: 'Confirm your action',
   *      commit: 'Continue',
   *      cancel: 'Cancel',
   *      fade:   false,
   *      verifyClass: 'form-control',
   *    });
   *
   */

  var defaults = {
    title: 'Are you sure?',
    commit: 'Confirm',
    commitClass: 'btn-danger',
    cancel: 'Cancel',
    cancelClass: 'btn-default',
    fade: true,
    verifyClass: 'form-control',
    elements: ['a[data-confirm]', 'button[data-confirm]', 'input[type=submit][data-confirm]'],
    focus: 'commit',
    zIndex: 1050,
    modalClass: false,
    modalCloseContent: '×',
    show: true
  };

  var settings;

  window.dataConfirmModal = {
    setDefaults: function (newSettings) {
      settings = $.extend(settings, newSettings);
    },

    restoreDefaults: function () {
      settings = $.extend({}, defaults);
    },

    confirm: function (options) {
      // Build an ephemeral modal
      //
      var modal = buildModal(options);

      modal.spawn();
      modal.on('hidden.bs.modal', function () {
        modal.remove();
      });

      modal.find('.commit').on('click', function () {
        if (options.onConfirm && options.onConfirm.call)
          options.onConfirm.call();

        modal.modal('hide');
      });

      modal.find('.cancel').on('click', function () {
        if (options.onCancel && options.onCancel.call)
          options.onCancel.call();

        modal.modal('hide');
      });
    }
  };

  dataConfirmModal.restoreDefaults();

  // Detect bootstrap version, or bail out.
  //
  if ($.fn.modal == undefined) {
    throw new Error("The bootstrap modal plugin does not appear to be loaded.");
  }

  if ($.fn.modal.Constructor == undefined) {
    throw new Error("The bootstrap modal plugin does not have a Constructor ?!?");
  }

  if ($.fn.modal.Constructor.VERSION == undefined) {
    throw new Error("The bootstrap modal plugin does not have its version defined ?!?");
  }

  var versionString = $.fn.modal.Constructor.VERSION;
  var match = versionString.match(/^(\d)\./);
  if (!match) {
    throw new Error("Cannot identify Bootstrap version. Version string: " + versionString);
  }

  var bootstrapVersion = parseInt(match[1]);
  if (bootstrapVersion != 3 && bootstrapVersion != 4) {
    throw new Error("Unsupported bootstrap version: " + bootstrapVersion + ". data-confirm-modal supports version 3 and 4.");
  }

  var buildElementModal = function (element) {
    var options = {
      title:             element.data('title') || element.attr('title') || element.data('original-title'),
      text:              element.data('confirm'),
      focus:             element.data('focus'),
      method:            element.data('method'),
      modalClass:        element.data('modal-class'),
      modalCloseContent: element.data('modal-close-content'),
      commit:            element.data('commit'),
      commitClass:       element.data('commit-class'),
      cancel:            element.data('cancel'),
      cancelClass:       element.data('cancel-class'),
      remote:            element.data('remote'),
      verify:            element.data('verify'),
      verifyRegexp:      element.data('verify-regexp'),
      verifyLabel:       element.data('verify-text'),
      verifyRegexpCaseInsensitive: element.data('verify-regexp-caseinsensitive'),
      backdrop:          element.data('backdrop'),
      keyboard:          element.data('keyboard'),
      show:              element.data('show')
    };

    var modal = buildModal(options);

    modal.find('.commit').on('click', function () {
      // Call the original event handler chain
      element.get(0).click();

      modal.modal('hide');
    });

    return modal;
  }

  var buildModal = function (options) {
    var id = 'confirm-modal-' + String(Math.random()).slice(2, -1);
    var fade = settings.fade ? 'fade' : '';
    var modalClass = options.modalClass ? options.modalClass : settings.modalClass;

    var modalCloseContent = options.modalCloseContent ? options.modalCloseContent : settings.modalCloseContent;
    var modalClose = '<button type="button" class="close" data-dismiss="modal" aria-hidden="true">'+modalCloseContent+'</button>'

    var modalTitle = '

 '
    var modalHeader;

    // Bootstrap 3 and 4 have different DOMs and different CSS. In B4, the
    // modalHeader is display:flex and the modalClose uses negative margins,
    // so it can stay after the modalTitle.
    //
    // In B3, the close button floats to the right, so it must stay before
    // the modalTitle.
    //
    switch (bootstrapVersion) {
    case 3:
      modalHeader = modalClose + modalTitle;
      break;
    case 4:
      modalHeader = modalTitle + modalClose;
      break;
    }

    var modal = $(
      '
' +
        '
' +
          '
' +
            '
' +
              modalHeader +
            '
' +
            '

' +
            '
' +
              '<button class="btn cancel" data-dismiss="modal" aria-hidden="true"></button>' +
              '<button class="btn commit"></button>' +
            '
'+
          '
'+
        '
'+
      '
'
    );

    // Make sure it's always the top zindex
    var highest, current;
    highest = current = settings.zIndex;
    $('.modal.in').not('#'+id).each(function() {
      current = parseInt($(this).css('z-index'), 10);
      if(current > highest) {
        highest = current
      }
    });
    modal.css('z-index', parseInt(highest) + 1);

    modal.find('.modal-title').text(options.title || settings.title);

    var body = modal.find('.modal-body');

    $.each((options.text||'').split(/\n{2}/), function (i, piece) {
      body.append($('
').html(piece));
    });

    var commit = modal.find('.commit');
    commit.text(options.commit || settings.commit);
    commit.addClass(options.commitClass || settings.commitClass);

    var cancel = modal.find('.cancel');
    cancel.text(options.cancel || settings.cancel);
    cancel.addClass(options.cancelClass || settings.cancelClass);

    if (options.remote) {
      commit.attr('data-dismiss', 'modal');
    }

    if (options.verify || options.verifyRegexp) {
      commit.prop('disabled', true);

      var isMatch;
      if (options.verifyRegexp) {
        var caseInsensitive = options.verifyRegexpCaseInsensitive;
        var re = new RegExp(options.verifyRegexp, caseInsensitive ? 'i' : '');

        isMatch = function (input) { return input.match(re) };
      } else if(options.stripAlpha) {
        isMatch = function (input) { return options.verify == input.replace(/[^\w\s]/gi, '') };
      } else {
        isMatch = function (input) { return options.verify == input };
      }

      var verification = $('<input/>', {"type": 'text', "class": settings.verifyClass}).on('keyup', function () {
        commit.prop('disabled', !isMatch($(this).val()));
      });

      modal.on('shown.bs.modal', function () {
        verification.focus();
      });

      modal.on('hidden.bs.modal', function () {
        verification.val('').trigger('keyup');
        modals = $(".modal.in");
        console.log(modals);
        if(modals.length > 0 && !$("body").hasClass("modal-open")) {
          $('body').addClass('modal-open');
        }
      });

      if (options.verifyLabel)
        body.append($('
', {text: options.verifyLabel}))

      body.append(verification);
    }

    var focus_element;
    if (options.focus) {
      focus_element = options.focus;
    } else if (options.method == 'delete') {
      focus_element = 'cancel'
    } else {
      focus_element = settings.focus;
    }
    focus_element = modal.find('.' + focus_element);

    modal.on('shown.bs.modal', function () {
      focus_element.focus();
    });

    $('body').append(modal);

    modal.spawn = function() {
      return modal.modal($.extend({}, {
        backdrop: options.backdrop,
        keyboard: options.keyboard,
        show:     options.show
      }));
    };

    return modal;
  };

  /**
   * Returns a modal already built for the given element or builds a new one,
   * caching it into the element's `confirm-modal` data attribute.
   */
  $.fn.getConfirmModal = function () {
    var element = $(this), modal = element.data('confirm-modal');

    if (!modal) {
      modal = buildElementModal(element);
      element.data('confirm-modal', modal);
    }

    return modal;
  };

  $.fn.confirmModal = function () {
    var modal = $(this).getConfirmModal();

    modal.spawn();

    return modal;
  };

  if (window.Rails || $.rails) {
    /**
     * Attaches to Rails' UJS adapter's 'confirm' event, triggered on elements
     * having a `data-confirm` attribute set.
     *
     * If the modal is not visible, then it is spawned and the default Rails
     * confirmation dialog is canceled.
     *
     * If the modal is visible, it means the handler is being called by the
     * modal commit button click handler, as such the user has successfully
     * clicked on the confirm button. In this case Rails' confirm function
     * is briefly overriden, and afterwards reset when the modal is closed.
     *
     */
    var window_confirm = window.confirm;

    $(document).delegate(settings.elements.join(', '), 'confirm', function() {
      var modal = $(this).getConfirmModal();

      if (!modal.is(':visible')) {
        modal.spawn();

        // Cancel Rails' confirmation
        return false;

      } else {
        // Modal has been confirmed. Override Rails' handler
        window.confirm = function () {
          return true;
        }

        modal.one('hidden.bs.modal', function() {
          // Reset it after modal is closed.
          window.confirm = window_confirm;
          modals = $(".modal.in");
          if(modals.length > 0 && !$("body").hasClass("modal-open")) {
            $('body').addClass('modal-open');
          }
        });

        // Proceed with Rails' handlers
        return true;
      }
    });
  }

})(jQuery);
<|endoftext|>


<commit_before>'use strict';
var chai = require('chai');
var assert = chai.assert; //http://chaijs.com/api/assert/
var expect = chai.expect; //http://chaijs.com/api/bdd/#method_language-chains

var httpMocks = require('node-mocks-http'); //doc: https://github.com/howardabrams/node-mocks-http
var errorHandler = require('../../src/rest/errorHandler.js');

describe('rest', function() {

    var errorPages = {};
    before(function(done) {
        var fs = require('fs');
        fs.readFile(__dirname + '/../../src/html/404.html', 'utf8', function(err, html) {
            if (err) {
                errorPages.P404 = '';
                throw err;
            } else {
                errorPages.P404 = html;
            }
            done();
        });
    });

    describe('errorHandler',
        function() {
            describe('InternalServerError', function() {
                it('is defined', function() {
                    //used for filesystem operation etc
                    assert.isDefined(errorHandler.InternalServerError);
                    assert.isFunction(errorHandler.InternalServerError);
                });

                it('throws error if response parametr is empty', function() {
                    var fail = function() {
                        errorHandler.InternalServerError(Error('lorem ipsum'), null);
                    };

                    assert.throws(fail, Error);
                });

                it('should not fail with a defect response parameter', function() {
                    var fail = function() {
                        errorHandler.InternalServerError(Error('lorem ipsum'), {
                            'some': 'data'
                        });
                    };
                    expect(fail).to.not.throw(Error);
                });

                it('do fail if err parameter is missing', function() {
                    var fail = function() {
                        var response = httpMocks.createResponse();
                        errorHandler.InternalServerError(null, response);
                    };
                    assert.throws(fail, Error);
                    expect(fail).to.throw(Error);
                });

                it('sending response with errorcode 500', function() {
                    var response = httpMocks.createResponse();
                    errorHandler.InternalServerError(Error('lorem ipsum'), response);
                    expect(response.statusCode).to.equal(500);
                });

                it('sending response with defined error message ', function() {
                    var response = httpMocks.createResponse();
                    errorHandler.InternalServerError(Error('lorem ipsum'), response);
                    expect(response._getData()).to.include('lorem ipsum');
                });
            }); //500

            describe('DataNotFound', function() {
                it('is defined', function() {
                    //used for filesystem operation etc
                    assert.isDefined(errorHandler.DataNotFound);
                    assert.isFunction(errorHandler.DataNotFound);
                });

                it('do fail if response parameter is empty', function() {
                    var fail = function() {
                        errorHandler.DataNotFound(Error('lorem ipsum'), null);
                    };
                    assert.throws(fail, Error);
                    expect(fail).to.throw(Error);
                });

                it('do fail if err parameter is missing', function() {
                    var fail = function() {
                        var response = httpMocks.createResponse();
                        errorHandler.DataNotFound(null, response);
                    };
                    assert.throws(fail, Error);
                    expect(fail).to.throw(Error);
                });

                it('sending response with errorcode 404', function(done) {

                    var response = httpMocks.createResponse({
                        eventEmitter: require('events').EventEmitter
                    });
                    //capture the send event to see the switched status
                    response.on('send', () => {
                        expect(response.statusCode).to.equal(404);
                        done();
                    });
                    //do the call
                    errorHandler.DataNotFound(Error('lorem ipsum'), response);
                    //it is also possible to wait a specific time
                    /*setTimeout(function () {
                        expect(response.statusCode).to.equal(404);
                        done();
                    }, 1000);*/
                });

                it('sending response is the 404.html', function(done) {
                    var response = httpMocks.createResponse({
                        eventEmitter: require('events').EventEmitter
                    });
                    //capture the send event to see the switched status
                    response.on('send', () => {
                        expect(response._getData()).to.include(errorPages.P404);
                        done();
                    });
                    errorHandler.DataNotFound(Error('lorem ipsum'), response);
                });
            }); //500

        });
});
<commit_msg>remove new inline expression because of issues on travisci<commit_after>'use strict';
var chai = require('chai');
var assert = chai.assert; //http://chaijs.com/api/assert/
var expect = chai.expect; //http://chaijs.com/api/bdd/#method_language-chains

var httpMocks = require('node-mocks-http'); //doc: https://github.com/howardabrams/node-mocks-http
var errorHandler = require('../../src/rest/errorHandler.js');

describe('rest', function () {

    var errorPages = {};
    before(function (done) {
        var fs = require('fs');
        fs.readFile(__dirname + '/../../src/html/404.html', 'utf8', function (err, html) {
            if (err) {
                errorPages.P404 = '';
                throw err;
            } else {
                errorPages.P404 = html;
            }
            done();
        });
    });

    describe('errorHandler',
        function () {
            describe('InternalServerError', function () {
                it('is defined', function () {
                    //used for filesystem operation etc
                    assert.isDefined(errorHandler.InternalServerError);
                    assert.isFunction(errorHandler.InternalServerError);
                });

                it('throws error if response parametr is empty', function () {
                    var fail = function () {
                        errorHandler.InternalServerError(Error('lorem ipsum'), null);
                    };

                    assert.throws(fail, Error);
                });

                it('should not fail with a defect response parameter', function () {
                    var fail = function () {
                        errorHandler.InternalServerError(Error('lorem ipsum'), {
                            'some': 'data'
                        });
                    };
                    expect(fail).to.not.throw(Error);
                });

                it('do fail if err parameter is missing', function () {
                    var fail = function () {
                        var response = httpMocks.createResponse();
                        errorHandler.InternalServerError(null, response);
                    };
                    assert.throws(fail, Error);
                    expect(fail).to.throw(Error);
                });

                it('sending response with errorcode 500', function () {
                    var response = httpMocks.createResponse();
                    errorHandler.InternalServerError(Error('lorem ipsum'), response);
                    expect(response.statusCode).to.equal(500);
                });

                it('sending response with defined error message ', function () {
                    var response = httpMocks.createResponse();
                    errorHandler.InternalServerError(Error('lorem ipsum'), response);
                    expect(response._getData()).to.include('lorem ipsum');
                });
            }); //500

            describe('DataNotFound', function () {
                it('is defined', function () {
                    //used for filesystem operation etc
                    assert.isDefined(errorHandler.DataNotFound);
                    assert.isFunction(errorHandler.DataNotFound);
                });

                it('do fail if response parameter is empty', function () {
                    var fail = function () {
                        errorHandler.DataNotFound(Error('lorem ipsum'), null);
                    };
                    assert.throws(fail, Error);
                    expect(fail).to.throw(Error);
                });

                it('do fail if err parameter is missing', function () {
                    var fail = function () {
                        var response = httpMocks.createResponse();
                        errorHandler.DataNotFound(null, response);
                    };
                    assert.throws(fail, Error);
                    expect(fail).to.throw(Error);
                });

                it('sending response with errorcode 404', function (done) {

                    var response = httpMocks.createResponse({
                        eventEmitter: require('events').EventEmitter
                    });
                    //capture the send event to see the switched status
                    response.on('send', function () {
                        expect(response.statusCode).to.equal(404);
                        done();
                    });
                    //do the call
                    errorHandler.DataNotFound(Error('lorem ipsum'), response);
                    //it is also possible to wait a specific time
                    /*setTimeout(function () {
                        expect(response.statusCode).to.equal(404);
                        done();
                    }, 1000);*/
                });

                it('sending response is the 404.html', function (done) {
                    var response = httpMocks.createResponse({
                        eventEmitter: require('events').EventEmitter
                    });
                    //capture the send event to see the switched status
                    response.on('send', function () {
                        expect(response._getData()).to.include(errorPages.P404);
                        done();
                    });
                    errorHandler.DataNotFound(Error('lorem ipsum'), response);
                });
            }); //500

        });
});
<|endoftext|>


<commit_before>! function() {
    'use strict';

    var his = window.history
    var loc = window.location
    var ctxes = [] // Context queue
    var lts = [] // Routed listeners
    var mds = [] // Middlewares
    var evt = 'hashchange'

    /**
     *  Route action type
     */
    var T_BOOT = 'boot'
    var T_MANUAL = 'manual'
    var T_ROUTE = 'route'
    var T_REPLACE = 'replace'
    // var T_BACK = 'back'
    // var T_FORWARD = 'forward'

    /**
     *  private state
     */
    var inited = 0

    /**
     *  Will be called when the first time to call "Routed(observers)"
     */
    function boot() {
        inited = 1
        bindCtx(T_BOOT, function(ctx) {
            ctx.next = hash()
            ctx.curr = ''
            ctx.prev = ''
            dispatch({
                newURL: loc.href
            })
        })()
    }

    /**
     *  Routed API Method
     *  @param ob <Function> "ob" will be called when boot or hash has change
     */
    function R(ob) {
        lts.push(ob)
        if (!inited) boot()
    }
    /**
     *  e.g: http://github.com#profile ==> #profile
     */
    function hashFromUrl(url) {
        return (url || '').split('#')[1] || ''
    }
    /**
     *  Remove the start '#' char of hash string
     */
    function hash() {
        return loc.hash.replace(/^#/, '')
    }
    /**
     *  Emit all routed listener
     */
    function emit(ctx) {
        for (var i = 0; i < lts.length; i++) {
            lts[i].call(ctx, ctx)
        }
    }
    /**
     *  Call middleware in sequence and return the final result
     */
    function middleware(r) {
        var result
        for (var i = 0; i < mds.length; i++) {
            result = mds[i](r)
            if (result === false) break
        }
        return result
    }

    function validate(ctx, n, p) {
        return ctx.curr === p && ctx.next === n
    }

    /**
     *  Call when hashchange trigger or initial boot
     */
    function dispatch(e) {
        var prev = hashFromUrl(e.oldURL)
        var next = hashFromUrl(e.newURL)
        var ctx = ctxes[0]
        var legal

        if (!ctx || !(legal = validate(ctx, next, prev))) {
            ctx = new Context({
                type: T_MANUAL, // route without hook through this branch
                prev: prev
            })
        }
        ctx && legal && ctxes.shift()
        ctx.path = next
        delete ctx.next
        delete ctx.curr

        // if any middleware return false, will stop call emit
        if (middleware(ctx) !== false) emit(ctx)
    }

    function Context(opt) {
        this.type = opt.type // boot | route | manual | replace
        this.prev = opt.prev
    }

    /**
     *  Push the context instance to the queue for next dispatch handler to get it
     */
    function bindCtx(t, next) {
        return function() {
            var ctx = new Context({
                type: t,
                prev: hash()
            })
            ctxes.push(ctx)
            var args = [].slice.call(arguments)
            args.unshift(ctx)
            next.apply(this, args)
        }
    }

    R.use = function(md) {
        mds.push(md)
    }

    /**
     *  Need hook to native method
     */
    var ntb = his.back
    var ntf = his.forward
    R.back = his.back = function() {
        ntb.apply(his, arguments)
    }
    R.forward = his.forward = function() {
        ntf.apply(his, arguments)
    }
    R.route = bindCtx(T_ROUTE, function(ctx, p) {
        p = hashFromUrl(p)
        ctx.next = p
        ctx.curr = hash()
        loc.hash = p
    })
    /**
     *  Cannot assign 'replace' of 'Location' in no-inline script
     *  Please use `location.replace = Routed.replace` in inline script
     */
    var ntr = loc.replace
    R.replace = bindCtx(T_REPLACE, function(ctx, p) {
        p = hashFromUrl(p)
        ctx.next = p
        ctx.curr = hash()
        ntr.call(loc, '#' + p)
    })
    /**
     *  Support IE
     */
    window.addEventListener ? window.addEventListener(evt, dispatch, false) : win.attachEvent('on' + evt, dispatch)

    /**
     *  Global namespace is "Routed"
     */
    if (typeof exports !== 'undefined') {
        if (typeof module !== 'undefined' && module.exports) module.exports = R
        else exports.Routed = R
    } else window.Routed = R

}();
<commit_msg>expose current context<commit_after>/** Routed v0.0.2 by switer **/
! function() {
    'use strict';

    var his = window.history
    var loc = window.location
    var ctxes = [] // Context queue
    var lts = [] // Routed listeners
    var mds = [] // Middlewares
    var evt = 'hashchange'
    var current // Save last context

    /**
     *  Route action type
     */
    var T_BOOT = 'boot'
    var T_MANUAL = 'manual'
    var T_ROUTE = 'route'
    var T_REPLACE = 'replace'
    var T_UNKNOW = 'unknow'
    // var T_BACK = 'back'
    // var T_FORWARD = 'forward'

    /**
     *  private state
     */
    var inited = 0

    /**
     *  Will be called when the first time to call "Routed(observers)"
     */
    function boot() {
        inited = 1
        bindCtx(T_BOOT, function(ctx) {
            ctx.next = hash()
            ctx.curr = ''
            ctx.prev = ''
            dispatch({
                newURL: loc.href
            })
        })()
    }

    /**
     *  Routed API Method
     *  @param ob <Function> "ob" will be called when boot or hash has change
     */
    function R(ob) {
        lts.push(ob)
        if (!inited) boot()
    }
    /**
     *  e.g: http://github.com#profile ==> #profile
     */
    function hashFromUrl(url) {
        return /#/.test(url)? url.split('#')[1] || '' : url
    }
    /**
     *  Remove the start '#' char of hash string
     */
    function hash() {
        return loc.hash.replace(/^#/, '')
    }
    /**
     *  Emit all routed listener
     */
    function emit(ctx) {
        for (var i = 0; i < lts.length; i++) {
            lts[i].call(ctx, ctx)
        }
    }
    /**
     *  Call middleware in sequence and return the final result
     */
    function middleware(r) {
        var result
        for (var i = 0; i < mds.length; i++) {
            result = mds[i](r)
            if (result === false) break
        }
        return result
    }

    function validate(ctx, n, p) {
        return ctx.curr === p && ctx.next === n
    }

    /**
     *  Call when hashchange trigger or initial boot
     */
    function dispatch(e) {
        var prev = hashFromUrl(e.oldURL)
        var next = hashFromUrl(e.newURL)
        var ctx = ctxes[0]
        var legal

        if (!ctx || !(legal = validate(ctx, next, prev))) {
            ctx = new Context({
                type: T_MANUAL, // route without hook through this branch
                prev: prev
            })
        }
        ctx && legal && ctxes.shift()
        ctx.path = next
        delete ctx.next
        delete ctx.curr

        current = ctx
        // if any middleware return false, will stop call emit
        if (middleware(ctx) !== false) emit(ctx)
    }

    function Context(opt) {
        this.type = opt.type // boot | route | manual | replace
        this.prev = opt.prev
    }

    /**
     *  Push the context instance to the queue for next dispatch handler to get it
     */
    function bindCtx(t, next) {
        return function() {
            var ctx = new Context({
                type: t,
                prev: hash()
            })
            ctxes.push(ctx)
            var args = [].slice.call(arguments)
            args.unshift(ctx)
            next.apply(this, args)
        }
    }
    R.current = function () {
        return current || middleware({
            type: T_UNKNOW,
            prev: null,
            path: hash()
        })
    }
    R.use = function(md) {
        mds.push(md)
    }

    /**
     *  Need hook to native method
     */
    var ntb = his.back
    var ntf = his.forward
    R.back = his.back = function() {
        ntb.apply(his, arguments)
    }
    R.forward = his.forward = function() {
        ntf.apply(his, arguments)
    }
    R.route = bindCtx(T_ROUTE, function(ctx, p) {
        p = hashFromUrl(p)
        ctx.next = p
        ctx.curr = hash()
        loc.hash = p
    })
    /**
     *  Cannot assign 'replace' of 'Location' in no-inline script
     *  Please use `location.replace = Routed.replace` in inline script
     */
    var ntr = loc.replace
    R.replace = bindCtx(T_REPLACE, function(ctx, p) {
        p = hashFromUrl(p)
        ctx.next = p
        ctx.curr = hash()
        ntr.call(loc, '#' + p)
    })
    /**
     *  Support IE
     */
    window.addEventListener ? window.addEventListener(evt, dispatch, false) : window.attachEvent('on' + evt, dispatch)

    /**
     *  Global namespace is "Routed"
     */
    if (typeof exports !== 'undefined') {
        if (typeof module !== 'undefined' && module.exports) module.exports = R
        else exports.Routed = R
    } else window.Routed = R

}();<|endoftext|>


<commit_before>
/* Event-rendering methods for the TimeGrid class
----------------------------------------------------------------------------------------------------------------------*/

$.extend(TimeGrid.prototype, {

	segs: null, // segment objects rendered in the component. null of events haven't been rendered yet
	eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements

	// Renders the events onto the grid and returns an array of segments that have been rendered
	renderEvents: function(events) {
		var res = this.renderEventTable(events);

		this.eventSkeletonEl = $('
').append(res.tableEl);
		this.el.append(this.eventSkeletonEl);

		this.segs = res.segs;
	},

	// Retrieves rendered segment objects
	getSegs: function() {
		return this.segs || [];
	},

	// Removes all event segment elements from the view
	destroyEvents: function() {
		Grid.prototype.destroyEvents.call(this); // call the super-method

		if (this.eventSkeletonEl) {
			this.eventSkeletonEl.remove();
			this.eventSkeletonEl = null;
		}

		this.segs = null;
	},

	// Renders and returns the
 portion of the event-skeleton.
	// Returns an object with properties 'tbodyEl' and 'segs'.
	renderEventTable: function(events) {
		var tableEl = $('

');
		var trEl = tableEl.find('tr');
		var segs = this.eventsToSegs(events);
		var segCols;
		var i, seg;
		var col, colSegs;
		var containerEl;

		segs = this.renderSegs(segs); // returns only the visible segs
		segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg

		this.computeSegVerticals(segs); // compute and assign top/bottom

		for (col = 0; col < segCols.length; col++) { // iterate each column grouping
			colSegs = segCols[col];
			placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array

			containerEl = $('
');

			// assign positioning CSS and insert into container
			for (i = 0; i < colSegs.length; i++) {
				seg = colSegs[i];
				seg.el.css(this.generateSegPositionCss(seg));

				// if the height is short, add a className for alternate styling
				if (seg.bottom - seg.top < 30) {
					seg.el.addClass('fc-short');
				}

				containerEl.append(seg.el);
			}

			trEl.append($('').append(containerEl));
		}

		this.bookendCells(trEl, 'eventSkeleton');

		return  {
			tableEl: tableEl,
			segs: segs
		};
	},

	// Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom.
	updateSegVerticals: function() {
		var segs = this.segs;
		var i;

		if (segs) {
			this.computeSegVerticals(segs);

			for (i = 0; i < segs.length; i++) {
				segs[i].el.css(
					this.generateSegVerticalCss(segs[i])
				);
			}
		}
	},

	// For each segment in an array, computes and assigns its top and bottom properties
	computeSegVerticals: function(segs) {
		var i, seg;

		for (i = 0; i < segs.length; i++) {
			seg = segs[i];
			seg.top = this.computeDateTop(seg.start, seg.start);
			seg.bottom = this.computeDateTop(seg.end, seg.start);
		}
	},

	// Renders the HTML for a single event segment's default rendering
	renderSegHtml: function(seg, disableResizing) {
		var view = this.view;
		var event = seg.event;
		var isDraggable = view.isEventDraggable(event);
		var isResizable = !disableResizing && seg.isEnd && view.isEventResizable(event);
		var classes = this.getSegClasses(seg, isDraggable, isResizable);
		var skinCss = this.getEventSkinCss(event);
		var timeText;
		var fullTimeText; // more verbose time text. for the print stylesheet
		var startTimeText; // just the start time text
		var title = view.trigger("determineEventTitle", this, event) || '';
    var confirmationClass = view.trigger("determineEventConfirmationClass", this, event) || '';
    var stateClass = view.trigger("determineEventStateClass", this, event) || '';
    var eventNotes = view.trigger("determineEventNotes", this, event) || '';
		var stateText =  view.trigger("determineEventStateText", this, event) || '';
		classes.unshift('fc-time-grid-event');

		if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day...
			// Don't display time text on segments that run entirely through a day.
			// That would appear as midnight-midnight and would look dumb.
			// Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am)
			if (seg.isStart || seg.isEnd) {
				timeText = view.getEventTimeText(seg.start, seg.end);
				fullTimeText = view.getEventTimeText(seg.start, seg.end, 'LT');
				startTimeText = view.getEventTimeText(seg.start, null);
			}
		} else {
			// Display the normal time text for the *event's* times
			timeText = view.getEventTimeText(event);
			fullTimeText = view.getEventTimeText(event, 'LT');
			startTimeText = view.getEventTimeText(event.start, null);
		}

		return '
' +
				'
' +
				'
' +
					(timeText ?
						'
' +
							'' + htmlEscape(startTimeText) + '' +
						'
' :
						''
						) +
						'' +
								title +
						'' +
						'
' + stateText + '
'+
						''+
					'
' +
					'
' +
            eventNotes +
					'
' +
				'
' +
				'
' +
				(isResizable ?
					'
' :
					''
					) +
			'
';
	},

	// Generates an object with CSS properties/values that should be applied to an event segment element.
	// Contains important positioning-related properties that should be applied to any event element, customized or not.
	generateSegPositionCss: function(seg) {
		var view = this.view;
		var isRTL = view.opt('isRTL');
		var shouldOverlap = view.opt('slotEventOverlap');
		var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point
		var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point
		var props = this.generateSegVerticalCss(seg); // get top/bottom first
		var left; // amount of space from left edge, a fraction of the total width
		var right; // amount of space from right edge, a fraction of the total width

		if (shouldOverlap) {
			// double the width, but don't go beyond the maximum forward coordinate (1.0)
			forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2);
		}

		if (isRTL) {
			left = 1 - forwardCoord;
			right = backwardCoord;
		}
		else {
			left = backwardCoord;
			right = 1 - forwardCoord;
		}

		props.zIndex = seg.level + 1; // convert from 0-base to 1-based
		props.left = left * 100 + '%';
		props.right = right * 100 + '%';

		if (shouldOverlap && seg.forwardPressure) {
			// add padding to the edge so that forward stacked events don't cover the resizer's icon
			props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width
		}

		return props;
	},

	// Generates an object with CSS properties for the top/bottom coordinates of a segment element
	generateSegVerticalCss: function(seg) {
		return {
			top: seg.top,
			bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container
		};
	},

	// Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
	groupSegCols: function(segs) {
		var view = this.view;
		var segCols = [];
		var i;

		for (i = 0; i < view.colCnt; i++) {
			segCols.push([]);
		}

		for (i = 0; i < segs.length; i++) {
			segCols[segs[i].col].push(segs[i]);
		}

		return segCols;
	}

});

// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each.
// Also reorders the given array by date!
function placeSlotSegs(segs) {
	var levels;
	var level0;
	var i;

	segs.sort(compareSegs); // order by date
	levels = buildSlotSegLevels(segs);
	computeForwardSlotSegs(levels);

	if ((level0 = levels[0])) {

		for (i = 0; i < level0.length; i++) {
			computeSlotSegPressures(level0[i]);
		}

		for (i = 0; i < level0.length; i++) {
			computeSlotSegCoords(level0[i], 0, 0);
		}
	}
}

// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is
// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date.
function buildSlotSegLevels(segs) {
	var levels = [];
	var i, seg;
	var j;

	for (i=0; i<segs.length; i++) {
		seg = segs[i];

		// go through all the levels and stop on the first level where there are no collisions
		for (j=0; j<levels.length; j++) {
			if (!computeSlotSegCollisions(seg, levels[j]).length) {
				break;
			}
		}

		seg.level = j;

		(levels[j] || (levels[j] = [])).push(seg);
	}

	return levels;
}

// For every segment, figure out the other segments that are in subsequent
// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs
function computeForwardSlotSegs(levels) {
	var i, level;
	var j, seg;
	var k;

	for (i=0; i<levels.length; i++) {
		level = levels[i];

		for (j=0; j<level.length; j++) {
			seg = level[j];

			seg.forwardSegs = [];
			for (k=i+1; k<levels.length; k++) {
				computeSlotSegCollisions(seg, levels[k], seg.forwardSegs);
			}
		}
	}
}

// Figure out which path forward (via seg.forwardSegs) results in the longest path until
// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure
function computeSlotSegPressures(seg) {
	var forwardSegs = seg.forwardSegs;
	var forwardPressure = 0;
	var i, forwardSeg;

	if (seg.forwardPressure === undefined) { // not already computed

		for (i=0; i<forwardSegs.length; i++) {
			forwardSeg = forwardSegs[i];

			// figure out the child's maximum forward path
			computeSlotSegPressures(forwardSeg);

			// either use the existing maximum, or use the child's forward pressure
			// plus one (for the forwardSeg itself)
			forwardPressure = Math.max(
				forwardPressure,
				1 + forwardSeg.forwardPressure
			);
		}

		seg.forwardPressure = forwardPressure;
	}
}

// Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range
// from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and
// seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left.
//
// The segment might be part of a "series", which means consecutive segments with the same pressure
// who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of
// segments behind this one in the current series, and `seriesBackwardCoord` is the starting
// coordinate of the first segment in the series.
function computeSlotSegCoords(seg, seriesBackwardPressure, seriesBackwardCoord) {
	var forwardSegs = seg.forwardSegs;
	var i;

	if (seg.forwardCoord === undefined) { // not already computed

		if (!forwardSegs.length) {

			// if there are no forward segments, this segment should butt up against the edge
			seg.forwardCoord = 1;
		}
		else {

			// sort highest pressure first
			forwardSegs.sort(compareForwardSlotSegs);

			// this segment's forwardCoord will be calculated from the backwardCoord of the
			// highest-pressure forward segment.
			computeSlotSegCoords(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);
			seg.forwardCoord = forwardSegs[0].backwardCoord;
		}

		// calculate the backwardCoord from the forwardCoord. consider the series
		seg.backwardCoord = seg.forwardCoord -
			(seg.forwardCoord - seriesBackwardCoord) / // available width for series
			(seriesBackwardPressure + 1); // # of segments in the series

		// use this segment's coordinates to computed the coordinates of the less-pressurized
		// forward segments
		for (i=0; i<forwardSegs.length; i++) {
			computeSlotSegCoords(forwardSegs[i], 0, seg.forwardCoord);
		}
	}
}

// Find all the segments in `otherSegs` that vertically collide with `seg`.
// Append into an optionally-supplied `results` array and return.
function computeSlotSegCollisions(seg, otherSegs, results) {
	results = results || [];

	for (var i=0; i<otherSegs.length; i++) {
		if (isSlotSegCollision(seg, otherSegs[i])) {
			results.push(otherSegs[i]);
		}
	}

	return results;
}

// Do these segments occupy the same vertical space?
function isSlotSegCollision(seg1, seg2) {
	return seg1.bottom > seg2.top && seg1.top < seg2.bottom;
}

// A cmp function for determining which forward segment to rely on more when computing coordinates.
function compareForwardSlotSegs(seg1, seg2) {
	// put higher-pressure first
	return seg2.forwardPressure - seg1.forwardPressure ||
		// put segments that are closer to initial edge first (and favor ones with no coords yet)
		(seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
		// do normal sorting...
		compareSegs(seg1, seg2);
}
<commit_msg>remove fc-short class for agenda views<commit_after>
/* Event-rendering methods for the TimeGrid class
----------------------------------------------------------------------------------------------------------------------*/

$.extend(TimeGrid.prototype, {

	segs: null, // segment objects rendered in the component. null of events haven't been rendered yet
	eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements

	// Renders the events onto the grid and returns an array of segments that have been rendered
	renderEvents: function(events) {
		var res = this.renderEventTable(events);

		this.eventSkeletonEl = $('
').append(res.tableEl);
		this.el.append(this.eventSkeletonEl);

		this.segs = res.segs;
	},

	// Retrieves rendered segment objects
	getSegs: function() {
		return this.segs || [];
	},

	// Removes all event segment elements from the view
	destroyEvents: function() {
		Grid.prototype.destroyEvents.call(this); // call the super-method

		if (this.eventSkeletonEl) {
			this.eventSkeletonEl.remove();
			this.eventSkeletonEl = null;
		}

		this.segs = null;
	},

	// Renders and returns the
 portion of the event-skeleton.
	// Returns an object with properties 'tbodyEl' and 'segs'.
	renderEventTable: function(events) {
		var tableEl = $('

');
		var trEl = tableEl.find('tr');
		var segs = this.eventsToSegs(events);
		var segCols;
		var i, seg;
		var col, colSegs;
		var containerEl;

		segs = this.renderSegs(segs); // returns only the visible segs
		segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg

		this.computeSegVerticals(segs); // compute and assign top/bottom

		for (col = 0; col < segCols.length; col++) { // iterate each column grouping
			colSegs = segCols[col];
			placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array

			containerEl = $('
');

			// assign positioning CSS and insert into container
			for (i = 0; i < colSegs.length; i++) {
				seg = colSegs[i];
				seg.el.css(this.generateSegPositionCss(seg));

				containerEl.append(seg.el);
			}

			trEl.append($('').append(containerEl));
		}

		this.bookendCells(trEl, 'eventSkeleton');

		return  {
			tableEl: tableEl,
			segs: segs
		};
	},

	// Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom.
	updateSegVerticals: function() {
		var segs = this.segs;
		var i;

		if (segs) {
			this.computeSegVerticals(segs);

			for (i = 0; i < segs.length; i++) {
				segs[i].el.css(
					this.generateSegVerticalCss(segs[i])
				);
			}
		}
	},

	// For each segment in an array, computes and assigns its top and bottom properties
	computeSegVerticals: function(segs) {
		var i, seg;

		for (i = 0; i < segs.length; i++) {
			seg = segs[i];
			seg.top = this.computeDateTop(seg.start, seg.start);
			seg.bottom = this.computeDateTop(seg.end, seg.start);
		}
	},

	// Renders the HTML for a single event segment's default rendering
	renderSegHtml: function(seg, disableResizing) {
		var view = this.view;
		var event = seg.event;
		var isDraggable = view.isEventDraggable(event);
		var isResizable = !disableResizing && seg.isEnd && view.isEventResizable(event);
		var classes = this.getSegClasses(seg, isDraggable, isResizable);
		var skinCss = this.getEventSkinCss(event);
		var timeText;
		var fullTimeText; // more verbose time text. for the print stylesheet
		var startTimeText; // just the start time text
		var title = view.trigger("determineEventTitle", this, event) || '';
    var confirmationClass = view.trigger("determineEventConfirmationClass", this, event) || '';
    var stateClass = view.trigger("determineEventStateClass", this, event) || '';
    var eventNotes = view.trigger("determineEventNotes", this, event) || '';
		var stateText =  view.trigger("determineEventStateText", this, event) || '';
		classes.unshift('fc-time-grid-event');

		if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day...
			// Don't display time text on segments that run entirely through a day.
			// That would appear as midnight-midnight and would look dumb.
			// Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am)
			if (seg.isStart || seg.isEnd) {
				timeText = view.getEventTimeText(seg.start, seg.end);
				fullTimeText = view.getEventTimeText(seg.start, seg.end, 'LT');
				startTimeText = view.getEventTimeText(seg.start, null);
			}
		} else {
			// Display the normal time text for the *event's* times
			timeText = view.getEventTimeText(event);
			fullTimeText = view.getEventTimeText(event, 'LT');
			startTimeText = view.getEventTimeText(event.start, null);
		}

		return '
' +
				'
' +
				'
' +
					(timeText ?
						'
' +
							'' + htmlEscape(startTimeText) + '' +
						'
' :
						''
						) +
						'' +
								title +
						'' +
						'
' + stateText + '
'+
						''+
					'
' +
					'
' +
            eventNotes +
					'
' +
				'
' +
				'
' +
				(isResizable ?
					'
' :
					''
					) +
			'
';
	},

	// Generates an object with CSS properties/values that should be applied to an event segment element.
	// Contains important positioning-related properties that should be applied to any event element, customized or not.
	generateSegPositionCss: function(seg) {
		var view = this.view;
		var isRTL = view.opt('isRTL');
		var shouldOverlap = view.opt('slotEventOverlap');
		var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point
		var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point
		var props = this.generateSegVerticalCss(seg); // get top/bottom first
		var left; // amount of space from left edge, a fraction of the total width
		var right; // amount of space from right edge, a fraction of the total width

		if (shouldOverlap) {
			// double the width, but don't go beyond the maximum forward coordinate (1.0)
			forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2);
		}

		if (isRTL) {
			left = 1 - forwardCoord;
			right = backwardCoord;
		}
		else {
			left = backwardCoord;
			right = 1 - forwardCoord;
		}

		props.zIndex = seg.level + 1; // convert from 0-base to 1-based
		props.left = left * 100 + '%';
		props.right = right * 100 + '%';

		if (shouldOverlap && seg.forwardPressure) {
			// add padding to the edge so that forward stacked events don't cover the resizer's icon
			props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width
		}

		return props;
	},

	// Generates an object with CSS properties for the top/bottom coordinates of a segment element
	generateSegVerticalCss: function(seg) {
		return {
			top: seg.top,
			bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container
		};
	},

	// Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
	groupSegCols: function(segs) {
		var view = this.view;
		var segCols = [];
		var i;

		for (i = 0; i < view.colCnt; i++) {
			segCols.push([]);
		}

		for (i = 0; i < segs.length; i++) {
			segCols[segs[i].col].push(segs[i]);
		}

		return segCols;
	}

});

// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each.
// Also reorders the given array by date!
function placeSlotSegs(segs) {
	var levels;
	var level0;
	var i;

	segs.sort(compareSegs); // order by date
	levels = buildSlotSegLevels(segs);
	computeForwardSlotSegs(levels);

	if ((level0 = levels[0])) {

		for (i = 0; i < level0.length; i++) {
			computeSlotSegPressures(level0[i]);
		}

		for (i = 0; i < level0.length; i++) {
			computeSlotSegCoords(level0[i], 0, 0);
		}
	}
}

// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is
// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date.
function buildSlotSegLevels(segs) {
	var levels = [];
	var i, seg;
	var j;

	for (i=0; i<segs.length; i++) {
		seg = segs[i];

		// go through all the levels and stop on the first level where there are no collisions
		for (j=0; j<levels.length; j++) {
			if (!computeSlotSegCollisions(seg, levels[j]).length) {
				break;
			}
		}

		seg.level = j;

		(levels[j] || (levels[j] = [])).push(seg);
	}

	return levels;
}

// For every segment, figure out the other segments that are in subsequent
// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs
function computeForwardSlotSegs(levels) {
	var i, level;
	var j, seg;
	var k;

	for (i=0; i<levels.length; i++) {
		level = levels[i];

		for (j=0; j<level.length; j++) {
			seg = level[j];

			seg.forwardSegs = [];
			for (k=i+1; k<levels.length; k++) {
				computeSlotSegCollisions(seg, levels[k], seg.forwardSegs);
			}
		}
	}
}

// Figure out which path forward (via seg.forwardSegs) results in the longest path until
// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure
function computeSlotSegPressures(seg) {
	var forwardSegs = seg.forwardSegs;
	var forwardPressure = 0;
	var i, forwardSeg;

	if (seg.forwardPressure === undefined) { // not already computed

		for (i=0; i<forwardSegs.length; i++) {
			forwardSeg = forwardSegs[i];

			// figure out the child's maximum forward path
			computeSlotSegPressures(forwardSeg);

			// either use the existing maximum, or use the child's forward pressure
			// plus one (for the forwardSeg itself)
			forwardPressure = Math.max(
				forwardPressure,
				1 + forwardSeg.forwardPressure
			);
		}

		seg.forwardPressure = forwardPressure;
	}
}

// Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range
// from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and
// seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left.
//
// The segment might be part of a "series", which means consecutive segments with the same pressure
// who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of
// segments behind this one in the current series, and `seriesBackwardCoord` is the starting
// coordinate of the first segment in the series.
function computeSlotSegCoords(seg, seriesBackwardPressure, seriesBackwardCoord) {
	var forwardSegs = seg.forwardSegs;
	var i;

	if (seg.forwardCoord === undefined) { // not already computed

		if (!forwardSegs.length) {

			// if there are no forward segments, this segment should butt up against the edge
			seg.forwardCoord = 1;
		}
		else {

			// sort highest pressure first
			forwardSegs.sort(compareForwardSlotSegs);

			// this segment's forwardCoord will be calculated from the backwardCoord of the
			// highest-pressure forward segment.
			computeSlotSegCoords(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);
			seg.forwardCoord = forwardSegs[0].backwardCoord;
		}

		// calculate the backwardCoord from the forwardCoord. consider the series
		seg.backwardCoord = seg.forwardCoord -
			(seg.forwardCoord - seriesBackwardCoord) / // available width for series
			(seriesBackwardPressure + 1); // # of segments in the series

		// use this segment's coordinates to computed the coordinates of the less-pressurized
		// forward segments
		for (i=0; i<forwardSegs.length; i++) {
			computeSlotSegCoords(forwardSegs[i], 0, seg.forwardCoord);
		}
	}
}

// Find all the segments in `otherSegs` that vertically collide with `seg`.
// Append into an optionally-supplied `results` array and return.
function computeSlotSegCollisions(seg, otherSegs, results) {
	results = results || [];

	for (var i=0; i<otherSegs.length; i++) {
		if (isSlotSegCollision(seg, otherSegs[i])) {
			results.push(otherSegs[i]);
		}
	}

	return results;
}

// Do these segments occupy the same vertical space?
function isSlotSegCollision(seg1, seg2) {
	return seg1.bottom > seg2.top && seg1.top < seg2.bottom;
}

// A cmp function for determining which forward segment to rely on more when computing coordinates.
function compareForwardSlotSegs(seg1, seg2) {
	// put higher-pressure first
	return seg2.forwardPressure - seg1.forwardPressure ||
		// put segments that are closer to initial edge first (and favor ones with no coords yet)
		(seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
		// do normal sorting...
		compareSegs(seg1, seg2);
}
<|endoftext|>