<commit_before>/*! customSelect: a jQuery UI widget to select items and ranges
    http://github.com/rixth/customSelect
*/

/*jshint browser: true, jquery: true, indent: 2, white: true, curly: true, forin: true, noarg: true, immed: true, newcap: true, noempty: true */

(function ($) {
  var openClass = 'ui-customSelect-open',
      disabledClass = 'ui-customRange-disabled',
      eventPrefix = 'customselect',
      createCustomRange,
      createCustomValue;

  $.widget("ui.customSelect", {
    options: {
      placeholder: 'Please select some items',
      defaultValue: null,
      customRange: false,
      customValue: false,
      customValuePlaceholder: 'custom value',
      customRanges: null,
      windowFormatter: function (value) {
        return value;
      },
      customRangeHelper: function (min, max) {
        var value, display;

        if (min && max) {
          value = min + '-' + max;
          display = min == max ? min : (min + ' to ' + max);
        } else if (min && !max) {
          value = min + 'p';
          display = min + '+';
        } else if (!min && max) {
          value =  '0-' + max;
          display = '0 to ' + max;
        }

        return [value, display];
      },
      reverseRangeHelper: function (string) {
        if (string.match(/^\d+-\d+$/)) {
          return string.split('-');
        } else if (string.match(/^\d+p$/)) {
          return [string.match(/^(\d+)p$/)[1], ''];
        }
      }
    },
    _create: function () {
      var self = this,
          options = self.options,
          defaultValue = options.defaultValue,
          select = this.element,
          root, rootHtml,
          isOpen = false;

      if (!select.is('select')) {
        throw new TypeError("jquery.customSelect expects a <select> element.");
      }

      self.userCustomValue = null;
      self.rootId = select.attr('id') + '_customSelect';

      // Create the base HTML, the window and dropdown
      rootHtml = [
        '
',
        '
',
        '
▼
',
        '
▲
',
        '
',
        '

',
        '
'
      ];

      // Place ourselves
      this.root = root = $(rootHtml.join('')).attr('id', self.rootId);
      select.after(root).hide();

      this.window = root.find('.ui-customSelect-window span');
      this.list = root.find('ul');

      self._createFromSelect();
      self._setWindowText();

      // Bind events
      self.window.parent().click(function (event) {
        self._trigger(isOpen ? 'blur' : 'focus', event);
      });

      root.delegate('li>input', 'change', function (event) {
        self.userCustomValue = null;
        self._setWindowText();
        select.val(self.getVal()).trigger('change');
        self._trigger('change', event, {
          element: this,
          value: this.value
        });
        $('.ui-customSelect-rangeContainer input, .ui-customSelect-customValue').val('');
        $('.ui-customSelect-error').hide();
        self.userCustomValue = null;
      });

      select.on(eventPrefix + 'focus', function () {
        if (!root.hasClass(disabledClass)) {
          isOpen = true;
          root.addClass(openClass);

          // Close when a click is detected on something other than the widget
          $(document).on('click.customRange' + self.rootId, function (event) {
            if (!$.contains(root[0], event.target)) {
              self._trigger('blur');
            }
          });
        }
      }).on(eventPrefix + 'blur', function () {
        $(document).off('click.customRange' + self.rootId);
        isOpen = false;
        root.removeClass(openClass);
      }).on(eventPrefix + 'disabled', function () {
        root.addClass(disabledClass);
        if (isOpen) {
          self._trigger('blur');
        }
      }).on(eventPrefix + 'enabled', function () {
        root.removeClass(disabledClass);
      });

      if (self.isMultiple && defaultValue) {
        function setDefaultItem() {
          if (!self.getVal().length) {
            self.list.find('input[value=' + defaultValue + ']').attr('checked', true).change();
          }
        };
        select.on(eventPrefix + 'change', setDefaultItem);
        setDefaultItem();
      }

      if (!self.isMultiple && (options.customRange || options.customValue)) {
        self.setCustomValueError = function (error) {
          root.find('.ui-customSelect-error').show().html(error);
        };

        if (options.customRange) {
          createCustomRange.call(self, root, this.list, options);
        } else if (options.customValue) {
          createCustomValue.call(self, root, this.list, options);
        }
      }
    },
    setVal: function (value) {
      var existingOption = this.element.find('option[value=' + value + ']'),
          customRange;

      if (existingOption.length && !existingOption[0].attributes['data-custom']) {
        this.element.val(value);
        this.reload();
        this._setWindowText();
      } else {
        if (this.options.customRange) {
          customRange = this.options.reverseRangeHelper(value);
          this.root.find('.ui-customSelect-min').val(customRange[0]);
          this.root.find('.ui-customSelect-max').val(customRange[1]);
          setCustomUIValues.call(this, null, customRange);
        } else if (this.options.customValue) {
          this.root.find('.ui-customSelect-customValue').val(value);
          setCustomUIValues.call(this, null, value);

        }
      }
    },
    getVal: function () {
      if (this.userCustomValue !== null) {
        return this.userCustomValue;
      }

      var result = this.root.find('li>input:checked').map(function () {
        return this.value;
      }).toArray();
      return result.length ? result[this.isMultiple ? 'slice' : 'pop']() : (this.isMultiple ? [] : null);
    },
    friendlyVal: function () {
      return this.root.find('input:checked+label').map(function () {
        return this.innerHTML;
      }).toArray();
    },
    _setWindowText: function (windowValue) {
      var value;
      if (!windowValue) {
        value = this.friendlyVal();
        windowValue = value.length ? value.join(', ') : this.options.placeholder;
      }
      this.window.html(this.options.windowFormatter(windowValue));
    },
    _createFromSelect: function () {
      var self = this,
          idCounter = 0,
          list = self.list,
          insertionPoint = list.prev()[0] || list.parent();

      self.isMultiple = self.element.attr('multiple');
      list.empty();

      this.element.children().each(function () {
        var element = this,
            id = self.rootId + '_' + (idCounter++),
            label = '' + element.innerHTML + '',
            input;
        if (self.isMultiple) {
          input = $('<input id="' + id + '" type="checkbox" value="' + element.value + '">');
        } else {
          input = $('<input id="' + id + '" name="' + self.rootId + '_radio" type="radio" value="' + element.value + '">');
        }

        list.append($('

').append(input).append(' ').append(label));

        // For some reason, this needs to happen after appending, otherwise
        // IE7 appears to "forget" the checked flag.
        if (element.selected) {
          input.attr('checked', true);
        }
      });
    },
    reload: function () {
      // Remove all custom options
      this.element.find('option[data-custom]').remove();
      this.root.find("input[type='text']").val('').blur();
      this.userCustomValue = null;
      this._createFromSelect();
    },
    _setOption: function (name, value) {
      if (name === 'disabled') {
        this._trigger((value ? 'dis' : 'en') + 'abled');
      }
    },
    destroy: function () {
      $.Widget.prototype.destroy.apply(this, arguments);
      $(document).off('click.customRange' + this.rootId);
      this.root.remove();
      this.element.show();
    }
  });

  function setCustomUIValues(event, value) {
    var self = this,
        temp, displayValue, dataValue,
        option;

    if (self.options.customRange) {
      temp = self.options.customRangeHelper(value[0], value[1]);
      dataValue = temp[0];
      displayValue = temp[1];
    } else {
      dataValue = displayValue = value;
    }

    self._setWindowText(displayValue);
    self.userCustomValue = dataValue;

    option = $('<option data-custom="true" value="' + dataValue + '">' + displayValue + '</option>');
    self.element.find('option[data-custom]').remove();
    self.element.append(option.attr('selected', true)).trigger('change', event);
    self._trigger('change', event);
    self.root.find('input:checked').attr('checked', false);
  }

  createCustomValue = function (root, list, options) {
    var self = this,
        optionValue = options.customValue,
        input;

    list.before($([
      '
',
      '  <input class="ui-customSelect-customValue" type="text" placeholder="' + options.customValuePlaceholder + '" />',
      '

',
      '
'
    ].join('')));

    input = root.find('.ui-customSelect-customValue');

    function customValueHandler(event) {
      var value = input.val();
      if (self._trigger('customvaluechange', event, { value: value, widget: self })) {
        $('.ui-customSelect-error').hide();
        setCustomUIValues.call(self, event, value);
        self._trigger('blur');
      }
    }

    if (typeof(optionValue) === 'string' || typeof(optionValue) === 'number') {
      input.val(optionValue);
      customValueHandler();
    }

    input.keydown(function (event) {
      if (event.which === 13) {
        customValueHandler(event);
      }
    });
  };

  createCustomRange = function (root, list, options) {
    var customRangeHtml, errorDiv, minInput, maxInput,
        self = this;

    customRangeHtml = [
      '
',
      '  <input type="text" class="ui-customSelect-min" placeholder="min" /> to  <input type="text" class="ui-customSelect-max" placeholder="max" />',
      '

',
      '
'
    ];

    list.after($(customRangeHtml.join('')));
    minInput = root.find('.ui-customSelect-min');
    maxInput = root.find('.ui-customSelect-max');
    errorDiv = root;

    function customRangeHandler(event) {
      var min = minInput.val(),
          max = maxInput.val(),
          rangeChangeData = {
            min: min,
            max: max,
            widget: self
          },
          formattingResult, option;

      if (isNaN(min) || isNaN(max)) {
        self.setCustomValueError("Please enter only numbers.");
      } else {
        if (min && max && min > max) {
          self.setCustomValueError("Min cannot be bigger than max.");
        } else {
          if (self._trigger('rangechange', event, rangeChangeData)) {
            $('.ui-customSelect-error').hide();
            setCustomUIValues.call(self, event, [min, max]);
            self._trigger('blur');
          }
        }
      }
    }

    if (options.customRanges) {
      minInput.val(options.customRanges.min);
      maxInput.val(options.customRanges.max);
      customRangeHandler();
    }

    minInput.add(maxInput).keydown(function (event) {
      if (event.which === 13) {
        customRangeHandler(event);
      }
    });
  };
}(jQuery));<commit_msg>fix bug<commit_after>/*! customSelect: a jQuery UI widget to select items and ranges
    http://github.com/rixth/customSelect
*/

/*jshint browser: true, jquery: true, indent: 2, white: true, curly: true, forin: true, noarg: true, immed: true, newcap: true, noempty: true */

(function ($) {
  var openClass = 'ui-customSelect-open',
      disabledClass = 'ui-customRange-disabled',
      eventPrefix = 'customselect',
      createCustomRange,
      createCustomValue;

  $.widget("ui.customSelect", {
    options: {
      placeholder: 'Please select some items',
      defaultValue: null,
      customRange: false,
      customValue: false,
      customValuePlaceholder: 'custom value',
      customRanges: null,
      windowFormatter: function (value) {
        return value;
      },
      customRangeHelper: function (min, max) {
        var value, display;

        if (min && max) {
          value = min + '-' + max;
          display = min == max ? min : (min + ' to ' + max);
        } else if (min && !max) {
          value = min + 'p';
          display = min + '+';
        } else if (!min && max) {
          value =  '0-' + max;
          display = '0 to ' + max;
        }

        return [value, display];
      },
      reverseRangeHelper: function (string) {
        if (string.match(/^\d+-\d+$/)) {
          return string.split('-');
        } else if (string.match(/^\d+p$/)) {
          return [string.match(/^(\d+)p$/)[1], ''];
        }
      }
    },
    _create: function () {
      var self = this,
          options = self.options,
          defaultValue = options.defaultValue,
          select = this.element,
          root, rootHtml,
          isOpen = false;

      if (!select.is('select')) {
        throw new TypeError("jquery.customSelect expects a <select> element.");
      }

      self.userCustomValue = null;
      self.rootId = select.attr('id') + '_customSelect';

      // Create the base HTML, the window and dropdown
      rootHtml = [
        '
',
        '
',
        '
▼
',
        '
▲
',
        '
',
        '

',
        '
'
      ];

      // Place ourselves
      this.root = root = $(rootHtml.join('')).attr('id', self.rootId);
      select.after(root).hide();

      this.window = root.find('.ui-customSelect-window span');
      this.list = root.find('ul');

      self._createFromSelect();
      self._setWindowText();

      // Bind events
      self.window.parent().click(function (event) {
        self._trigger(isOpen ? 'blur' : 'focus', event);
      });

      root.delegate('li>input', 'change', function (event) {
        self.userCustomValue = null;
        self._setWindowText();
        select.val(self.getVal()).trigger('change');
        self._trigger('change', event, {
          element: this,
          value: this.value
        });
        $('.ui-customSelect-rangeContainer input, .ui-customSelect-customValue').val('');
        $('.ui-customSelect-error').hide();
        self.userCustomValue = null;
      });

      select.on(eventPrefix + 'focus', function () {
        if (!root.hasClass(disabledClass)) {
          isOpen = true;
          root.addClass(openClass);

          // Close when a click is detected on something other than the widget
          $(document).on('click.customRange' + self.rootId, function (event) {
            if (!$.contains(root[0], event.target)) {
              self._trigger('blur');
            }
          });
        }
      }).on(eventPrefix + 'blur', function () {
        $(document).off('click.customRange' + self.rootId);
        isOpen = false;
        root.removeClass(openClass);
      }).on(eventPrefix + 'disabled', function () {
        root.addClass(disabledClass);
        if (isOpen) {
          self._trigger('blur');
        }
      }).on(eventPrefix + 'enabled', function () {
        root.removeClass(disabledClass);
      });

      if (self.isMultiple && defaultValue) {
        function setDefaultItem() {
          if (!self.getVal().length) {
            self.list.find('input[value=' + defaultValue + ']').attr('checked', true).change();
          }
        };
        select.on(eventPrefix + 'change', setDefaultItem);
        setDefaultItem();
      }

      if (!self.isMultiple && (options.customRange || options.customValue)) {
        self.setCustomValueError = function (error) {
          root.find('.ui-customSelect-error').show().html(error);
        };

        if (options.customRange) {
          createCustomRange.call(self, root, this.list, options);
        } else if (options.customValue) {
          createCustomValue.call(self, root, this.list, options);
        }
      }
    },
    setVal: function (value) {
      var existingOption = this.element.find('option[value=' + value + ']'),
          customRange;

      if (existingOption.length && !existingOption[0].attributes['data-custom']) {
        this.element.val(value);
        this.reload();
        this._setWindowText();
      } else {
        if (this.options.customRange) {
          customRange = this.options.reverseRangeHelper(value);
          this.root.find('.ui-customSelect-min').val(customRange[0]);
          this.root.find('.ui-customSelect-max').val(customRange[1]);
          setCustomUIValues.call(this, null, customRange);
        } else if (this.options.customValue) {
          this.root.find('.ui-customSelect-customValue').val(value);
          setCustomUIValues.call(this, null, value);

        }
      }
    },
    getVal: function () {
      if (this.userCustomValue !== null) {
        return this.userCustomValue;
      }

      var result = this.root.find('li>input:checked').map(function () {
        return this.value;
      }).toArray();
      return result.length ? result[this.isMultiple ? 'slice' : 'pop']() : (this.isMultiple ? [] : null);
    },
    friendlyVal: function () {
      return this.root.find('input:checked+label').map(function () {
        return this.innerHTML;
      }).toArray();
    },
    _setWindowText: function (windowValue) {
      var value;
      if (!windowValue) {
        value = this.friendlyVal();
        windowValue = value.length ? value.join(', ') : this.options.placeholder;
      }
      this.window.html(this.options.windowFormatter(windowValue));
    },
    _createFromSelect: function () {
      var self = this,
          idCounter = 0,
          list = self.list,
          insertionPoint = list.prev()[0] || list.parent();

      self.isMultiple = self.element.attr('multiple');
      list.empty();

      this.element.children().each(function () {
        var element = this,
            id = self.rootId + '_' + (idCounter++),
            label = '' + element.innerHTML + '',
            input;
        if (self.isMultiple) {
          input = $('<input id="' + id + '" type="checkbox" value="' + element.value + '">');
        } else {
          input = $('<input id="' + id + '" name="' + self.rootId + '_radio" type="radio" value="' + element.value + '">');
        }

        list.append($('

').append(input).append(' ').append(label));

        // For some reason, this needs to happen after appending, otherwise
        // IE7 appears to "forget" the checked flag.
        if (element.selected) {
          input.attr('checked', true);
        }
      });
    },
    reload: function () {
      // Remove all custom options
      this.element.find('option[data-custom]').remove();
      this.root.find("input[type='text']").val('').blur();
      this.userCustomValue = null;
      this._createFromSelect();
      this._setWindowText();
    },
    _setOption: function (name, value) {
      if (name === 'disabled') {
        this._trigger((value ? 'dis' : 'en') + 'abled');
      }
    },
    destroy: function () {
      $.Widget.prototype.destroy.apply(this, arguments);
      $(document).off('click.customRange' + this.rootId);
      this.root.remove();
      this.element.show();
    }
  });

  function setCustomUIValues(event, value) {
    var self = this,
        temp, displayValue, dataValue,
        option;

    if (self.options.customRange) {
      temp = self.options.customRangeHelper(value[0], value[1]);
      dataValue = temp[0];
      displayValue = temp[1];
    } else {
      dataValue = displayValue = value;
    }

    self._setWindowText(displayValue);
    self.userCustomValue = dataValue;

    option = $('<option data-custom="true" value="' + dataValue + '">' + displayValue + '</option>');
    self.element.find('option[data-custom]').remove();
    self.element.append(option.attr('selected', true)).trigger('change', event);
    self._trigger('change', event);
    self.root.find('input:checked').attr('checked', false);
  }

  createCustomValue = function (root, list, options) {
    var self = this,
        optionValue = options.customValue,
        input;

    list.before($([
      '
',
      '  <input class="ui-customSelect-customValue" type="text" placeholder="' + options.customValuePlaceholder + '" />',
      '

',
      '
'
    ].join('')));

    input = root.find('.ui-customSelect-customValue');

    function customValueHandler(event) {
      var value = input.val();
      if (self._trigger('customvaluechange', event, { value: value, widget: self })) {
        $('.ui-customSelect-error').hide();
        setCustomUIValues.call(self, event, value);
        self._trigger('blur');
      }
    }

    if (typeof(optionValue) === 'string' || typeof(optionValue) === 'number') {
      input.val(optionValue);
      customValueHandler();
    }

    input.keydown(function (event) {
      if (event.which === 13) {
        customValueHandler(event);
      }
    });
  };

  createCustomRange = function (root, list, options) {
    var customRangeHtml, errorDiv, minInput, maxInput,
        self = this;

    customRangeHtml = [
      '
',
      '  <input type="text" class="ui-customSelect-min" placeholder="min" /> to  <input type="text" class="ui-customSelect-max" placeholder="max" />',
      '

',
      '
'
    ];

    list.after($(customRangeHtml.join('')));
    minInput = root.find('.ui-customSelect-min');
    maxInput = root.find('.ui-customSelect-max');
    errorDiv = root;

    function customRangeHandler(event) {
      var min = minInput.val(),
          max = maxInput.val(),
          rangeChangeData = {
            min: min,
            max: max,
            widget: self
          },
          formattingResult, option;

      if (isNaN(min) || isNaN(max)) {
        self.setCustomValueError("Please enter only numbers.");
      } else {
        if (min && max && min > max) {
          self.setCustomValueError("Min cannot be bigger than max.");
        } else {
          if (self._trigger('rangechange', event, rangeChangeData)) {
            $('.ui-customSelect-error').hide();
            setCustomUIValues.call(self, event, [min, max]);
            self._trigger('blur');
          }
        }
      }
    }

    if (options.customRanges) {
      minInput.val(options.customRanges.min);
      maxInput.val(options.customRanges.max);
      customRangeHandler();
    }

    minInput.add(maxInput).keydown(function (event) {
      if (event.which === 13) {
        customRangeHandler(event);
      }
    });
  };
}(jQuery));<|endoftext|>


<commit_before>/**
 *  ACCORDION
 *
 * A lightwight vanilla JS accordion with an exstensible API
 */

// import uuid from 'uuid/v4';
// const uuidV4 = uuid;
/* eslint-disable no-unused-vars */
import arrayFromPolyfill from 'array-from-polyfill';
import onCSSTransitionEnd from 'transition-end';

/**
 * CONSTRUCTOR
 * Initializes the object
 */
class BadgerAccordion {
    constructor(el, options) {
        const container = typeof el === 'string' ? document.querySelector(el) : el;

        // If el is not defined
        if (container == null) {
            return;
        }

        const defaults = {
            headerClass:        '.js-badger-accordion-header',
            panelClass:         '.js-badger-accordion-panel',
            panelInnerClass:    '.js-badger-accordion-panel-inner',
            hiddenClass:        '-ba-is-hidden',
            activeClass:        '-ba-is-active',
            get hidenClass() { return this.hiddenClass; },
            initializedClass:    'badger-accordion--initialized',
            get initalisedClass() { return this.initializedClass; },
            headerDataAttr:     'data-badger-accordion-header-id',
            openMultiplePanels: false,
            openHeadersOnLoad:  [],
            headerOpenLabel:    '',
            headerCloseLabel:   '',
            roles:              true
            // toggleEl:            // If you want to use a different element to trigger the accordion
        };

        // Options
        this.settings = Object.assign({}, defaults, options);

        // Setting getting elements
        this.container = container;

        // Selecting children of the current accordion instance
        const children = Array.from(this.container.children);

        // Since the Accordions header button is nested inside an element with class
        // of `badger-accordion__header` it is a grandchild of the accordion instance.
        // In order to have nested accordions we need each to only get all the button
        // elements for this instance. Here an array is created to show all the children
        // of the element `badger-accordion__header`.
        const headerParent = children.filter(header => !header.classList.contains(this.settings.panelClass.substr(1)));

        // Creating an array of all DOM nodes that are Accordion headers
        this.headers = headerParent.reduce((acc, header) => {
            // Gets all the elements that have the headerClass
            const a = Array.from(header.children).filter( child => child.classList.contains( this.settings.headerClass.substr(1) ));

            // Merges the current `badger-accordion__header` accordion triggers
            // with all the others.
            acc = [].concat(...acc, a);

            return acc;
        }, []);

        // Creates an array of all panel elements for this instance of the accordion
        this.panels = children.filter(panel => panel.classList.contains( this.settings.panelClass.substr(1) ));

        this.toggleEl = this.settings.toggleEl !== undefined ? Array.from(this.container.querySelectorAll(this.settings.toggleEl)) : this.headers;

        // This is for managing state of the accordion. It by default sets
        // all accordion panels to be closed
        this.states = [].map.call(this.headers, () => {
            return { state: 'closed' };
        });

        this.ids = [].map.call(this.headers, () => {
            return { id: Math.floor((Math.random() * 1000000) + 1) };
        });

        // This is to ensure that once an open/close event has been fired
        // another cannot start until the first event has finished.
        // @TODO - get this working...
        this.toggling = false;

        // Initiating the accordion
        if( this.container ) {
            this.init();
        } else {
            /* eslint-disable no-console */
            console.log('Something is wrong with you markup...');
        }
    }

    /**
     *  INIT
     *
     *  Initalises the accordion
     */
    init() {
        // Sets up ID, aria attrs & data-attrs
        this._setupAttributes();

        // Setting up the inital view of the accordion
        this._initalState();

        // Setting the height of each panel
        this.calculateAllPanelsHeight();

        // Inserting data-attribute onto each `header`
        this._insertDataAttrs();

        // Adding listeners to headers
        this._addListeners();

        // Adds class to accordion for initalisation
        this._finishInitialization();
    }

    /**
     * CHECK ROLES ETTING
     * @return {[boolean]}
     * Checks roles setting for all roles or a single role.
     * First checks if a `boolean` has been used to set all
     * roles to either true or false. If the setting is an
     * object it will only set the attribute where each
     * attribute has explicitly been set as true, eg;
     * ```
     * roles: {
     *     region: true
     * }
     * ```
     */
    _setRole(role, el) {
        if(typeof this.settings.roles === 'boolean' && this.settings.roles || this.settings.roles[role] !== undefined && this.settings.roles[role] !== false) {
            el.setAttribute('role', role);
        }
    }

    /**
     *  INSERT DATA ATTRS
     *
     *  Updates state object for inital loading of the accordion
     */
    _initalState() {
        // Sets state object as per `this.settings.openHeadersOnLoad`
        const headersToOpen = this.settings.openHeadersOnLoad;

        if (headersToOpen.length) {
            this._openHeadersOnLoad(headersToOpen);
        }

        // Render DOM as per the updates `this.states` object
        this._renderDom();
    }

    /**
     *  INSERT DATA ATTRS
     *
     *  Adds `headerDataAttr` to all headers
     */
    _insertDataAttrs() {
        this.headers.forEach( (header, index) => {
            header.setAttribute(this.settings.headerDataAttr, index);
        });
    }

    /**
     *  FINISH INITALISATION
     *
     *  Adds in `initializedClass` to accordion
     */
    _finishInitialization() {
        this.container.classList.add(this.settings.initializedClass);
        this._setRole('presentation', this.container);
    }

    /**
     *  ADD LISTENERS
     *
     *  Adds click event to each header
     */
    _addListeners() {
        // So we can reference the badger-accordion object inside out eventListener
        const _this = this;

        // Adding click event to accordion
        this.headers.forEach((header, index) => {
            header.addEventListener('click', function() {
                // Getting the target of the click
                // const clickedEl = event.target;

                _this.handleClick(header, index);
            });
        });
    }

    /**
     *  HANDLE CLICK
     *
     *  Handles click and checks if click was on an header element
     *  @param {object} targetHeader - The header node you want to open
     */
    handleClick(targetHeader, headerIndex) {
        // Removing current `.` from `this.settings.headerClass` class so it can
        // be checked against the `targetHeader` classList
        const targetHeaderClass = this.settings.headerClass.substr(1);

        // Checking that the thing that was clicked on was the accordions header
        if (targetHeader.classList.contains(targetHeaderClass) && this.toggling === false) {
            this.toggling = true;

            // Updating states
            this.setState(headerIndex);

            // Render DOM as per the updates `this.states` object
            this._renderDom();
        }
    }

    /**
     *  SET STATES
     *
     *  Sets the state for all headers. The 'target header' will have its state toggeled
     *  @param {object} targetHeaderId - The header node you want to open
     */
    setState(targetHeaderId) {
        const states = this.getState();

        // If `this.settings.openMultiplePanels` is false we need to ensure only one panel
        // be can open at once. If it is false then all panels state APART from the one that
        // has just been clicked needs to be set to 'closed'.
        if (!this.settings.openMultiplePanels) {
            states.filter((state, index) => {
                if (index != targetHeaderId) {
                    state.state = 'closed';
                }
            });
        }

        // Toggles the state value of the target header. This was `array.find` but `find`
        // isnt supported in IE11
        states.filter((state, index) => {
            if (index == targetHeaderId) {
                const newState = this.toggleState(state.state);
                return (state.state = newState);
            }
        });
    }

    /**
     *  RENDER DOM
     *
     *  Renders the accordion in the DOM using the `this.states` object
     */
    _renderDom() {
        // Filter through all open headers and open them
        this.states.filter( (state, index) => {
            if(state.state === 'open') {
                // Opening the current panel but _NOT_ updating the state
                this.open(index, false);
            }
        });

        // Filter through all closed headers and closes them
        this.states.filter( (state, index) => {
            if(state.state === 'closed') {
                // Closing the current panel but _NOT_ updating the state
                this.close(index, false);
            }
        });
    }

    /**
     *  OPEN
     *
     *  Closes a specific panel
     *  @param {integer} headerIndex - The header node index you want to open
     */
    open(headerIndex, setState = true) {
        // 1. If being fired directly the state needs to be updated.
        if(setState) {
            this.setState(headerIndex);
        }

        this.togglePanel('open', headerIndex);
    }

    /**
     *  CLOSE
     *
     *  Closes a specific panel
     *  @param {integer} headerIndex - The header node index you want to close
     */
    close(headerIndex, setState = true) {
        // 1. If being fired directly the state needs to be updated.
        if(setState) {
            this.setState(headerIndex);
        }

        this.togglePanel('closed', headerIndex);
    }

    /**
     *  OPEN ALL
     *
     *  Opens all panels
     */
    openAll() {
        this.headers.forEach((header, headerIndex) => {
            this.togglePanel('open', headerIndex);
        });
    }

    /**
     *  CLOSE ALL
     *
     *  Closes all panels
     */
    closeAll() {
        this.headers.forEach((header, headerIndex) => {
            this.togglePanel('closed', headerIndex);
        });
    }

    /**
     *  GET STATE
     *
     *  Getting state of headers. By default gets state of all headers
     *  @param {string} animationAction - The animation you want to invoke
     *  @param {integer} headerIndex    - The header node index you want to animate
     */
    togglePanel(animationAction, headerIndex) {
        if(animationAction !== undefined && headerIndex !== undefined) {
            if(animationAction === 'closed') {
                // 1. Getting ID of panel that we want to close
                const header        = this.headers[headerIndex];
                const panelToClose  = this.panels[headerIndex];

                // 2. Closeing panel
                panelToClose.classList.add(this.settings.hiddenClass);

                // 3. Removing active classes
                panelToClose.classList.remove(this.settings.activeClass);
                header.classList.remove(this.settings.activeClass);

                // 4. Set aria attrs
                header.setAttribute('aria-expanded', false);

                // 5. Resetting toggling so a new event can be fired
                panelToClose.onCSSTransitionEnd(() => this.toggling = false );
            } else if(animationAction === 'open') {
                // 1. Getting ID of panel that we want to open
                const header      = this.headers[headerIndex];
                const panelToOpen = this.panels[headerIndex];

                // 2. Opening panel
                panelToOpen.classList.remove(this.settings.hiddenClass);

                // 3. Adding active classes
                panelToOpen.classList.add(this.settings.activeClass);
                header.classList.add(this.settings.activeClass);

                // 4. Set aria attrs
                header.setAttribute('aria-expanded', true);

                // 5. Resetting toggling so a new event can be fired
                panelToOpen.onCSSTransitionEnd(() => this.toggling = false );
            }
        }
    }

    // @TODO - is this needed anymore?
    // checkState(headerId) {
    //     let state = this.states[headerId].state;
    //
    //     if(state === 'closed') {
    //         return state;
    //     } else if(state === 'open') {
    //         return state;
    //     }
    // }

    /**
     *  GET STATE
     *
     *  Getting state of headers. By default gets state of all headers
     *  @param {array} headerIds - Id/'s of the headers you want to check
     */
    getState(headerIds = []) {
        if(headerIds.length && Array.isArray(headerIds)) {
            let states = headerIds.map( header => this.states[header] );

            return states;
        } else {
            return this.states;
        }
    }

    /**
     *  TOGGLE STATE
     *
     *  Toggling the state value
     *  @param {string} currentState - Current state value for a header
     */
    toggleState(currentState) {
        if(currentState !== undefined) {
            return (currentState === 'closed') ? 'open' : 'closed';
        }
    }

    /**
     *  HEADERS TO OPEN
     *
     *  Setting which headers should be open when accordion is initalised
     *  @param {array} headersToOpen - Array of ID's for the headers to be open
     */
    _openHeadersOnLoad(headersToOpen = []) {
        if (headersToOpen.length && Array.isArray(headersToOpen)) {
            let headers = headersToOpen.filter(header => header != undefined);

            headers.forEach(header => {
                this.setState(header);
            });
        }
    }

    /**
     *  SET UP ATTRIBUTES
     *
     *  Initalises accordion attribute methods
     */
    _setupAttributes() {
        // Adding ID & aria-controls
        this._setupHeaders();

        // Adding ID & aria-labelledby
        this._setupPanels();

        // Inserting data-attribute onto each `header`
        this._insertDataAttrs();
    }

    /**
     *  SET PANEL HEIGHT - ** DEPRICATED **
     *
     *  Depreicated as this method is becoming public and
     *  I want to name it something that lets devs know
     *  it's not just for using inside the `init()` method.
     */
    _setPanelHeight() {
        this.calculateAllPanelsHeight();
    }

    /**
     *  CALCULATE PANEL HEIGHT
     *
     *  Setting height for panels using pannels inner element
     */
    calculatePanelHeight(panel) {
        const panelInner = panel.querySelector(this.settings.panelInnerClass);

        let activeHeight = panelInner.offsetHeight;

        return panel.style.maxHeight = `${activeHeight}px`;
    }

    /**
     *  CALCULATE PANEL HEIGHT
     *
     *  Setting height for panels using pannels inner element
     */
    calculateAllPanelsHeight() {
        this.panels.forEach(panel => {
            this.calculatePanelHeight(panel);
        });
    }

    /**
     * SET UP HEADERS
     */
    _setupHeaders() {
        this.headers.forEach( (header, index) => {
            header.setAttribute('id', `badger-accordion-header-${this.ids[index].id}`);
            header.setAttribute('aria-controls', `badger-accordion-panel-${this.ids[index].id}`);
        });
    }

    /**
     * SET UP PANELS
     */
    _setupPanels() {
        this.panels.forEach( (panel, index) => {
            panel.setAttribute('id', `badger-accordion-panel-${this.ids[index].id}`);
            panel.setAttribute('aria-labelledby', `badger-accordion-header-${this.ids[index].id}`);
            if(this.settings.roles === true || this.settings.roles.region !== false) {
                this._setRole('region', panel);
            }
        });
    }
}

// Export
export default BadgerAccordion;
<commit_msg>Adds the option to disable clickable headers<commit_after>/**
 *  ACCORDION
 *
 * A lightwight vanilla JS accordion with an exstensible API
 */

// import uuid from 'uuid/v4';
// const uuidV4 = uuid;
/* eslint-disable no-unused-vars */
import arrayFromPolyfill from 'array-from-polyfill';
import onCSSTransitionEnd from 'transition-end';

/**
 * CONSTRUCTOR
 * Initializes the object
 */
class BadgerAccordion {
    constructor(el, options) {
        const container = typeof el === 'string' ? document.querySelector(el) : el;

        // If el is not defined
        if (container == null) {
            return;
        }

        const defaults = {
            headerClass:        '.js-badger-accordion-header',
            panelClass:         '.js-badger-accordion-panel',
            panelInnerClass:    '.js-badger-accordion-panel-inner',
            hiddenClass:        '-ba-is-hidden',
            activeClass:        '-ba-is-active',
            get hidenClass() { return this.hiddenClass; },
            initializedClass:    'badger-accordion--initialized',
            get initalisedClass() { return this.initializedClass; },
            headerDataAttr:     'data-badger-accordion-header-id',
            openMultiplePanels: false,
            openHeadersOnLoad:  [],
            headersClickable:   true,
            headerOpenLabel:    '',
            headerCloseLabel:   '',
            roles:              true
            // toggleEl:            // If you want to use a different element to trigger the accordion
        };

        // Options
        this.settings = Object.assign({}, defaults, options);

        // Setting getting elements
        this.container = container;

        // Selecting children of the current accordion instance
        const children = Array.from(this.container.children);

        // Since the Accordions header button is nested inside an element with class
        // of `badger-accordion__header` it is a grandchild of the accordion instance.
        // In order to have nested accordions we need each to only get all the button
        // elements for this instance. Here an array is created to show all the children
        // of the element `badger-accordion__header`.
        const headerParent = children.filter(header => !header.classList.contains(this.settings.panelClass.substr(1)));

        // Creating an array of all DOM nodes that are Accordion headers
        this.headers = headerParent.reduce((acc, header) => {
            // Gets all the elements that have the headerClass
            const a = Array.from(header.children).filter( child => child.classList.contains( this.settings.headerClass.substr(1) ));

            // Merges the current `badger-accordion__header` accordion triggers
            // with all the others.
            acc = [].concat(...acc, a);

            return acc;
        }, []);

        // Creates an array of all panel elements for this instance of the accordion
        this.panels = children.filter(panel => panel.classList.contains( this.settings.panelClass.substr(1) ));

        this.toggleEl = this.settings.toggleEl !== undefined ? Array.from(this.container.querySelectorAll(this.settings.toggleEl)) : this.headers;

        // This is for managing state of the accordion. It by default sets
        // all accordion panels to be closed
        this.states = [].map.call(this.headers, () => {
            return { state: 'closed' };
        });

        this.ids = [].map.call(this.headers, () => {
            return { id: Math.floor((Math.random() * 1000000) + 1) };
        });

        // This is to ensure that once an open/close event has been fired
        // another cannot start until the first event has finished.
        // @TODO - get this working...
        this.toggling = false;

        // Initiating the accordion
        if( this.container ) {
            this.init();
        } else {
            /* eslint-disable no-console */
            console.log('Something is wrong with you markup...');
        }
    }

    /**
     *  INIT
     *
     *  Initalises the accordion
     */
    init() {
        // Sets up ID, aria attrs & data-attrs
        this._setupAttributes();

        // Setting up the inital view of the accordion
        this._initalState();

        // Setting the height of each panel
        this.calculateAllPanelsHeight();

        // Inserting data-attribute onto each `header`
        this._insertDataAttrs();

        // Adding listeners to headers
        this._addListeners();

        // Adds class to accordion for initalisation
        this._finishInitialization();
    }

    /**
     * CHECK ROLES ETTING
     * @return {[boolean]}
     * Checks roles setting for all roles or a single role.
     * First checks if a `boolean` has been used to set all
     * roles to either true or false. If the setting is an
     * object it will only set the attribute where each
     * attribute has explicitly been set as true, eg;
     * ```
     * roles: {
     *     region: true
     * }
     * ```
     */
    _setRole(role, el) {
        if(typeof this.settings.roles === 'boolean' && this.settings.roles || this.settings.roles[role] !== undefined && this.settings.roles[role] !== false) {
            el.setAttribute('role', role);
        }
    }

    /**
     *  INSERT DATA ATTRS
     *
     *  Updates state object for inital loading of the accordion
     */
    _initalState() {
        // Sets state object as per `this.settings.openHeadersOnLoad`
        const headersToOpen = this.settings.openHeadersOnLoad;

        if (headersToOpen.length) {
            this._openHeadersOnLoad(headersToOpen);
        }

        // Render DOM as per the updates `this.states` object
        this._renderDom();
    }

    /**
     *  INSERT DATA ATTRS
     *
     *  Adds `headerDataAttr` to all headers
     */
    _insertDataAttrs() {
        this.headers.forEach( (header, index) => {
            header.setAttribute(this.settings.headerDataAttr, index);
        });
    }

    /**
     *  FINISH INITALISATION
     *
     *  Adds in `initializedClass` to accordion
     */
    _finishInitialization() {
        this.container.classList.add(this.settings.initializedClass);
        this._setRole('presentation', this.container);
    }

    /**
     *  ADD LISTENERS
     *
     *  Adds click event to each header
     */
    _addListeners() {
        if (! this.settings.headersClickable) return;

        // So we can reference the badger-accordion object inside out eventListener
        const _this = this;

        // Adding click event to accordion
        this.headers.forEach((header, index) => {
            header.addEventListener('click', function() {
                // Getting the target of the click
                // const clickedEl = event.target;

                _this.handleClick(header, index);
            });
        });
    }

    /**
     *  HANDLE CLICK
     *
     *  Handles click and checks if click was on an header element
     *  @param {object} targetHeader - The header node you want to open
     */
    handleClick(targetHeader, headerIndex) {
        // Removing current `.` from `this.settings.headerClass` class so it can
        // be checked against the `targetHeader` classList
        const targetHeaderClass = this.settings.headerClass.substr(1);

        // Checking that the thing that was clicked on was the accordions header
        if (targetHeader.classList.contains(targetHeaderClass) && this.toggling === false) {
            this.toggling = true;

            // Updating states
            this.setState(headerIndex);

            // Render DOM as per the updates `this.states` object
            this._renderDom();
        }
    }

    /**
     *  SET STATES
     *
     *  Sets the state for all headers. The 'target header' will have its state toggeled
     *  @param {object} targetHeaderId - The header node you want to open
     */
    setState(targetHeaderId) {
        const states = this.getState();

        // If `this.settings.openMultiplePanels` is false we need to ensure only one panel
        // be can open at once. If it is false then all panels state APART from the one that
        // has just been clicked needs to be set to 'closed'.
        if (!this.settings.openMultiplePanels) {
            states.filter((state, index) => {
                if (index != targetHeaderId) {
                    state.state = 'closed';
                }
            });
        }

        // Toggles the state value of the target header. This was `array.find` but `find`
        // isnt supported in IE11
        states.filter((state, index) => {
            if (index == targetHeaderId) {
                const newState = this.toggleState(state.state);
                return (state.state = newState);
            }
        });
    }

    /**
     *  RENDER DOM
     *
     *  Renders the accordion in the DOM using the `this.states` object
     */
    _renderDom() {
        // Filter through all open headers and open them
        this.states.filter( (state, index) => {
            if(state.state === 'open') {
                // Opening the current panel but _NOT_ updating the state
                this.open(index, false);
            }
        });

        // Filter through all closed headers and closes them
        this.states.filter( (state, index) => {
            if(state.state === 'closed') {
                // Closing the current panel but _NOT_ updating the state
                this.close(index, false);
            }
        });
    }

    /**
     *  OPEN
     *
     *  Closes a specific panel
     *  @param {integer} headerIndex - The header node index you want to open
     */
    open(headerIndex, setState = true) {
        // 1. If being fired directly the state needs to be updated.
        if(setState) {
            this.setState(headerIndex);
        }

        this.togglePanel('open', headerIndex);
    }

    /**
     *  CLOSE
     *
     *  Closes a specific panel
     *  @param {integer} headerIndex - The header node index you want to close
     */
    close(headerIndex, setState = true) {
        // 1. If being fired directly the state needs to be updated.
        if(setState) {
            this.setState(headerIndex);
        }

        this.togglePanel('closed', headerIndex);
    }

    /**
     *  OPEN ALL
     *
     *  Opens all panels
     */
    openAll() {
        this.headers.forEach((header, headerIndex) => {
            this.togglePanel('open', headerIndex);
        });
    }

    /**
     *  CLOSE ALL
     *
     *  Closes all panels
     */
    closeAll() {
        this.headers.forEach((header, headerIndex) => {
            this.togglePanel('closed', headerIndex);
        });
    }

    /**
     *  GET STATE
     *
     *  Getting state of headers. By default gets state of all headers
     *  @param {string} animationAction - The animation you want to invoke
     *  @param {integer} headerIndex    - The header node index you want to animate
     */
    togglePanel(animationAction, headerIndex) {
        if(animationAction !== undefined && headerIndex !== undefined) {
            if(animationAction === 'closed') {
                // 1. Getting ID of panel that we want to close
                const header        = this.headers[headerIndex];
                const panelToClose  = this.panels[headerIndex];

                // 2. Closeing panel
                panelToClose.classList.add(this.settings.hiddenClass);

                // 3. Removing active classes
                panelToClose.classList.remove(this.settings.activeClass);
                header.classList.remove(this.settings.activeClass);

                // 4. Set aria attrs
                header.setAttribute('aria-expanded', false);

                // 5. Resetting toggling so a new event can be fired
                panelToClose.onCSSTransitionEnd(() => this.toggling = false );
            } else if(animationAction === 'open') {
                // 1. Getting ID of panel that we want to open
                const header      = this.headers[headerIndex];
                const panelToOpen = this.panels[headerIndex];

                // 2. Opening panel
                panelToOpen.classList.remove(this.settings.hiddenClass);

                // 3. Adding active classes
                panelToOpen.classList.add(this.settings.activeClass);
                header.classList.add(this.settings.activeClass);

                // 4. Set aria attrs
                header.setAttribute('aria-expanded', true);

                // 5. Resetting toggling so a new event can be fired
                panelToOpen.onCSSTransitionEnd(() => this.toggling = false );
            }
        }
    }

    // @TODO - is this needed anymore?
    // checkState(headerId) {
    //     let state = this.states[headerId].state;
    //
    //     if(state === 'closed') {
    //         return state;
    //     } else if(state === 'open') {
    //         return state;
    //     }
    // }

    /**
     *  GET STATE
     *
     *  Getting state of headers. By default gets state of all headers
     *  @param {array} headerIds - Id/'s of the headers you want to check
     */
    getState(headerIds = []) {
        if(headerIds.length && Array.isArray(headerIds)) {
            let states = headerIds.map( header => this.states[header] );

            return states;
        } else {
            return this.states;
        }
    }

    /**
     *  TOGGLE STATE
     *
     *  Toggling the state value
     *  @param {string} currentState - Current state value for a header
     */
    toggleState(currentState) {
        if(currentState !== undefined) {
            return (currentState === 'closed') ? 'open' : 'closed';
        }
    }

    /**
     *  HEADERS TO OPEN
     *
     *  Setting which headers should be open when accordion is initalised
     *  @param {array} headersToOpen - Array of ID's for the headers to be open
     */
    _openHeadersOnLoad(headersToOpen = []) {
        if (headersToOpen.length && Array.isArray(headersToOpen)) {
            let headers = headersToOpen.filter(header => header != undefined);

            headers.forEach(header => {
                this.setState(header);
            });
        }
    }

    /**
     *  SET UP ATTRIBUTES
     *
     *  Initalises accordion attribute methods
     */
    _setupAttributes() {
        // Adding ID & aria-controls
        this._setupHeaders();

        // Adding ID & aria-labelledby
        this._setupPanels();

        // Inserting data-attribute onto each `header`
        this._insertDataAttrs();
    }

    /**
     *  SET PANEL HEIGHT - ** DEPRICATED **
     *
     *  Depreicated as this method is becoming public and
     *  I want to name it something that lets devs know
     *  it's not just for using inside the `init()` method.
     */
    _setPanelHeight() {
        this.calculateAllPanelsHeight();
    }

    /**
     *  CALCULATE PANEL HEIGHT
     *
     *  Setting height for panels using pannels inner element
     */
    calculatePanelHeight(panel) {
        const panelInner = panel.querySelector(this.settings.panelInnerClass);

        let activeHeight = panelInner.offsetHeight;

        return panel.style.maxHeight = `${activeHeight}px`;
    }

    /**
     *  CALCULATE PANEL HEIGHT
     *
     *  Setting height for panels using pannels inner element
     */
    calculateAllPanelsHeight() {
        this.panels.forEach(panel => {
            this.calculatePanelHeight(panel);
        });
    }

    /**
     * SET UP HEADERS
     */
    _setupHeaders() {
        this.headers.forEach( (header, index) => {
            header.setAttribute('id', `badger-accordion-header-${this.ids[index].id}`);
            header.setAttribute('aria-controls', `badger-accordion-panel-${this.ids[index].id}`);
        });
    }

    /**
     * SET UP PANELS
     */
    _setupPanels() {
        this.panels.forEach( (panel, index) => {
            panel.setAttribute('id', `badger-accordion-panel-${this.ids[index].id}`);
            panel.setAttribute('aria-labelledby', `badger-accordion-header-${this.ids[index].id}`);
            if(this.settings.roles === true || this.settings.roles.region !== false) {
                this._setRole('region', panel);
            }
        });
    }
}

// Export
export default BadgerAccordion;
<|endoftext|>


<commit_before>/*global console define require window*/
var registerPlugin;
require(['orion/plugin', 'orion/EventTarget', 'orion/textview/textModel', 'orion/editor/mirror', 'orioncodemirror/mirrorTextModel', 'orioncodemirror/highlighter', 'requirejs/domReady!'],
	function(PluginProvider, EventTarget, mTextModel, mMirror, mMirrorTextModel, mHighlighter, document) {
		// Invert 1:1 map
		function invert(obj) {
			var result = {};
			for (var key in obj) {
				if (obj.hasOwnProperty(key)) {
					var arr = obj[key];
					if (arr instanceof Array) {
						for (var i=0; i < arr.length; i++) {
							var val = arr[i];
							result[val] = key;
						}
					} else {
						result[arr] = key;
					}
				}
			}
			return result;
		}

		var modes = ["clike", "clojure", "coffeescript", "css", "diff", "groovy", "haskell", "htmlmixed", "javascript", "jinja2", "lua", "markdown", "ntriples", "pascal", "perl", "php", "plsql", "python", "r", "rst", "ruby", "rust", "scheme", "smalltalk", "sparql", "stex", "tiddlywiki", "velocity", "xml", "xmlpure", "yaml"];
		// Orion's content type IDs are MIME-like but don't match CodeMirror's exactly, so we map them.
		var mime2ContentType = {
			"application/xml": "application/xml", // orion
			"text/x-java": "text/x-java-source", // orion
			"text/javascript": "application/javascript" // orion
		};
		var contentType2Mime = invert(mime2ContentType);
		var mime2Ext = {
			"text/x-csrc": ["c", "C", "h"],
			"text/x-c++src": ["cc", "cpp", "c++"],
			"text/x-clojure": ["clj"],
			"text/x-coffeescript": ["coffee"],
			"text/x-csharp": ["cs"],
			"text/css": ["css"],
			"text/x-diff": ["diff", "patch"],
			"text/x-groovy": ["groovy"],
			"text/x-haskell": ["hs"],
			"text/html": ["html", "htm"],
			"text/x-java": ["java"],
			"text/javascript": ["js"],
			"application/json": ["json"],
			"jinja2": [],
			"text/x-lua": ["lua"],
			"text/x-markdown": ["md"],
			"text/n-triples": ["nt"],
			"text/x-pascal": ["pascal", "p"],
			"text/x-perl": ["pl"],
			"text/x-php": ["php", "php3", "php4", "php5"],
			"application/x-httpd-php": ["phtml"],
			"text/x-plsql": ["sql"],
			"text/x-python": ["py"],
			"text/x-rsrc": ["r"],
			"text/x-rst": ["rst"],
			"text/x-ruby": ["rb"],
			"text/x-rust": ["rs", "rc"],
			"text/x-scheme": ["scm", "ss"],
			"text/x-stsrc": ["sm"],
			"application/x-sparql-query": ["spk"],
			"text/stex": [],
			"text/x-tiddlywiki": [],
			"text/velocity": [],
	//		"text/xml": ["xml"],
			"application/xml": ["xml"],
			"text/x-yaml": ["yaml", "yml"]
		};

		function getMimeForContentTypeId(contentTypeId) {
			return contentType2Mime[contentTypeId] || contentTypeId;
		}
		function getContentTypeIdForMime(mime) {
			return mime2ContentType[mime];
		}

		// Important: the modes we are about to load need the 'window.CodeMirror' object.
		// TODO replace this with a RequireJS 2 "shim" config
		var globalCodeMirror = window.CodeMirror = new mMirror.Mirror();
		require(['codemirror2-compressed/modes-compressed'], function(mModes) {
			function getMimes(modes) {
				return globalCodeMirror.listMIMEs().filter(
					function(mime) {
						var mname = globalCodeMirror._getModeName(mime);
						return modes.indexOf(mname) !== -1;
					});
			}
			// Create Orion content types for modes
			function getContentTypes(modes) {
				return getMimes(modes).map(function(mime) {
					return {
						id: getContentTypeIdForMime(mime) || mime,
						extension: mime2Ext[mime],
						"extends": "text/plain"
					};
				});
			}
			function createForm() {
				var list = document.getElementById("modelist");
				modes.forEach(function(mode) {
					var li = document.createElement("li");
					var label = document.createElement("div");
					var modeNode = document.createElement("a");
					modeNode.href = "http://codemirror.net/mode/" + mode + "/";
					modeNode.innerHTML = mode;
					label.appendChild(modeNode);
					li.appendChild(label);
					list.appendChild(li);
				});
			}

			function errback(e) {
				if (typeof console === "object" && console) {
					console.log("orion-codemirror: Couldn't install plugin: " + e);
				}
			}

			// Register plugin
			(function() {
				var modeSet = modes; // modeSet to install
				try {
					var model = new mMirrorTextModel.MirrorTextModel();
					var highlighter = new mHighlighter.Highlighter(model, globalCodeMirror);
					var contentTypes = getContentTypes(modeSet);

					var provider = new PluginProvider();
					provider.registerService("orion.edit.model",
						{
							onModelChanging: function(modelChangingEvent) {
								model.onTargetModelChanging(modelChangingEvent);
							},
							onScroll: function(scrollEvent) {
								highlighter.setViewportIndex(scrollEvent.topIndex);
							}
						},
						{ types: ["ModelChanging", "Scroll"],
						  contentType: contentTypes
						});

					// Register editor associations for installed modes
					provider.registerService("orion.file.contenttype", {},
						{	contentTypes: contentTypes
						});
					provider.registerService("orion.navigate.openWith", {},
						{	editor: "orion.editor",
							contentType: contentTypes.map(function(ct) { return ct.id; })
						});

					var highlighterServiceImpl = {
						setContentType: function(contentType) {
							var mime = getMimeForContentTypeId(contentType.id);
							if (mime) {
								highlighter.setMode(mime);
							} else {
								console.log("Missing MIME in content type " + contentType.id);
							}
						}
					};
					// Turn the service impl into an event emitter
					EventTarget.attach(highlighterServiceImpl);
					provider.registerService("orion.edit.highlighter",
						highlighterServiceImpl,
						{ type: "highlighter",
						  contentType: contentTypes
						});
					highlighter.addEventListener("StyleReady", function(styleReadyEvent) {
						styleReadyEvent.type = "orion.edit.highlighter.styleReady";
						highlighterServiceImpl.dispatchEvent(styleReadyEvent);
					});

					provider.connect(function(e){
		//				console.log("orion-codemirror: connected. Supported modes: [" + modeSet.join(",") + "]");
					}, errback);
				} catch (e) {
					errback(e);
				}

				createForm();
				document.getElementById("url").value =  window.location.href;
			}());
		});
});<commit_msg>Add plugin headers<commit_after>/*global console define require window*/
var registerPlugin;
require(['orion/plugin', 'orion/EventTarget', 'orion/textview/textModel', 'orion/editor/mirror', 'orioncodemirror/mirrorTextModel', 'orioncodemirror/highlighter', 'requirejs/domReady!'],
	function(PluginProvider, EventTarget, mTextModel, mMirror, mMirrorTextModel, mHighlighter, document) {
		// Invert 1:1 map
		function invert(obj) {
			var result = {};
			for (var key in obj) {
				if (obj.hasOwnProperty(key)) {
					var arr = obj[key];
					if (arr instanceof Array) {
						for (var i=0; i < arr.length; i++) {
							var val = arr[i];
							result[val] = key;
						}
					} else {
						result[arr] = key;
					}
				}
			}
			return result;
		}

		var modes = ["clike", "clojure", "coffeescript", "css", "diff", "groovy", "haskell", "htmlmixed", "javascript", "jinja2", "lua", "markdown", "ntriples", "pascal", "perl", "php", "plsql", "python", "r", "rst", "ruby", "rust", "scheme", "smalltalk", "sparql", "stex", "tiddlywiki", "velocity", "xml", "xmlpure", "yaml"];
		// Orion's content type IDs are MIME-like but don't match CodeMirror's exactly, so we map them.
		var mime2ContentType = {
			"application/xml": "application/xml", // orion
			"text/x-java": "text/x-java-source", // orion
			"text/javascript": "application/javascript" // orion
		};
		var contentType2Mime = invert(mime2ContentType);
		var mime2Ext = {
			"text/x-csrc": ["c", "C", "h"],
			"text/x-c++src": ["cc", "cpp", "c++"],
			"text/x-clojure": ["clj"],
			"text/x-coffeescript": ["coffee"],
			"text/x-csharp": ["cs"],
			"text/css": ["css"],
			"text/x-diff": ["diff", "patch"],
			"text/x-groovy": ["groovy"],
			"text/x-haskell": ["hs"],
			"text/html": ["html", "htm"],
			"text/x-java": ["java"],
			"text/javascript": ["js"],
			"application/json": ["json"],
			"jinja2": [],
			"text/x-lua": ["lua"],
			"text/x-markdown": ["md"],
			"text/n-triples": ["nt"],
			"text/x-pascal": ["pascal", "p"],
			"text/x-perl": ["pl"],
			"text/x-php": ["php", "php3", "php4", "php5"],
			"application/x-httpd-php": ["phtml"],
			"text/x-plsql": ["sql"],
			"text/x-python": ["py"],
			"text/x-rsrc": ["r"],
			"text/x-rst": ["rst"],
			"text/x-ruby": ["rb"],
			"text/x-rust": ["rs", "rc"],
			"text/x-scheme": ["scm", "ss"],
			"text/x-stsrc": ["sm"],
			"application/x-sparql-query": ["spk"],
			"text/stex": [],
			"text/x-tiddlywiki": [],
			"text/velocity": [],
	//		"text/xml": ["xml"],
			"application/xml": ["xml"],
			"text/x-yaml": ["yaml", "yml"]
		};

		function getMimeForContentTypeId(contentTypeId) {
			return contentType2Mime[contentTypeId] || contentTypeId;
		}
		function getContentTypeIdForMime(mime) {
			return mime2ContentType[mime];
		}

		// Important: the modes we are about to load need the 'window.CodeMirror' object.
		// TODO replace this with a RequireJS 2 "shim" config
		var globalCodeMirror = window.CodeMirror = new mMirror.Mirror();
		require(['codemirror2-compressed/modes-compressed'], function(mModes) {
			function getMimes(modes) {
				return globalCodeMirror.listMIMEs().filter(
					function(mime) {
						var mname = globalCodeMirror._getModeName(mime);
						return modes.indexOf(mname) !== -1;
					});
			}
			// Create Orion content types for modes
			function getContentTypes(modes) {
				return getMimes(modes).map(function(mime) {
					return {
						id: getContentTypeIdForMime(mime) || mime,
						extension: mime2Ext[mime],
						"extends": "text/plain"
					};
				});
			}
			function createForm() {
				var list = document.getElementById("modelist");
				modes.forEach(function(mode) {
					var li = document.createElement("li");
					var label = document.createElement("div");
					var modeNode = document.createElement("a");
					modeNode.href = "http://codemirror.net/mode/" + mode + "/";
					modeNode.innerHTML = mode;
					label.appendChild(modeNode);
					li.appendChild(label);
					list.appendChild(li);
				});
			}

			function errback(e) {
				if (typeof console === "object" && console) {
					console.log("orion-codemirror: Couldn't install plugin: " + e);
				}
			}

			// Register plugin
			(function() {
				var modeSet = modes; // modeSet to install
				try {
					var model = new mMirrorTextModel.MirrorTextModel();
					var highlighter = new mHighlighter.Highlighter(model, globalCodeMirror);
					var contentTypes = getContentTypes(modeSet);

					var provider = new PluginProvider({
						name: "Orion CodeMirror syntax highlighting",
						version: "2.0",
						description: "Provides syntax highlighting for various languages. Powered by code from the CodeMirror project.",
						license: "Eclipse Distribution License",
						website: "https://github.com/mamacdon/orion-codemirror"
					});
					provider.registerService("orion.edit.model",
						{
							onModelChanging: function(modelChangingEvent) {
								model.onTargetModelChanging(modelChangingEvent);
							},
							onScroll: function(scrollEvent) {
								highlighter.setViewportIndex(scrollEvent.topIndex);
							}
						},
						{ types: ["ModelChanging", "Scroll"],
						  contentType: contentTypes
						});

					// Register editor associations for installed modes
					provider.registerService("orion.file.contenttype", {},
						{	contentTypes: contentTypes
						});
					provider.registerService("orion.navigate.openWith", {},
						{	editor: "orion.editor",
							contentType: contentTypes.map(function(ct) { return ct.id; })
						});

					var highlighterServiceImpl = {
						setContentType: function(contentType) {
							var mime = getMimeForContentTypeId(contentType.id);
							if (mime) {
								highlighter.setMode(mime);
							} else {
								console.log("Missing MIME in content type " + contentType.id);
							}
						}
					};
					// Turn the service impl into an event emitter
					EventTarget.attach(highlighterServiceImpl);
					provider.registerService("orion.edit.highlighter",
						highlighterServiceImpl,
						{ type: "highlighter",
						  contentType: contentTypes
						});
					highlighter.addEventListener("StyleReady", function(styleReadyEvent) {
						styleReadyEvent.type = "orion.edit.highlighter.styleReady";
						highlighterServiceImpl.dispatchEvent(styleReadyEvent);
					});

					provider.connect(function(e){
		//				console.log("orion-codemirror: connected. Supported modes: [" + modeSet.join(",") + "]");
					}, errback);
				} catch (e) {
					errback(e);
				}

				createForm();
				document.getElementById("url").value =  window.location.href;
			}());
		});
});<|endoftext|>


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

// Constants for the focus/blur animation.
var NORMAL_SCALE = new THREE.Vector3(1, 1, 1);
var FOCUS_SCALE = new THREE.Vector3(1.2, 1.2, 1.2);
var FOCUS_DURATION = 200;

// Constants for the active/inactive animation.
var INACTIVE_COLOR = new THREE.Color(1, 1, 1);
var ACTIVE_COLOR = new THREE.Color(0.8, 0, 0);
var ACTIVE_DURATION = 100;

// Constants for opacity.
var MAX_INNER_OPACITY = 0.8;
var MAX_OUTER_OPACITY = 0.5;
var FADE_START_ANGLE_DEG = 35;
var FADE_END_ANGLE_DEG = 60;
/**
 * Responsible for rectangular hot spots that the user can interact with.
 *
 * Specific duties:
 *   Adding and removing hotspots.
 *   Rendering the hotspots (debug mode only).
 *   Notifying when hotspots are interacted with.
 *
 * Emits the following events:
 *   click (id): a hotspot is clicked.
 *   focus (id): a hotspot is focused.
 *   blur (id): a hotspot is no longer hovered over.
 */
function HotspotRenderer(worldRenderer) {
  this.worldRenderer = worldRenderer;
  this.scene = worldRenderer.scene;

  // Note: this event must be added to document.body and not to window for it to
  // work inside iOS iframes.
  var body = document.body;
  // Bind events for hotspot interaction.
  if (!Util.isMobile()) {
    // Only enable mouse events on desktop.
    body.addEventListener('mousedown', this.onMouseDown_.bind(this), false);
    body.addEventListener('mousemove', this.onMouseMove_.bind(this), false);
    body.addEventListener('mouseup', this.onMouseUp_.bind(this), false);
  }
  body.addEventListener('touchstart', this.onTouchStart_.bind(this), false);
  body.addEventListener('touchend', this.onTouchEnd_.bind(this), false);

  // Add a placeholder for hotspots.
  this.hotspotRoot = new THREE.Object3D();
  // Align the center with the center of the camera too.
  this.hotspotRoot.rotation.y = Math.PI / 2;
  this.scene.add(this.hotspotRoot);

  // All hotspot IDs.
  this.hotspots = {};

  // Currently selected hotspots.
  this.selectedHotspots = {};

  // Hotspots that the last touchstart / mousedown event happened for.
  this.downHotspots = {};

  // For raycasting. Initialize mouse to be off screen initially.
  this.pointer = new THREE.Vector2(1, 1);
  this.raycaster = new THREE.Raycaster();
}
HotspotRenderer.prototype = new EventEmitter();

/**
 * @param pitch {Number} The latitude of center, specified in degrees, between
 * -90 and 90, with 0 at the horizon.
 * @param yaw {Number} The longitude of center, specified in degrees, between
 * -180 and 180, with 0 at the image center.
 * @param radius {Number} The radius of the hotspot, specified in meters.
 * @param distance {Number} The distance of the hotspot from camera, specified
 * in meters.
 * @param hotspotId {String} The ID of the hotspot.
 */
HotspotRenderer.prototype.add = function(pitch, yaw, radius, distance, id) {
  // If a hotspot already exists with this ID, stop.
  if (this.hotspots[id]) {
    // TODO: Proper error reporting.
    console.error('Attempt to add hotspot with existing id %s.', id);
    return;
  }
  var hotspot = this.createHotspot_(radius, distance);
  hotspot.name = id;

  // Position the hotspot based on the pitch and yaw specified.
  var quat = new THREE.Quaternion();
  quat.setFromEuler(new THREE.Euler(THREE.Math.degToRad(pitch), THREE.Math.degToRad(yaw), 0));
  hotspot.position.applyQuaternion(quat);
  hotspot.lookAt(new THREE.Vector3());

  this.hotspotRoot.add(hotspot);
  this.hotspots[id] = hotspot;
}

/**
 * Removes a hotspot based on the ID.
 *
 * @param ID {String} Identifier of the hotspot to be removed.
 */
HotspotRenderer.prototype.remove = function(id) {
  // If there's no hotspot with this ID, fail.
  if (!this.hotspots[id]) {
    // TODO: Proper error reporting.
    console.error('Attempt to remove non-existing hotspot with id %s.', id);
    return;
  }
  // Remove the mesh from the scene.
  this.hotspotRoot.remove(this.hotspots[id]);

  // If this hotspot was selected, make sure it gets unselected.
  delete this.selectedHotspots[id];
  delete this.downHotspots[id];
  delete this.hotspots[id];
  this.emit('blur', id);
};

/**
 * Clears all hotspots from the pano. Often called when changing panos.
 */
HotspotRenderer.prototype.clearAll = function() {
  for (var id in this.hotspots) {
    this.remove(id);
  }
};

HotspotRenderer.prototype.getCount = function() {
  var count = 0;
  for (var id in this.hotspots) {
    count += 1;
  }
  return count;
};

HotspotRenderer.prototype.update = function(camera) {
  if (this.worldRenderer.isVRMode()) {
    this.pointer.set(0, 0);
  }
  // Update the picking ray with the camera and mouse position.
  this.raycaster.setFromCamera(this.pointer, camera);

  // Fade hotspots out if they are really far from center to avoid overly
  // distorted visuals.
  this.fadeOffCenterHotspots_(camera);

  var hotspots = this.hotspotRoot.children;

  // Go through all hotspots to see if they are currently selected.
  for (var i = 0; i < hotspots.length; i++) {
    var hotspot = hotspots[i];
    //hotspot.lookAt(camera.position);
    var id = hotspot.name;
    // Check if hotspot is intersected with the picking ray.
    var intersects = this.raycaster.intersectObjects(hotspot.children);
    var isIntersected = (intersects.length > 0);

    // If newly selected, emit a focus event.
    if (isIntersected && !this.selectedHotspots[id]) {
      this.emit('focus', id);
      this.focus_(id);
    }
    // If no longer selected, emit a blur event.
    if (!isIntersected && this.selectedHotspots[id]) {
      this.emit('blur', id);
      this.blur_(id);
    }
    // Update the set of selected hotspots.
    if (isIntersected) {
      this.selectedHotspots[id] = true;
    } else {
      delete this.selectedHotspots[id];
    }
  }
};

/**
 * Toggle whether or not hotspots are visible.
 */
HotspotRenderer.prototype.setVisibility = function(isVisible) {
  this.hotspotRoot.visible = isVisible;
};

HotspotRenderer.prototype.onTouchStart_ = function(e) {
  // In VR mode, don't touch the pointer position.
  if (!this.worldRenderer.isVRMode()) {
    this.updateTouch_(e);
  }

  // Force a camera update to see if any hotspots were selected.
  this.update(this.worldRenderer.camera);

  this.downHotspots = {};
  for (var id in this.selectedHotspots) {
    this.downHotspots[id] = true;
    this.down_(id);
  }
  return false;
};

HotspotRenderer.prototype.onTouchEnd_ = function(e) {
  // If no hotspots are pressed, emit an empty click event.
  if (Util.isEmptyObject(this.downHotspots)) {
    this.emit('click');
    return;
  }

  // Only emit a click if the finger was down on the same hotspot before.
  for (var id in this.downHotspots) {
    this.emit('click', id);
    this.up_(id);
    e.preventDefault();
  }
};

HotspotRenderer.prototype.updateTouch_ = function(e) {
  var size = this.getSize_();
  var touch = e.touches[0];
	this.pointer.x = (touch.clientX / size.width) * 2 - 1;
	this.pointer.y = - (touch.clientY / size.height) * 2 + 1;
};

HotspotRenderer.prototype.onMouseDown_ = function(e) {
  this.updateMouse_(e);

  this.downHotspots = {};
  for (var id in this.selectedHotspots) {
    this.downHotspots[id] = true;
    this.down_(id);
  }
};

HotspotRenderer.prototype.onMouseMove_ = function(e) {
  this.updateMouse_(e);
};

HotspotRenderer.prototype.onMouseUp_ = function(e) {
  this.updateMouse_(e);

  // If no hotspots are pressed, emit an empty click event.
  if (Util.isEmptyObject(this.downHotspots)) {
    this.emit('click');
    return;
  }

  // Only emit a click if the mouse was down on the same hotspot before.
  for (var id in this.selectedHotspots) {
    if (id in this.downHotspots) {
      this.emit('click', id);
      this.up_(id);
    }
  }
};

HotspotRenderer.prototype.updateMouse_ = function(e) {
  var size = this.getSize_();
	this.pointer.x = (e.clientX / size.width) * 2 - 1;
	this.pointer.y = - (e.clientY / size.height) * 2 + 1;
};

HotspotRenderer.prototype.getSize_ = function() {
  var canvas = this.worldRenderer.renderer.domElement;
  return this.worldRenderer.renderer.getSize();
};

HotspotRenderer.prototype.createHotspot_ = function(radius, distance) {
  var innerGeometry = new THREE.CircleGeometry(radius, 32);

  var innerMaterial = new THREE.MeshBasicMaterial({
    color: 0xffffff, side: THREE.DoubleSide, transparent: true,
    opacity: MAX_INNER_OPACITY, depthTest: false
  });

  var inner = new THREE.Mesh(innerGeometry, innerMaterial);
  inner.name = 'inner';

  var outerMaterial = new THREE.MeshBasicMaterial({
    color: 0xffffff, side: THREE.DoubleSide, transparent: true,
    opacity: MAX_OUTER_OPACITY, depthTest: false
  });
  var outerGeometry = new THREE.RingGeometry(radius * 0.85, radius, 32);
  var outer = new THREE.Mesh(outerGeometry, outerMaterial);
  outer.name = 'outer';

  // Position at the extreme end of the sphere.
  var hotspot = new THREE.Object3D();
  hotspot.position.z = -distance;
  hotspot.scale.set(NORMAL_SCALE);

  hotspot.add(inner);
  hotspot.add(outer);

  return hotspot;
};

/**
 * Large aspect ratios tend to cause visually jarring distortions on the sides.
 * Here we fade hotspots out to avoid them.
 */
HotspotRenderer.prototype.fadeOffCenterHotspots_ = function(camera) {
  var lookAt = new THREE.Vector3(1, 0, 0);
  lookAt.applyQuaternion(camera.quaternion);
  // Take into account the camera parent too.
  lookAt.applyQuaternion(camera.parent.quaternion);

  // Go through each hotspot. Calculate how far off center it is.
  for (var id in this.hotspots) {
    var hotspot = this.hotspots[id];
    var angle = hotspot.position.angleTo(lookAt);
    var angleDeg = THREE.Math.radToDeg(angle);
    var isVisible = angleDeg < 45;
    var opacity;
    if (angleDeg < FADE_START_ANGLE_DEG) {
      opacity = 1;
    } else if (angleDeg > FADE_END_ANGLE_DEG) {
      opacity = 0;
    } else {
      // We are in the case START < angle < END. Linearly interpolate.
      var range = FADE_END_ANGLE_DEG - FADE_START_ANGLE_DEG;
      var value = FADE_END_ANGLE_DEG - angleDeg;
      opacity = value / range;
    }

    // Opacity a function of angle. If angle is large, opacity is zero. At some
    // point, ramp opacity down.
    this.setOpacity_(id, opacity);
  }
};

HotspotRenderer.prototype.focus_ = function(id) {
  var hotspot = this.hotspots[id];

  var self = this;
  // Tween scale of hotspot.
  this.tween = new TWEEN.Tween(hotspot.scale).to(FOCUS_SCALE, FOCUS_DURATION)
      .easing(TWEEN.Easing.Quadratic.InOut)
      .onComplete(function() { self.emit('click'); })
      .start();

};

HotspotRenderer.prototype.blur_ = function(id) {
  var hotspot = this.hotspots[id];

  this.tween = new TWEEN.Tween(hotspot.scale).to(NORMAL_SCALE, FOCUS_DURATION)
      .easing(TWEEN.Easing.Quadratic.InOut)
      .start();
};

HotspotRenderer.prototype.down_ = function(id) {
  // Become active.
  var hotspot = this.hotspots[id];
  var outer = hotspot.getObjectByName('inner');

  this.tween = new TWEEN.Tween(outer.material.color).to(ACTIVE_COLOR, ACTIVE_DURATION)
      .start();
};

HotspotRenderer.prototype.up_ = function(id) {
  // Become inactive.
  var hotspot = this.hotspots[id];
  var outer = hotspot.getObjectByName('inner');

  this.tween = new TWEEN.Tween(outer.material.color).to(INACTIVE_COLOR, ACTIVE_DURATION)
      .start();
};

HotspotRenderer.prototype.setOpacity_ = function(id, opacity) {
  var hotspot = this.hotspots[id];
  var outer = hotspot.getObjectByName('outer');
  var inner = hotspot.getObjectByName('inner');

  outer.material.opacity = opacity * MAX_OUTER_OPACITY;
  inner.material.opacity = opacity * MAX_INNER_OPACITY;
};

module.exports = HotspotRenderer;
<commit_msg>Revert "Raji Fixing"<commit_after>/*
 * Copyright 2016 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var EventEmitter = require('eventemitter3');
var TWEEN = require('tween.js');

// Constants for the focus/blur animation.
var NORMAL_SCALE = new THREE.Vector3(1, 1, 1);
var FOCUS_SCALE = new THREE.Vector3(1.2, 1.2, 1.2);
var FOCUS_DURATION = 200;

// Constants for the active/inactive animation.
var INACTIVE_COLOR = new THREE.Color(1, 1, 1);
var ACTIVE_COLOR = new THREE.Color(0.8, 0, 0);
var ACTIVE_DURATION = 100;

// Constants for opacity.
var MAX_INNER_OPACITY = 0.8;
var MAX_OUTER_OPACITY = 0.5;
var FADE_START_ANGLE_DEG = 35;
var FADE_END_ANGLE_DEG = 60;
/**
 * Responsible for rectangular hot spots that the user can interact with.
 *
 * Specific duties:
 *   Adding and removing hotspots.
 *   Rendering the hotspots (debug mode only).
 *   Notifying when hotspots are interacted with.
 *
 * Emits the following events:
 *   click (id): a hotspot is clicked.
 *   focus (id): a hotspot is focused.
 *   blur (id): a hotspot is no longer hovered over.
 */
function HotspotRenderer(worldRenderer) {
  this.worldRenderer = worldRenderer;
  this.scene = worldRenderer.scene;

  // Note: this event must be added to document.body and not to window for it to
  // work inside iOS iframes.
  var body = document.body;
  // Bind events for hotspot interaction.
  if (!Util.isMobile()) {
    // Only enable mouse events on desktop.
    body.addEventListener('mousedown', this.onMouseDown_.bind(this), false);
    body.addEventListener('mousemove', this.onMouseMove_.bind(this), false);
    body.addEventListener('mouseup', this.onMouseUp_.bind(this), false);
  }
  body.addEventListener('touchstart', this.onTouchStart_.bind(this), false);
  body.addEventListener('touchend', this.onTouchEnd_.bind(this), false);

  // Add a placeholder for hotspots.
  this.hotspotRoot = new THREE.Object3D();
  // Align the center with the center of the camera too.
  this.hotspotRoot.rotation.y = Math.PI / 2;
  this.scene.add(this.hotspotRoot);

  // All hotspot IDs.
  this.hotspots = {};

  // Currently selected hotspots.
  this.selectedHotspots = {};

  // Hotspots that the last touchstart / mousedown event happened for.
  this.downHotspots = {};

  // For raycasting. Initialize mouse to be off screen initially.
  this.pointer = new THREE.Vector2(1, 1);
  this.raycaster = new THREE.Raycaster();
}
HotspotRenderer.prototype = new EventEmitter();

/**
 * @param pitch {Number} The latitude of center, specified in degrees, between
 * -90 and 90, with 0 at the horizon.
 * @param yaw {Number} The longitude of center, specified in degrees, between
 * -180 and 180, with 0 at the image center.
 * @param radius {Number} The radius of the hotspot, specified in meters.
 * @param distance {Number} The distance of the hotspot from camera, specified
 * in meters.
 * @param hotspotId {String} The ID of the hotspot.
 */
HotspotRenderer.prototype.add = function(pitch, yaw, radius, distance, id) {
  // If a hotspot already exists with this ID, stop.
  if (this.hotspots[id]) {
    // TODO: Proper error reporting.
    console.error('Attempt to add hotspot with existing id %s.', id);
    return;
  }
  var hotspot = this.createHotspot_(radius, distance);
  hotspot.name = id;

  // Position the hotspot based on the pitch and yaw specified.
  var quat = new THREE.Quaternion();
  quat.setFromEuler(new THREE.Euler(THREE.Math.degToRad(pitch), THREE.Math.degToRad(yaw), 0));
  hotspot.position.applyQuaternion(quat);
  hotspot.lookAt(new THREE.Vector3());

  this.hotspotRoot.add(hotspot);
  this.hotspots[id] = hotspot;
}

/**
 * Removes a hotspot based on the ID.
 *
 * @param ID {String} Identifier of the hotspot to be removed.
 */
HotspotRenderer.prototype.remove = function(id) {
  // If there's no hotspot with this ID, fail.
  if (!this.hotspots[id]) {
    // TODO: Proper error reporting.
    console.error('Attempt to remove non-existing hotspot with id %s.', id);
    return;
  }
  // Remove the mesh from the scene.
  this.hotspotRoot.remove(this.hotspots[id]);

  // If this hotspot was selected, make sure it gets unselected.
  delete this.selectedHotspots[id];
  delete this.downHotspots[id];
  delete this.hotspots[id];
  this.emit('blur', id);
};

/**
 * Clears all hotspots from the pano. Often called when changing panos.
 */
HotspotRenderer.prototype.clearAll = function() {
  for (var id in this.hotspots) {
    this.remove(id);
  }
};

HotspotRenderer.prototype.getCount = function() {
  var count = 0;
  for (var id in this.hotspots) {
    count += 1;
  }
  return count;
};

HotspotRenderer.prototype.update = function(camera) {
  if (this.worldRenderer.isVRMode()) {
    this.pointer.set(0, 0);
  }
  // Update the picking ray with the camera and mouse position.
  this.raycaster.setFromCamera(this.pointer, camera);

  // Fade hotspots out if they are really far from center to avoid overly
  // distorted visuals.
  this.fadeOffCenterHotspots_(camera);

  var hotspots = this.hotspotRoot.children;

  // Go through all hotspots to see if they are currently selected.
  for (var i = 0; i < hotspots.length; i++) {
    var hotspot = hotspots[i];
    //hotspot.lookAt(camera.position);
    var id = hotspot.name;
    // Check if hotspot is intersected with the picking ray.
    var intersects = this.raycaster.intersectObjects(hotspot.children);
    var isIntersected = (intersects.length > 0);

    // If newly selected, emit a focus event.
    if (isIntersected && !this.selectedHotspots[id]) {
      this.emit('focus', id);
      this.focus_(id);
    }
    // If no longer selected, emit a blur event.
    if (!isIntersected && this.selectedHotspots[id]) {
      this.emit('blur', id);
      this.blur_(id);
    }
    // Update the set of selected hotspots.
    if (isIntersected) {
      this.selectedHotspots[id] = true;
    } else {
      delete this.selectedHotspots[id];
    }
  }
};

/**
 * Toggle whether or not hotspots are visible.
 */
HotspotRenderer.prototype.setVisibility = function(isVisible) {
  this.hotspotRoot.visible = isVisible;
};

HotspotRenderer.prototype.onTouchStart_ = function(e) {
  // In VR mode, don't touch the pointer position.
  if (!this.worldRenderer.isVRMode()) {
    this.updateTouch_(e);
  }

  // Force a camera update to see if any hotspots were selected.
  this.update(this.worldRenderer.camera);

  this.downHotspots = {};
  for (var id in this.selectedHotspots) {
    this.downHotspots[id] = true;
    this.down_(id);
  }
  return false;
};

HotspotRenderer.prototype.onTouchEnd_ = function(e) {
  // If no hotspots are pressed, emit an empty click event.
  if (Util.isEmptyObject(this.downHotspots)) {
    this.emit('click');
    return;
  }

  // Only emit a click if the finger was down on the same hotspot before.
  for (var id in this.downHotspots) {
    this.emit('click', id);
    this.up_(id);
    e.preventDefault();
  }
};

HotspotRenderer.prototype.updateTouch_ = function(e) {
  var size = this.getSize_();
  var touch = e.touches[0];
	this.pointer.x = (touch.clientX / size.width) * 2 - 1;
	this.pointer.y = - (touch.clientY / size.height) * 2 + 1;
};

HotspotRenderer.prototype.onMouseDown_ = function(e) {
  this.updateMouse_(e);

  this.downHotspots = {};
  for (var id in this.selectedHotspots) {
    this.downHotspots[id] = true;
    this.down_(id);
  }
};

HotspotRenderer.prototype.onMouseMove_ = function(e) {
  this.updateMouse_(e);
};

HotspotRenderer.prototype.onMouseUp_ = function(e) {
  this.updateMouse_(e);

  // If no hotspots are pressed, emit an empty click event.
  if (Util.isEmptyObject(this.downHotspots)) {
    this.emit('click');
    return;
  }

  // Only emit a click if the mouse was down on the same hotspot before.
  for (var id in this.selectedHotspots) {
    if (id in this.downHotspots) {
      this.emit('click', id);
      this.up_(id);
    }
  }
};

HotspotRenderer.prototype.updateMouse_ = function(e) {
  var size = this.getSize_();
	this.pointer.x = (e.clientX / size.width) * 2 - 1;
	this.pointer.y = - (e.clientY / size.height) * 2 + 1;
};

HotspotRenderer.prototype.getSize_ = function() {
  var canvas = this.worldRenderer.renderer.domElement;
  return this.worldRenderer.renderer.getSize();
};

HotspotRenderer.prototype.createHotspot_ = function(radius, distance) {
  var innerGeometry = new THREE.CircleGeometry(radius, 32);

  var innerMaterial = new THREE.MeshBasicMaterial({
    color: 0xffffff, side: THREE.DoubleSide, transparent: true,
    opacity: MAX_INNER_OPACITY, depthTest: false
  });

  var inner = new THREE.Mesh(innerGeometry, innerMaterial);
  inner.name = 'inner';

  var outerMaterial = new THREE.MeshBasicMaterial({
    color: 0xffffff, side: THREE.DoubleSide, transparent: true,
    opacity: MAX_OUTER_OPACITY, depthTest: false
  });
  var outerGeometry = new THREE.RingGeometry(radius * 0.85, radius, 32);
  var outer = new THREE.Mesh(outerGeometry, outerMaterial);
  outer.name = 'outer';

  // Position at the extreme end of the sphere.
  var hotspot = new THREE.Object3D();
  hotspot.position.z = -distance;
  hotspot.scale.set(NORMAL_SCALE);

  hotspot.add(inner);
  hotspot.add(outer);

  return hotspot;
};

/**
 * Large aspect ratios tend to cause visually jarring distortions on the sides.
 * Here we fade hotspots out to avoid them.
 */
HotspotRenderer.prototype.fadeOffCenterHotspots_ = function(camera) {
  var lookAt = new THREE.Vector3(1, 0, 0);
  lookAt.applyQuaternion(camera.quaternion);
  // Take into account the camera parent too.
  lookAt.applyQuaternion(camera.parent.quaternion);

  // Go through each hotspot. Calculate how far off center it is.
  for (var id in this.hotspots) {
    var hotspot = this.hotspots[id];
    var angle = hotspot.position.angleTo(lookAt);
    var angleDeg = THREE.Math.radToDeg(angle);
    var isVisible = angleDeg < 45;
    var opacity;
    if (angleDeg < FADE_START_ANGLE_DEG) {
      opacity = 1;
    } else if (angleDeg > FADE_END_ANGLE_DEG) {
      opacity = 0;
    } else {
      // We are in the case START < angle < END. Linearly interpolate.
      var range = FADE_END_ANGLE_DEG - FADE_START_ANGLE_DEG;
      var value = FADE_END_ANGLE_DEG - angleDeg;
      opacity = value / range;
    }

    // Opacity a function of angle. If angle is large, opacity is zero. At some
    // point, ramp opacity down.
    this.setOpacity_(id, opacity);
  }
};

HotspotRenderer.prototype.focus_ = function(id) {
  var hotspot = this.hotspots[id];

  var self = this;
  // Tween scale of hotspot.
  this.tween = new TWEEN.Tween(hotspot.scale).to(FOCUS_SCALE, FOCUS_DURATION)
      .easing(TWEEN.Easing.Quadratic.InOut)
      .onComplete(self.emit('click'))
      .start();

};

HotspotRenderer.prototype.blur_ = function(id) {
  var hotspot = this.hotspots[id];

  this.tween = new TWEEN.Tween(hotspot.scale).to(NORMAL_SCALE, FOCUS_DURATION)
      .easing(TWEEN.Easing.Quadratic.InOut)
      .start();
};

HotspotRenderer.prototype.down_ = function(id) {
  // Become active.
  var hotspot = this.hotspots[id];
  var outer = hotspot.getObjectByName('inner');

  this.tween = new TWEEN.Tween(outer.material.color).to(ACTIVE_COLOR, ACTIVE_DURATION)
      .start();
};

HotspotRenderer.prototype.up_ = function(id) {
  // Become inactive.
  var hotspot = this.hotspots[id];
  var outer = hotspot.getObjectByName('inner');

  this.tween = new TWEEN.Tween(outer.material.color).to(INACTIVE_COLOR, ACTIVE_DURATION)
      .start();
};

HotspotRenderer.prototype.setOpacity_ = function(id, opacity) {
  var hotspot = this.hotspots[id];
  var outer = hotspot.getObjectByName('outer');
  var inner = hotspot.getObjectByName('inner');

  outer.material.opacity = opacity * MAX_OUTER_OPACITY;
  inner.material.opacity = opacity * MAX_INNER_OPACITY;
};

module.exports = HotspotRenderer;
<|endoftext|>


<commit_before>var firebase = require('firebase');
var $ = require('jquery');

$.urlParam = function(name){
    var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
    if (results==null){
       return null;
    }
    else{
       return decodeURI(results[1]) || 0;
    }
}

var ListController = function(Auth, redirect) {
  return function () {
    var userId = firebase.auth().currentUser.uid;

    //////////////// Sorting //////////////////////////////////////
    let sortMethod = "mealName";

    if($.urlParam("sort")) {
      sortMethod = $.urlParam("sort");
    }

    //Sort button handling
    $(".sort-button").click(function() {
      window.location.href = window.location.pathname + "#/list/?sort=" + $(this).attr("value");
    });

    //////////////// Searching //////////////////////////////////////
    let searchValue = false;

    if($.urlParam("search")) {
      searchValue = $.urlParam("search");
    }

    //Sort button handling
    $("#search-form").submit(function() {
      window.location.href = window.location.pathname + "#/list/?sort=" + sortMethod + "&search=" + $("#search-input").val();
    });

    // Get a reference to the database service
    var markup = '';
    var database = firebase.database();
    var query;

    if(searchValue) {
      query = firebase.database().ref("meals").orderByChild(sortMethod).equalTo(searchValue);
    }
    else {
      query = firebase.database().ref("meals").orderByChild(sortMethod);
    }

    query.once("value")
      .then(function(snapshot) {
        //showMore = Object.keys(snapshot.val()).length > 10;
        snapshot.forEach(renderSingleSnapshot);
      }).then(function(){
        $(document).find('#list').html(markup);
      });

    var renderSingleSnapshot = function(childSnapshot) {
      var meal = childSnapshot.val();
      var html = '';

      html += '
';
      html +=  '
';
      html +=   '
';

      if(meal.picture)
        html +=     '';

      html +=   '
';
      html +=   '
';
      html +=     '
'+  meal.mealName +'
';

      if(meal.rating) {

        for(var i = 0; i < 5; i++) {
          if(i < meal.rating)
            html +=     '';
          else
            html +=     '';
        }
      }

      html +=   '
';
      html +=   '
';
      html +=     'Edit';
      html +=   '
';
      html +=  '
';
      html += '
';

      markup += html;
    }
  }
}

module.exports = ListController;
<commit_msg>Remove thumbnail images<commit_after>var firebase = require('firebase');
var $ = require('jquery');

$.urlParam = function(name){
    var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
    if (results==null){
       return null;
    }
    else{
       return decodeURI(results[1]) || 0;
    }
}

var ListController = function(Auth, redirect) {
  return function () {
    var userId = firebase.auth().currentUser.uid;

    //////////////// Sorting //////////////////////////////////////
    let sortMethod = "mealName";

    if($.urlParam("sort")) {
      sortMethod = $.urlParam("sort");
    }

    //Sort button handling
    $(".sort-button").click(function() {
      window.location.href = window.location.pathname + "#/list/?sort=" + $(this).attr("value");
    });

    //////////////// Searching //////////////////////////////////////
    let searchValue = false;

    if($.urlParam("search")) {
      searchValue = $.urlParam("search");
    }

    //Sort button handling
    $("#search-form").submit(function() {
      window.location.href = window.location.pathname + "#/list/?sort=" + sortMethod + "&search=" + $("#search-input").val();
    });

    // Get a reference to the database service
    var markup = '';
    var database = firebase.database();
    var query;

    if(searchValue) {
      query = firebase.database().ref("meals").orderByChild(sortMethod).equalTo(searchValue);
    }
    else {
      query = firebase.database().ref("meals").orderByChild(sortMethod);
    }

    query.once("value")
      .then(function(snapshot) {
        //showMore = Object.keys(snapshot.val()).length > 10;
        snapshot.forEach(renderSingleSnapshot);
      }).then(function(){
        $(document).find('#list').html(markup);
      });

    var renderSingleSnapshot = function(childSnapshot) {
      var meal = childSnapshot.val();
      var html = '';

      html += '
';
      html +=  '
';
      html +=   '
';
      html +=     '
'+  meal.mealName +'
';

      if(meal.rating) {

        for(var i = 0; i < 5; i++) {
          if(i < meal.rating)
            html +=     '';
          else
            html +=     '';
        }
      }

      html +=   '
';
      html +=   '
';
      html +=     'Edit';
      html +=   '
';
      html +=  '
';
      html += '
';

      markup += html;
    }
  }
}

module.exports = ListController;
<|endoftext|>