<commit_before>/* global define, module */

/**
 * Tablist module
 * @module Tablist
 * @param {Object} window - Browser window object
 * @param {Object} document - Browser document object
 * @param {string} exportName - Module export name. Constructor reference will be accessible on window[exportName].
 */
(function(window, document, exportName) {
  'use strict';

  /**
   * Tablist constructor
   * @constructor
   * @param {Node} elm - DOM node
   */
  var Tablist = function( elm, options ){

    var tablist = elm;

    /**
     * Close all tabs
     * @param {boolean} [silent] - If closing tabs should dispatch events
     */
    this.closeTabs = function closeTabs( silent ){
      var openedTabs;

      // kill switch if there's no opened tab or if it's not an accordion
      if( !tablist.openedTab.length || null === tablist.multiselectable ){
        return;
      }

      // copy the opened tabs array to close them all
      openedTabs = Array.prototype.slice.call( tablist.openedTab );

      // loop on each opened tab
      openedTabs.forEach(( function( tab ){
        // close the tab without triggering any event
        if( silent ){
          this.toggleDisplay( tab );
        }
        // close with triggering events
        else{
          tab.click();
        }
      }).bind( this ));

      if ( !silent && null !== tablist.multiselectable ) {
        options.closeAll( openedTabs );
      }
    };

    /**
     * Retrieve first activable tab (that does not have `disabled` attribute)
     */
    this.firstActiveTab = function firstActiveTab() {
      var i = 0,
          activeTab;

      for(; i < tablist.tabs.length; i++ ) {
        if ( !tablist.tabs[ i ].disabled) {
          activeTab = i;
          break;
        }
      }

      return activeTab;
    };

    /**
     * Returns the opened tab or array of opened tabs
     */
    this.getOpenedTabs = function(){
      return tablist.openedTab.slice();
    };

    /**
     * Toggle display of the tabPanel (show/hide)
     * @param {DOMEvent} e - Can be a `MouseEvent` or a `KeyboardEvent` object
     */
    this.handleDisplay = function handleDisplay( e ){
      e.preventDefault();

      var tab = e.currentTarget;

      if( tab.disabled ){
        return;
      }

      // ensure the tab has the focus when a click occurs
      if( tab !== document.activeElement ){
        tab.focus();
      }

      this.toggleDisplay( tab );
    };

    /**
     * Update the current tab index before selecting the current tab
     * @param {DOMEvent} e - A `FocusEvent` object
     */
    this.handleFocus = function handleFocus( e ){
      var tab = e.currentTarget;

      if( tab.disabled ){
        return;
      }

      tablist.currentTabIndex = tablist.tabs.indexOf( tab );

      this.select( tablist.tabs[ tablist.currentTabIndex ] );
    };

    /**
     * Handle keystroke on [role=tabpanel]
     * @param {DOMEvent} e - A `KeyboardEvent` object
     */
    this.handlePanel = function handlePanel( e ){

      if ( tablist.currentTabIndex === undefined ) {
        this.handlePanelFocus( e );
      }

      switch( e.keyCode ){
        case 33: // ctrl + page up
          if( e.ctrlKey ){
            e.preventDefault();
            // focus the previous tab
            this.switchTab( tablist.currentTabIndex - 1 );
          }
          break;

        case 34: // ctrl + page down
          if( e.ctrlKey ){
            e.preventDefault();
            // focus the next tab
            this.switchTab( tablist.currentTabIndex + 1 );
          }
          break;

        // focus back to tab
        case 38: // ctrl + up
          if( e.ctrlKey ){
            e.preventDefault();
            // focus linked tab
            this.switchTab( tablist.currentTabIndex );
          }
          break;
      }
    };

    /**
     * Ensure that the current tab index is the one matching the tabPanel
     * @param {DOMEvent} e - A `FocusEvent` or `KeyboardEvent` object
     */
    this.handlePanelFocus = function handlePanelFocus( e ){
      e.preventDefault();

      if( e.target.doubleFocus ){
        delete e.target.doubleFocus;
        return;
      }

      var tabPanel = e.currentTarget,
          tab;

      tab = tabPanel.tab;

      tablist.currentTabIndex = tablist.tabs.indexOf( tab );

      // prevent double focus event when the inputs are focused
      if( [ 'radio', 'checkbox' ].indexOf( e.target.type ) >= 0 ){
        e.target.doubleFocus = true;
      }
    };

    /**
     * Handle keystroke on [role=tab]
     * @param {DOMEvent} e - A `KeyboardEvent` object
     */
    this.handleTab = function handleTab( e ){

      if ( tablist.currentTabIndex === undefined ) {
        this.handleFocus( e );
      }

      switch( e.keyCode ){
        case 32: // space
        case 13: // return
          // toggle the display of the linked tabpanel
          this.handleDisplay( e );
          break;

        case 35: // end
          e.preventDefault();
          // focus the last tab
          this.switchTab( tablist.tabs.length - 1 );
          break;

        case 36: // home
          e.preventDefault();
          // focus the first active tab
          this.switchTab( this.firstActiveTab() );
          break;

        case 37: // left
        case 38: // up
          e.preventDefault();
          // focus the previous tab
          this.switchTab( tablist.currentTabIndex - 1 );
          break;

        case 39: // right
        case 40: // down
          e.preventDefault();
          // focus the next tab
          this.switchTab( tablist.currentTabIndex + 1 );
          break;
      }
    };

    /**
     * Kickstart Tablist instance
     */
    this.init = function init(){

      if ( !elm || !elm.nodeName ) {
        throw new Error( 'No DOM node provided. Abort.' );
      }

      options = {
        closeAll: options && options.closeAll || this.noop,
        closeTab: options && options.closeTab || this.noop,
        openTab: options && options.openTab || this.noop
      };

      this.setTablist();
    };

    /**
     * Dummy function
     */
    this.noop = function(){};

    /**
     * Update tab selected attributes (`aria-selected`, `tabindex`)
     * based on the `tabToSelect` attribute
     * @param {DOMElement} tabToSelect - Tab element to select
     */
    this.select = function select( tabToSelect ){
      var shouldSelect;

      // loop on each tab
      tablist.tabs.forEach(( function( tab ){
        shouldSelect = tabToSelect === tab;

        tab.setAttribute( 'aria-selected', shouldSelect );
        tab.setAttribute( 'tabindex', shouldSelect ? 0 : -1 );

        // only for tab to be selected
        if ( shouldSelect ) {
          // if it's not an accordion also open the linked tabpanel
          if( null === tablist.multiselectable ){
            this.toggleDisplay( tab );
          }
        }
      }).bind( this ));
    };

    /**
     * Parse tablist element to setup the tab and tabpanel elements
     */
    this.setTablist = function setTablist(){
      var controls,
          openedTab,
          firstTab;

      // create reference arrays
      tablist.tabs = [];
      tablist.tabPanels = [];
      tablist.openedTab = [];

      // set multiselectable to `true`, `false` or `null`
      // a `null` value will define a tab setup instead of an accordion
      tablist.multiselectable = tablist.attributes[ 'aria-multiselectable' ] ? tablist.attributes[ 'aria-multiselectable' ].value === 'true' : null;

      // loop on each tab elements to find tabpanel elements and update their attributes
      Array.prototype.forEach.call( tablist.querySelectorAll( '[role=tab]' ), ( function( tab ){
        openedTab = false;
        controls = tab.getAttribute( 'aria-controls' );
        // link the tabpanel to the tab element
        if( controls ){
          tab.tabPanel = document.getElementById( controls );
        }
        else if( tab.nextElementSibling && tab.nextElementSibling.getAttribute( 'aria-labelledby' ) === tab.id ){
          tab.tabPanel = tab.nextElementSibling;
        }

        if( !tab.tabPanel ){
          throw new Error( 'Could not find associated tabpanel for tab '+tab.id+'. Use [aria-controls="tabpanelId"] on the [role=tab] element to link them together' );
        }

        // link the tab to the tabpanel element
        tab.tabPanel.tab = tab;

        tab.tabList = tablist;

        tab.disabled = tab.hasAttribute( 'disabled' ) || tab.getAttribute( 'aria-disabled' ) === "true";

        // if tablist is not multiselectable and there's no opened tab yet
        if( tab.hasAttribute( 'data-tab-open' ) && !tab.disabled ){
          if( (!tablist.multiselectable && !tablist.openedTab.length) || tablist.multiselectable ){
            // store the tab in the openedTab array
            tablist.openedTab.push( tab );
            openedTab = true;

          }
        }

        // remove setup data attributes
        tab.removeAttribute( 'data-tab-open' );

        // get first non-disabled tab
        if( !firstTab && !tab.disabled ){
          firstTab = tab;
        }

        // set the attributes according the the openedTab status
        tab.setAttribute( 'tabindex', -1 );
        tab.setAttribute( 'aria-expanded', openedTab );
        tab.tabPanel.setAttribute( 'aria-hidden', !openedTab );

        // store the tab and the tabpanel on their respective arrays on the tablist
        tablist.tabs.push( tab );
        tablist.tabPanels.push( tab.tabPanel );

        // subscribe internal events for tab and tap panel
        tab.addEventListener( 'click', this.handleDisplay.bind( this ));
        tab.addEventListener( 'focus', this.handleFocus.bind( this ));
        tab.addEventListener( 'keydown', this.handleTab.bind( this ));
        tab.tabPanel.addEventListener( 'focus', this.handlePanelFocus.bind( this ));
        tab.tabPanel.addEventListener( 'keydown', this.handlePanel.bind( this ));
      }).bind( this ));

      // store constants
      tablist.tabsLength = tablist.tabs.length;
      tablist.tabPanelsLength = tablist.tabPanels.length;

      // set the tabindex so the first opened tab or the first non-disabled tab can be focused on tab navigation
      if( tablist.openedTab.length ){
        tablist.openedTab[0].setAttribute( 'tabindex', 0 );
      }
      // if there's no opened tab and it's not an accordion open the first tab
      else if( !tablist.openedTab.length && null === tablist.multiselectable ){
        firstTab.setAttribute( 'tabindex', 0 );
        firstTab.setAttribute( 'aria-expanded', true );
        firstTab.tabPanel.setAttribute( 'aria-hidden', false );
        tablist.openedTab.push( firstTab );
      }
      else{
        firstTab.setAttribute( 'tabindex', 0 );
      }

    };

    /**
     * Move the focus to the tab based on the index
     * @param {number} index - Index of the element to focus
     */
    this.switchTab = function switchTab( index ){

      // handle disabled tab
      var newIndex;
      if ( tablist.tabs[ index ] && tablist.tabs[ index ].disabled) {

        // cycling forward? Then go one item farther
        newIndex = index > tablist.currentTabIndex ? index + 1 : index - 1;

        this.switchTab( newIndex );
        return;
      }

      tablist.currentTabIndex = index;

      if( tablist.currentTabIndex < this.firstActiveTab() ){
        tablist.currentTabIndex = tablist.tabsLength - 1;
      }
      else if( tablist.currentTabIndex >= tablist.tabsLength ){
        tablist.currentTabIndex = this.firstActiveTab();
      }

      tablist.tabs[ tablist.currentTabIndex ].focus();
    };

    /**
     * Toggle the `aria-expanded` attribute on the tabpanel based on the passed tab
     * @param {DOMElement} tab - Tab element
     */
    this.toggleDisplay = function toggleDisplay( tab ){
      var tabPanel,
          expanded,
          openedIndex;

      tabPanel = tab.tabPanel;
      // get the current expanded status
      expanded = tab.getAttribute( 'aria-expanded' ) === "true";

      // don't set `aria-expanded` to `false` when it's not an accordion
      if( null === tablist.multiselectable ){
        if ( expanded ) {
          return;
        }

        // execute close tab callback
        // tab mode: so only 1 tab can be opened at once
        options.closeTab( tablist.openedTab[ 0 ]);
      }

      // if the tab is currently expanded it has to be closed
      if( expanded ){
        // find the tab index in the openedTab array
        openedIndex = tablist.openedTab.indexOf( tab );
        // remove it from the array of opened tabs
        tablist.openedTab.splice( openedIndex, 1 );

        // execute close tab callback
        options.closeTab( tab );
      }

      else {
        // if the tablist is not multiselectable
        // loop on each openedTab to close it
        if( !tablist.multiselectable ){
          tablist.openedTab.forEach( function( openedTab ){
            openedTab.setAttribute( 'aria-expanded', false );
            openedTab.tabPanel.setAttribute( 'aria-hidden', true );
            options.closeTab( openedTab );
          });
          // empty the array of opened tab because we closed them all
          tablist.openedTab.length = 0;
        }
        // add the current tab to the array of opened tabs
        tablist.openedTab.push( tab );

        // execute open tab callback
        options.openTab( tab );
      }

      // reverse the value of the `aria-expanded` attribute
      tab.setAttribute( 'aria-expanded', !expanded );
      // update the `aria-hidden` attribute on the tabpanel
      tabPanel.setAttribute( 'aria-hidden', expanded );
    };

    // init Tablist instance
    this.init();

    // expose what user might need
    return {
      closeAll: this.closeTabs,
      openedTab: this.getOpenedTabs,
    };
  };

  // Export our constructor
  if (typeof define === 'function' && define.amd) {
    define(function() {
      return Tablist;
    });
  }

  else if (typeof module !== 'undefined' && module.exports) {
    module.exports = Tablist;
  }

  else {
    window[exportName] = Tablist;
  }

})(window, document, 'Tablist');
<commit_msg>prevent default only in case of double focus<commit_after>/* global define, module */

/**
 * Tablist module
 * @module Tablist
 * @param {Object} window - Browser window object
 * @param {Object} document - Browser document object
 * @param {string} exportName - Module export name. Constructor reference will be accessible on window[exportName].
 */
(function(window, document, exportName) {
  'use strict';

  /**
   * Tablist constructor
   * @constructor
   * @param {Node} elm - DOM node
   */
  var Tablist = function( elm, options ){

    var tablist = elm;

    /**
     * Close all tabs
     * @param {boolean} [silent] - If closing tabs should dispatch events
     */
    this.closeTabs = function closeTabs( silent ){
      var openedTabs;

      // kill switch if there's no opened tab or if it's not an accordion
      if( !tablist.openedTab.length || null === tablist.multiselectable ){
        return;
      }

      // copy the opened tabs array to close them all
      openedTabs = Array.prototype.slice.call( tablist.openedTab );

      // loop on each opened tab
      openedTabs.forEach(( function( tab ){
        // close the tab without triggering any event
        if( silent ){
          this.toggleDisplay( tab );
        }
        // close with triggering events
        else{
          tab.click();
        }
      }).bind( this ));

      if ( !silent && null !== tablist.multiselectable ) {
        options.closeAll( openedTabs );
      }
    };

    /**
     * Retrieve first activable tab (that does not have `disabled` attribute)
     */
    this.firstActiveTab = function firstActiveTab() {
      var i = 0,
          activeTab;

      for(; i < tablist.tabs.length; i++ ) {
        if ( !tablist.tabs[ i ].disabled) {
          activeTab = i;
          break;
        }
      }

      return activeTab;
    };

    /**
     * Returns the opened tab or array of opened tabs
     */
    this.getOpenedTabs = function(){
      return tablist.openedTab.slice();
    };

    /**
     * Toggle display of the tabPanel (show/hide)
     * @param {DOMEvent} e - Can be a `MouseEvent` or a `KeyboardEvent` object
     */
    this.handleDisplay = function handleDisplay( e ){
      e.preventDefault();

      var tab = e.currentTarget;

      if( tab.disabled ){
        return;
      }

      // ensure the tab has the focus when a click occurs
      if( tab !== document.activeElement ){
        tab.focus();
      }

      this.toggleDisplay( tab );
    };

    /**
     * Update the current tab index before selecting the current tab
     * @param {DOMEvent} e - A `FocusEvent` object
     */
    this.handleFocus = function handleFocus( e ){
      var tab = e.currentTarget;

      if( tab.disabled ){
        return;
      }

      tablist.currentTabIndex = tablist.tabs.indexOf( tab );

      this.select( tablist.tabs[ tablist.currentTabIndex ] );
    };

    /**
     * Handle keystroke on [role=tabpanel]
     * @param {DOMEvent} e - A `KeyboardEvent` object
     */
    this.handlePanel = function handlePanel( e ){

      if ( tablist.currentTabIndex === undefined ) {
        this.handlePanelFocus( e );
      }

      switch( e.keyCode ){
        case 33: // ctrl + page up
          if( e.ctrlKey ){
            e.preventDefault();
            // focus the previous tab
            this.switchTab( tablist.currentTabIndex - 1 );
          }
          break;

        case 34: // ctrl + page down
          if( e.ctrlKey ){
            e.preventDefault();
            // focus the next tab
            this.switchTab( tablist.currentTabIndex + 1 );
          }
          break;

        // focus back to tab
        case 38: // ctrl + up
          if( e.ctrlKey ){
            e.preventDefault();
            // focus linked tab
            this.switchTab( tablist.currentTabIndex );
          }
          break;
      }
    };

    /**
     * Ensure that the current tab index is the one matching the tabPanel
     * @param {DOMEvent} e - A `FocusEvent` or `KeyboardEvent` object
     */
    this.handlePanelFocus = function handlePanelFocus( e ){

      if( e.target.doubleFocus ){
        e.preventDefault();
        delete e.target.doubleFocus;
        return;
      }

      var tabPanel = e.currentTarget,
          tab;

      tab = tabPanel.tab;

      tablist.currentTabIndex = tablist.tabs.indexOf( tab );

      // prevent double focus event when the inputs are focused
      if( [ 'radio', 'checkbox' ].indexOf( e.target.type ) >= 0 ){
        e.target.doubleFocus = true;
      }
    };

    /**
     * Handle keystroke on [role=tab]
     * @param {DOMEvent} e - A `KeyboardEvent` object
     */
    this.handleTab = function handleTab( e ){

      if ( tablist.currentTabIndex === undefined ) {
        this.handleFocus( e );
      }

      switch( e.keyCode ){
        case 32: // space
        case 13: // return
          // toggle the display of the linked tabpanel
          this.handleDisplay( e );
          break;

        case 35: // end
          e.preventDefault();
          // focus the last tab
          this.switchTab( tablist.tabs.length - 1 );
          break;

        case 36: // home
          e.preventDefault();
          // focus the first active tab
          this.switchTab( this.firstActiveTab() );
          break;

        case 37: // left
        case 38: // up
          e.preventDefault();
          // focus the previous tab
          this.switchTab( tablist.currentTabIndex - 1 );
          break;

        case 39: // right
        case 40: // down
          e.preventDefault();
          // focus the next tab
          this.switchTab( tablist.currentTabIndex + 1 );
          break;
      }
    };

    /**
     * Kickstart Tablist instance
     */
    this.init = function init(){

      if ( !elm || !elm.nodeName ) {
        throw new Error( 'No DOM node provided. Abort.' );
      }

      options = {
        closeAll: options && options.closeAll || this.noop,
        closeTab: options && options.closeTab || this.noop,
        openTab: options && options.openTab || this.noop
      };

      this.setTablist();
    };

    /**
     * Dummy function
     */
    this.noop = function(){};

    /**
     * Update tab selected attributes (`aria-selected`, `tabindex`)
     * based on the `tabToSelect` attribute
     * @param {DOMElement} tabToSelect - Tab element to select
     */
    this.select = function select( tabToSelect ){
      var shouldSelect;

      // loop on each tab
      tablist.tabs.forEach(( function( tab ){
        shouldSelect = tabToSelect === tab;

        tab.setAttribute( 'aria-selected', shouldSelect );
        tab.setAttribute( 'tabindex', shouldSelect ? 0 : -1 );

        // only for tab to be selected
        if ( shouldSelect ) {
          // if it's not an accordion also open the linked tabpanel
          if( null === tablist.multiselectable ){
            this.toggleDisplay( tab );
          }
        }
      }).bind( this ));
    };

    /**
     * Parse tablist element to setup the tab and tabpanel elements
     */
    this.setTablist = function setTablist(){
      var controls,
          openedTab,
          firstTab;

      // create reference arrays
      tablist.tabs = [];
      tablist.tabPanels = [];
      tablist.openedTab = [];

      // set multiselectable to `true`, `false` or `null`
      // a `null` value will define a tab setup instead of an accordion
      tablist.multiselectable = tablist.attributes[ 'aria-multiselectable' ] ? tablist.attributes[ 'aria-multiselectable' ].value === 'true' : null;

      // loop on each tab elements to find tabpanel elements and update their attributes
      Array.prototype.forEach.call( tablist.querySelectorAll( '[role=tab]' ), ( function( tab ){
        openedTab = false;
        controls = tab.getAttribute( 'aria-controls' );
        // link the tabpanel to the tab element
        if( controls ){
          tab.tabPanel = document.getElementById( controls );
        }
        else if( tab.nextElementSibling && tab.nextElementSibling.getAttribute( 'aria-labelledby' ) === tab.id ){
          tab.tabPanel = tab.nextElementSibling;
        }

        if( !tab.tabPanel ){
          throw new Error( 'Could not find associated tabpanel for tab '+tab.id+'. Use [aria-controls="tabpanelId"] on the [role=tab] element to link them together' );
        }

        // link the tab to the tabpanel element
        tab.tabPanel.tab = tab;

        tab.tabList = tablist;

        tab.disabled = tab.hasAttribute( 'disabled' ) || tab.getAttribute( 'aria-disabled' ) === "true";

        // if tablist is not multiselectable and there's no opened tab yet
        if( tab.hasAttribute( 'data-tab-open' ) && !tab.disabled ){
          if( (!tablist.multiselectable && !tablist.openedTab.length) || tablist.multiselectable ){
            // store the tab in the openedTab array
            tablist.openedTab.push( tab );
            openedTab = true;

          }
        }

        // remove setup data attributes
        tab.removeAttribute( 'data-tab-open' );

        // get first non-disabled tab
        if( !firstTab && !tab.disabled ){
          firstTab = tab;
        }

        // set the attributes according the the openedTab status
        tab.setAttribute( 'tabindex', -1 );
        tab.setAttribute( 'aria-expanded', openedTab );
        tab.tabPanel.setAttribute( 'aria-hidden', !openedTab );

        // store the tab and the tabpanel on their respective arrays on the tablist
        tablist.tabs.push( tab );
        tablist.tabPanels.push( tab.tabPanel );

        // subscribe internal events for tab and tap panel
        tab.addEventListener( 'click', this.handleDisplay.bind( this ));
        tab.addEventListener( 'focus', this.handleFocus.bind( this ));
        tab.addEventListener( 'keydown', this.handleTab.bind( this ));
        tab.tabPanel.addEventListener( 'focus', this.handlePanelFocus.bind( this ));
        tab.tabPanel.addEventListener( 'keydown', this.handlePanel.bind( this ));
      }).bind( this ));

      // store constants
      tablist.tabsLength = tablist.tabs.length;
      tablist.tabPanelsLength = tablist.tabPanels.length;

      // set the tabindex so the first opened tab or the first non-disabled tab can be focused on tab navigation
      if( tablist.openedTab.length ){
        tablist.openedTab[0].setAttribute( 'tabindex', 0 );
      }
      // if there's no opened tab and it's not an accordion open the first tab
      else if( !tablist.openedTab.length && null === tablist.multiselectable ){
        firstTab.setAttribute( 'tabindex', 0 );
        firstTab.setAttribute( 'aria-expanded', true );
        firstTab.tabPanel.setAttribute( 'aria-hidden', false );
        tablist.openedTab.push( firstTab );
      }
      else{
        firstTab.setAttribute( 'tabindex', 0 );
      }

    };

    /**
     * Move the focus to the tab based on the index
     * @param {number} index - Index of the element to focus
     */
    this.switchTab = function switchTab( index ){

      // handle disabled tab
      var newIndex;
      if ( tablist.tabs[ index ] && tablist.tabs[ index ].disabled) {

        // cycling forward? Then go one item farther
        newIndex = index > tablist.currentTabIndex ? index + 1 : index - 1;

        this.switchTab( newIndex );
        return;
      }

      tablist.currentTabIndex = index;

      if( tablist.currentTabIndex < this.firstActiveTab() ){
        tablist.currentTabIndex = tablist.tabsLength - 1;
      }
      else if( tablist.currentTabIndex >= tablist.tabsLength ){
        tablist.currentTabIndex = this.firstActiveTab();
      }

      tablist.tabs[ tablist.currentTabIndex ].focus();
    };

    /**
     * Toggle the `aria-expanded` attribute on the tabpanel based on the passed tab
     * @param {DOMElement} tab - Tab element
     */
    this.toggleDisplay = function toggleDisplay( tab ){
      var tabPanel,
          expanded,
          openedIndex;

      tabPanel = tab.tabPanel;
      // get the current expanded status
      expanded = tab.getAttribute( 'aria-expanded' ) === "true";

      // don't set `aria-expanded` to `false` when it's not an accordion
      if( null === tablist.multiselectable ){
        if ( expanded ) {
          return;
        }

        // execute close tab callback
        // tab mode: so only 1 tab can be opened at once
        options.closeTab( tablist.openedTab[ 0 ]);
      }

      // if the tab is currently expanded it has to be closed
      if( expanded ){
        // find the tab index in the openedTab array
        openedIndex = tablist.openedTab.indexOf( tab );
        // remove it from the array of opened tabs
        tablist.openedTab.splice( openedIndex, 1 );

        // execute close tab callback
        options.closeTab( tab );
      }

      else {
        // if the tablist is not multiselectable
        // loop on each openedTab to close it
        if( !tablist.multiselectable ){
          tablist.openedTab.forEach( function( openedTab ){
            openedTab.setAttribute( 'aria-expanded', false );
            openedTab.tabPanel.setAttribute( 'aria-hidden', true );
            options.closeTab( openedTab );
          });
          // empty the array of opened tab because we closed them all
          tablist.openedTab.length = 0;
        }
        // add the current tab to the array of opened tabs
        tablist.openedTab.push( tab );

        // execute open tab callback
        options.openTab( tab );
      }

      // reverse the value of the `aria-expanded` attribute
      tab.setAttribute( 'aria-expanded', !expanded );
      // update the `aria-hidden` attribute on the tabpanel
      tabPanel.setAttribute( 'aria-hidden', expanded );
    };

    // init Tablist instance
    this.init();

    // expose what user might need
    return {
      closeAll: this.closeTabs,
      openedTab: this.getOpenedTabs,
    };
  };

  // Export our constructor
  if (typeof define === 'function' && define.amd) {
    define(function() {
      return Tablist;
    });
  }

  else if (typeof module !== 'undefined' && module.exports) {
    module.exports = Tablist;
  }

  else {
    window[exportName] = Tablist;
  }

})(window, document, 'Tablist');
<|endoftext|>


<commit_before>// Try intercept traces links in Rails 4.2.
var traceFrames = document.getElementsByClassName('trace-frames');
var selectedFrame, currentSource = document.getElementById('frame-source-0');

// Add click listeners for all stack frames
for (var i = 0; i < traceFrames.length; i++) {
  traceFrames[i].addEventListener('click', function(e) {
    e.preventDefault();
    var target = e.target;
    var frameId = target.dataset.frameId;

    // Change the binding of the console.
    changeBinding(frameId, function() {
      if (selectedFrame) {
        selectedFrame.className = selectedFrame.className.replace("selected", "");
      }

      target.className += " selected";
      selectedFrame = target;
    });

    // Change the extracted source code
    changeSourceExtract(frameId);
  });
}

function changeBinding(frameId, callback) {
  var consoleEl = document.getElementById('console');
  if (! consoleEl) { return; }
  var url = consoleEl.dataset.remotePath + "/trace";
  var params = "frame_id=" + encodeURIComponent(frameId);
  var xhr = new XMLHttpRequest();
  xhr.open("POST", url, true);
  xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  xhr.send(params);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      callback();
    }
  }
}

function changeSourceExtract(frameId) {
  var el = document.getElementById('frame-source-' + frameId);
  if (currentSource && el) {
    currentSource.className += " hidden";
    el.className = el.className.replace(" hidden", "");
    currentSource = el;
  }
}
<commit_msg>Push the error page body upwards the size of the console.<commit_after>// Try intercept traces links in Rails 4.2.
var traceFrames = document.getElementsByClassName('trace-frames');
var selectedFrame, currentSource = document.getElementById('frame-source-0');

// Add click listeners for all stack frames
for (var i = 0; i < traceFrames.length; i++) {
  traceFrames[i].addEventListener('click', function(e) {
    e.preventDefault();
    var target = e.target;
    var frameId = target.dataset.frameId;

    // Change the binding of the console.
    changeBinding(frameId, function() {
      if (selectedFrame) {
        selectedFrame.className = selectedFrame.className.replace("selected", "");
      }

      target.className += " selected";
      selectedFrame = target;
    });

    // Change the extracted source code
    changeSourceExtract(frameId);
  });
}

function changeBinding(frameId, callback) {
  var consoleEl = document.getElementById('console');
  if (! consoleEl) { return; }
  var url = consoleEl.dataset.remotePath + "/trace";
  var params = "frame_id=" + encodeURIComponent(frameId);
  var xhr = new XMLHttpRequest();
  xhr.open("POST", url, true);
  xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  xhr.send(params);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      callback();
    }
  }
}

function changeSourceExtract(frameId) {
  var el = document.getElementById('frame-source-' + frameId);
  if (currentSource && el) {
    currentSource.className += " hidden";
    el.className = el.className.replace(" hidden", "");
    currentSource = el;
  }
}

// Push the error page body upwards the size of the console.
//
// While, I wouldn't like to do that on every custom page (so I don't screw
// user's layouts), I think a lot of developers want to see all of the content
// on the default Rails error page.
//
// Since it's quite special as is now, being a bit more special in the name of
// better user experience, won't hurt.
document.addEventListener('DOMContentLoaded', function() {
  var consoleElement = document.getElementById('console');
  var resizerElement = document.getElementById('resizer')
  var containerElement = document.getElementById('container');

  function setContainerElementBottomMargin(pixels) {
    containerElement.style.marginBottom = pixels + 'px';
  }

  var currentConsoleElementHeight = consoleElement.offsetHeight;
  setContainerElementBottomMargin(currentConsoleElementHeight);

  resizerElement.addEventListener('mousedown', function(event) {
    function recordConsoleElementHeight(event) {
      resizerElement.removeEventListener('mouseup', recordConsoleElementHeight);

      var currentConsoleElementHeight = consoleElement.offsetHeight;
      setContainerElementBottomMargin(currentConsoleElementHeight);
    }

    resizerElement.addEventListener('mouseup', recordConsoleElementHeight);
  });
});
<|endoftext|>


<commit_before>var Trickle = function (requestsPerInterval, interval) {
	this.requestsPerInterval = requestsPerInterval;
	this.interval = interval;
	this.availableRequests = requestsPerInterval;

	this.lastReload = new Date();
};

Trickle.prototype.trickle = function (requests, callback) {
	if (requests > this.requestsPerInterval) {
		var error = { 'code' : 42, 'message' : 'It\'s not gonna happen, ever.' };
		callback(error);
		return;
	}

	var self = this;
	this.reload();

	if (requests > this.availableRequests) {
		var wait = Math.ceil((requests - this.availableRequests) * (this.interval / this.requestsPerInterval));
		setTimeout(function() { self.trickle(requests, callback); }, wait);
		return false;
	}

	this.availableRequests -= requests;
	callback(null);
};

Trickle.prototype.reload = function () {
    var now = new Date();
    var delta = Math.min(now - this.lastReload, this.interval);
    this.lastReload = now;

    var reload = delta * (this.requestsPerInterval / this.interval);
    this.availableRequests = Math.min(this.availableRequests + reload, this.requestsPerInterval);
};

exports.Trickle = Trickle;
<commit_msg>Added version<commit_after>var VERSION = '0.0.1';

var Trickle = function (requestsPerInterval, interval) {
	this.requestsPerInterval = requestsPerInterval;
	this.interval = interval;
	this.availableRequests = requestsPerInterval;

	this.lastReload = new Date();
};

Trickle.prototype.trickle = function (requests, callback) {
	if (requests > this.requestsPerInterval) {
		var error = { 'code' : 42, 'message' : 'It\'s not gonna happen, ever.' };
		callback(error);
		return;
	}

	var self = this;
	this.reload();

	if (requests > this.availableRequests) {
		var wait = Math.ceil((requests - this.availableRequests) * (this.interval / this.requestsPerInterval));
		setTimeout(function() { self.trickle(requests, callback); }, wait);
		return false;
	}

	this.availableRequests -= requests;
	callback(null);
};

Trickle.prototype.reload = function () {
    var now = new Date();
    var delta = Math.min(now - this.lastReload, this.interval);
    this.lastReload = now;

    var reload = delta * (this.requestsPerInterval / this.interval);
    this.availableRequests = Math.min(this.availableRequests + reload, this.requestsPerInterval);
};

exports.VERSION = VERSION;
exports.Trickle = Trickle;
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2016 Guyon Roche
 * LICENCE: MIT - please refer to LICENCE file included with this module
 * or https://github.com/guyonroche/exceljs/blob/master/LICENSE
 */

'use strict';

var _ = require('../../../utils/under-dash');

var utils = require('../../../utils/utils');
var XmlStream = require('../../../utils/xml-stream');

var RelType = require('../../rel-type');

var Merges = require('./merges');

var BaseXform = require('../base-xform');
var ListXform = require('../list-xform');
var RowXform = require('./row-xform');
var ColXform = require('./col-xform');
var DimensionXform = require('./dimension-xform');
var HyperlinkXform = require('./hyperlink-xform');
var MergeCellXform = require('./merge-cell-xform');
var DataValidationsXform = require('./data-validations-xform');
var SheetPropertiesXform = require('./sheet-properties-xform');
var SheetFormatPropertiesXform = require('./sheet-format-properties-xform');
var SheetViewXform = require('./sheet-view-xform');
var PageMarginsXform = require('./page-margins-xform');
var PageSetupXform = require('./page-setup-xform');
var PrintOptionsXform = require('./print-options-xform');
var AutoFilterXform = require('./auto-filter-xform');
var PictureXform = require('./picture-xform');
var DrawingXform = require('./drawing-xform');
var RowBreaksXform = require('./row-breaks-xform');

var WorkSheetXform = module.exports = function(options) {
  var maxRows = options && options.maxRows;
  this.map = {
    sheetPr: new SheetPropertiesXform(),
    dimension: new DimensionXform(),
    sheetViews: new ListXform({tag: 'sheetViews', count: false, childXform: new SheetViewXform()}),
    sheetFormatPr: new SheetFormatPropertiesXform(),
    cols: new ListXform({tag: 'cols', count: false, childXform: new ColXform()}),
    sheetData: new ListXform({tag: 'sheetData', count: false, empty: true, childXform: new RowXform(), maxItems: maxRows}),
    autoFilter: new AutoFilterXform(),
    mergeCells: new ListXform({tag: 'mergeCells', count: true, childXform: new MergeCellXform()}),
    rowBreaks: new RowBreaksXform(),
    hyperlinks: new ListXform({tag: 'hyperlinks', count: false, childXform: new HyperlinkXform()}),
    pageMargins: new PageMarginsXform(),
    dataValidations: new DataValidationsXform(),
    pageSetup: new PageSetupXform(),
    printOptions: new PrintOptionsXform(),
    picture: new PictureXform(),
    drawing: new DrawingXform(),
  };
};

utils.inherits(WorkSheetXform, BaseXform, {
  WORKSHEET_ATTRIBUTES: {
    xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
    'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
    'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
    'mc:Ignorable': 'x14ac',
    'xmlns:x14ac': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac'
  }
}, {
  prepare: function(model, options) {
    options.merges = new Merges();
    model.hyperlinks = options.hyperlinks = [];

    options.formulae = {};
    options.siFormulae = 0;
    this.map.cols.prepare(model.cols, options);
    this.map.sheetData.prepare(model.rows, options);

    model.mergeCells = options.merges.mergeCells;

    // prepare relationships
    var rels = model.rels = [];
    function nextRid(r) { return 'rId' + (r.length + 1); }
    var rId;
    model.hyperlinks.forEach(function(hyperlink) {
      rId = nextRid(rels);
      hyperlink.rId = rId;
      rels.push({
        Id: rId,
        Type: RelType.Hyperlink,
        Target: hyperlink.target,
        TargetMode: 'External'
      });
    });

    var drawingRelsHash = [];
    var bookImage;
    model.media.forEach(function(medium) {
      if (medium.type === 'background') {
        rId = nextRid(rels);
        bookImage = options.media[medium.imageId];
        rels.push({
          Id: rId,
          Type: RelType.Image,
          Target: `../media/${bookImage.name}.${bookImage.extension}`,
        });
        model.background = {
          rId,
        };
        model.image = options.media[medium.imageId];
      } else if (medium.type === 'image') {
        var drawing = model.drawing;
        bookImage = options.media[medium.imageId];
        if (!drawing) {
          drawing = model.drawing = {
            rId: nextRid(rels),
            name: 'drawing' + (++options.drawingsCount),
            anchors: [],
            rels: [],
          };
          options.drawings.push(drawing);
          rels.push({
            Id: drawing.rId,
            Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
            Target: '../drawings/' + drawing.name + '.xml',
          });
        }
        var rIdImage = drawingRelsHash[medium.imageId];
        if (!rIdImage) {
          rIdImage = nextRid(drawing.rels);
          drawingRelsHash[medium.imageId] = rIdImage;
          drawing.rels.push({
            Id: rIdImage,
            Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
            Target: `../media/${bookImage.name}.${bookImage.extension}`,
          });
        }
        drawing.anchors.push({
          picture: {
            rId: rIdImage,
          },
          range: medium.range,
        });
      }
    });
  },

  render: function(xmlStream, model) {
    xmlStream.openXml(XmlStream.StdDocAttributes);
    xmlStream.openNode('worksheet', WorkSheetXform.WORKSHEET_ATTRIBUTES);

    var sheetFormatPropertiesModel = model.properties ? {
      defaultRowHeight: model.properties.defaultRowHeight,
      dyDescent: model.properties.dyDescent,
      outlineLevelCol: model.properties.outlineLevelCol,
      outlineLevelRow: model.properties.outlineLevelRow
    } : undefined;
    var sheetPropertiesModel = {
      outlineProperties: model.properties && model.properties.outlineProperties,
      tabColor: model.properties && model.properties.tabColor,
      pageSetup: model.pageSetup && model.pageSetup.fitToPage ? {
        fitToPage: model.pageSetup.fitToPage
      } : undefined
    };
    var pageMarginsModel = model.pageSetup && model.pageSetup.margins;
    var printOptionsModel = {
      showRowColHeaders: model.showRowColHeaders,
      showGridLines: model.showGridLines,
      horizontalCentered: model.horizontalCentered,
      verticalCentered: model.verticalCentered
    };

    this.map.sheetPr.render(xmlStream, sheetPropertiesModel);
    this.map.dimension.render(xmlStream, model.dimensions);
    this.map.sheetViews.render(xmlStream, model.views);
    this.map.sheetFormatPr.render(xmlStream, sheetFormatPropertiesModel);
    this.map.cols.render(xmlStream, model.cols);
    this.map.sheetData.render(xmlStream, model.rows);
    this.map.autoFilter.render(xmlStream, model.autoFilter);
    this.map.mergeCells.render(xmlStream, model.mergeCells);
    this.map.dataValidations.render(xmlStream, model.dataValidations);
    // For some reason hyperlinks have to be after the data validations
    this.map.hyperlinks.render(xmlStream, model.hyperlinks);
    this.map.pageMargins.render(xmlStream, pageMarginsModel);
    this.map.printOptions.render(xmlStream, printOptionsModel);
    this.map.pageSetup.render(xmlStream, model.pageSetup);
    this.map.drawing.render(xmlStream, model.drawing);
    this.map.picture.render(xmlStream, model.background); // Note: must be after drawing
    this.map.rowBreaks.render(xmlStream, model.rowBreaks);

    xmlStream.closeNode();
  },

  parseOpen: function(node) {
    if (this.parser) {
      this.parser.parseOpen(node);
      return true;
    }

    if (node.name === 'worksheet') {
      _.each(this.map, function(xform) {
        xform.reset();
      });
      return true;
    }

    this.parser = this.map[node.name];
    if (this.parser) {
      this.parser.parseOpen(node);
    }
    return true;
  },
  parseText: function(text) {
    if (this.parser) {
      this.parser.parseText(text);
    }
  },
  parseClose: function(name) {
    if (this.parser) {
      if (!this.parser.parseClose(name)) {
        this.parser = undefined;
      }
      return true;
    }
    switch (name) {
      case 'worksheet':
        var properties = this.map.sheetFormatPr.model;
        if (this.map.sheetPr.model && this.map.sheetPr.model.tabColor) {
          properties.tabColor = this.map.sheetPr.model.tabColor;
        }
        if (this.map.sheetPr.model && this.map.sheetPr.model.outlineProperties) {
            properties.outlineProperties = this.map.sheetPr.model.outlinePropertiesx;
        }
        var sheetProperties = {
          fitToPage: (this.map.sheetPr.model && this.map.sheetPr.model.pageSetup && this.map.sheetPr.model.pageSetup.fitToPage) || false,
          margins: this.map.pageMargins.model
        };
        var pageSetup = Object.assign(
          sheetProperties,
          this.map.pageSetup.model,
          this.map.printOptions.model
        );
        this.model = {
          dimensions: this.map.dimension.model,
          cols: this.map.cols.model,
          rows: this.map.sheetData.model,
          mergeCells: this.map.mergeCells.model,
          hyperlinks: this.map.hyperlinks.model,
          dataValidations: this.map.dataValidations.model,
          properties: properties,
          views: this.map.sheetViews.model,
          pageSetup: pageSetup,
          background: this.map.picture.model,
          drawing: this.map.drawing.model,
        };

        if (this.map.autoFilter.model) {
          this.model.autoFilter = this.map.autoFilter.model;
        }
        return false;
      default:
        // not quite sure how we get here!
        return true;
    }
  },

  reconcile: function(model, options) {
    // options.merges = new Merges();
    // options.merges.reconcile(model.mergeCells, model.rows);
    var rels = (model.relationships || []).reduce(function(h, rel) {
      h[rel.Id] = rel;
      return h;
    }, {});
    options.hyperlinkMap = (model.hyperlinks || []).reduce(function(h, hyperlink) {
      if (hyperlink.rId) {
        h[hyperlink.address] = rels[hyperlink.rId].Target;
      }
      return h;
    }, {});
    options.formulae = {};

    // compact the rows and cells
    model.rows = (model.rows && model.rows.filter(Boolean)) || [];
    model.rows.forEach(function(row) {
      row.cells = (row.cells && row.cells.filter(Boolean)) || [];
    });

    this.map.cols.reconcile(model.cols, options);
    this.map.sheetData.reconcile(model.rows, options);

    model.media = [];
    if (model.drawing) {
      var drawingRel = rels[model.drawing.rId];
      var match = drawingRel.Target.match(/\/drawings\/([a-zA-Z0-9]+)[.][a-zA-Z]{3,4}$/);
      if (match) {
        var drawingName = match[1];
        var drawing = options.drawings[drawingName];
        drawing.anchors.forEach(anchor => {
          if (anchor.medium) {
            var image = {
              type: 'image',
              imageId: anchor.medium.index,
              range: anchor.range,
            };
            model.media.push(image);
          }
        });
      }
    }

    var backgroundRel = model.background && rels[model.background.rId];
    if (backgroundRel) {
      var target = backgroundRel.Target.split('/media/')[1];
      var imageId = options.mediaIndex && options.mediaIndex[target];
      if (imageId !== undefined) {
        model.media.push({
          type: 'background',
          imageId,
        });
      }
    }

    delete model.relationships;
    delete model.hyperlinks;
  }
});

<commit_msg>drawing must go below rowBreaks according to Open XML spec<commit_after>/**
 * Copyright (c) 2016 Guyon Roche
 * LICENCE: MIT - please refer to LICENCE file included with this module
 * or https://github.com/guyonroche/exceljs/blob/master/LICENSE
 */

'use strict';

var _ = require('../../../utils/under-dash');

var utils = require('../../../utils/utils');
var XmlStream = require('../../../utils/xml-stream');

var RelType = require('../../rel-type');

var Merges = require('./merges');

var BaseXform = require('../base-xform');
var ListXform = require('../list-xform');
var RowXform = require('./row-xform');
var ColXform = require('./col-xform');
var DimensionXform = require('./dimension-xform');
var HyperlinkXform = require('./hyperlink-xform');
var MergeCellXform = require('./merge-cell-xform');
var DataValidationsXform = require('./data-validations-xform');
var SheetPropertiesXform = require('./sheet-properties-xform');
var SheetFormatPropertiesXform = require('./sheet-format-properties-xform');
var SheetViewXform = require('./sheet-view-xform');
var PageMarginsXform = require('./page-margins-xform');
var PageSetupXform = require('./page-setup-xform');
var PrintOptionsXform = require('./print-options-xform');
var AutoFilterXform = require('./auto-filter-xform');
var PictureXform = require('./picture-xform');
var DrawingXform = require('./drawing-xform');
var RowBreaksXform = require('./row-breaks-xform');

var WorkSheetXform = module.exports = function(options) {
  var maxRows = options && options.maxRows;
  this.map = {
    sheetPr: new SheetPropertiesXform(),
    dimension: new DimensionXform(),
    sheetViews: new ListXform({tag: 'sheetViews', count: false, childXform: new SheetViewXform()}),
    sheetFormatPr: new SheetFormatPropertiesXform(),
    cols: new ListXform({tag: 'cols', count: false, childXform: new ColXform()}),
    sheetData: new ListXform({tag: 'sheetData', count: false, empty: true, childXform: new RowXform(), maxItems: maxRows}),
    autoFilter: new AutoFilterXform(),
    mergeCells: new ListXform({tag: 'mergeCells', count: true, childXform: new MergeCellXform()}),
    rowBreaks: new RowBreaksXform(),
    hyperlinks: new ListXform({tag: 'hyperlinks', count: false, childXform: new HyperlinkXform()}),
    pageMargins: new PageMarginsXform(),
    dataValidations: new DataValidationsXform(),
    pageSetup: new PageSetupXform(),
    printOptions: new PrintOptionsXform(),
    picture: new PictureXform(),
    drawing: new DrawingXform(),
  };
};

utils.inherits(WorkSheetXform, BaseXform, {
  WORKSHEET_ATTRIBUTES: {
    xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
    'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
    'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
    'mc:Ignorable': 'x14ac',
    'xmlns:x14ac': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac'
  }
}, {
  prepare: function(model, options) {
    options.merges = new Merges();
    model.hyperlinks = options.hyperlinks = [];

    options.formulae = {};
    options.siFormulae = 0;
    this.map.cols.prepare(model.cols, options);
    this.map.sheetData.prepare(model.rows, options);

    model.mergeCells = options.merges.mergeCells;

    // prepare relationships
    var rels = model.rels = [];
    function nextRid(r) { return 'rId' + (r.length + 1); }
    var rId;
    model.hyperlinks.forEach(function(hyperlink) {
      rId = nextRid(rels);
      hyperlink.rId = rId;
      rels.push({
        Id: rId,
        Type: RelType.Hyperlink,
        Target: hyperlink.target,
        TargetMode: 'External'
      });
    });

    var drawingRelsHash = [];
    var bookImage;
    model.media.forEach(function(medium) {
      if (medium.type === 'background') {
        rId = nextRid(rels);
        bookImage = options.media[medium.imageId];
        rels.push({
          Id: rId,
          Type: RelType.Image,
          Target: `../media/${bookImage.name}.${bookImage.extension}`,
        });
        model.background = {
          rId,
        };
        model.image = options.media[medium.imageId];
      } else if (medium.type === 'image') {
        var drawing = model.drawing;
        bookImage = options.media[medium.imageId];
        if (!drawing) {
          drawing = model.drawing = {
            rId: nextRid(rels),
            name: 'drawing' + (++options.drawingsCount),
            anchors: [],
            rels: [],
          };
          options.drawings.push(drawing);
          rels.push({
            Id: drawing.rId,
            Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
            Target: '../drawings/' + drawing.name + '.xml',
          });
        }
        var rIdImage = drawingRelsHash[medium.imageId];
        if (!rIdImage) {
          rIdImage = nextRid(drawing.rels);
          drawingRelsHash[medium.imageId] = rIdImage;
          drawing.rels.push({
            Id: rIdImage,
            Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
            Target: `../media/${bookImage.name}.${bookImage.extension}`,
          });
        }
        drawing.anchors.push({
          picture: {
            rId: rIdImage,
          },
          range: medium.range,
        });
      }
    });
  },

  render: function(xmlStream, model) {
    xmlStream.openXml(XmlStream.StdDocAttributes);
    xmlStream.openNode('worksheet', WorkSheetXform.WORKSHEET_ATTRIBUTES);

    var sheetFormatPropertiesModel = model.properties ? {
      defaultRowHeight: model.properties.defaultRowHeight,
      dyDescent: model.properties.dyDescent,
      outlineLevelCol: model.properties.outlineLevelCol,
      outlineLevelRow: model.properties.outlineLevelRow
    } : undefined;
    var sheetPropertiesModel = {
      outlineProperties: model.properties && model.properties.outlineProperties,
      tabColor: model.properties && model.properties.tabColor,
      pageSetup: model.pageSetup && model.pageSetup.fitToPage ? {
        fitToPage: model.pageSetup.fitToPage
      } : undefined
    };
    var pageMarginsModel = model.pageSetup && model.pageSetup.margins;
    var printOptionsModel = {
      showRowColHeaders: model.showRowColHeaders,
      showGridLines: model.showGridLines,
      horizontalCentered: model.horizontalCentered,
      verticalCentered: model.verticalCentered
    };

    this.map.sheetPr.render(xmlStream, sheetPropertiesModel);
    this.map.dimension.render(xmlStream, model.dimensions);
    this.map.sheetViews.render(xmlStream, model.views);
    this.map.sheetFormatPr.render(xmlStream, sheetFormatPropertiesModel);
    this.map.cols.render(xmlStream, model.cols);
    this.map.sheetData.render(xmlStream, model.rows);
    this.map.autoFilter.render(xmlStream, model.autoFilter);
    this.map.mergeCells.render(xmlStream, model.mergeCells);
    this.map.dataValidations.render(xmlStream, model.dataValidations);
    // For some reason hyperlinks have to be after the data validations
    this.map.hyperlinks.render(xmlStream, model.hyperlinks);
    this.map.pageMargins.render(xmlStream, pageMarginsModel);
    this.map.printOptions.render(xmlStream, printOptionsModel);
    this.map.pageSetup.render(xmlStream, model.pageSetup);
    this.map.picture.render(xmlStream, model.background); // Note: must be after drawing
    this.map.rowBreaks.render(xmlStream, model.rowBreaks);
	  this.map.drawing.render(xmlStream, model.drawing);

    xmlStream.closeNode();
  },

  parseOpen: function(node) {
    if (this.parser) {
      this.parser.parseOpen(node);
      return true;
    }

    if (node.name === 'worksheet') {
      _.each(this.map, function(xform) {
        xform.reset();
      });
      return true;
    }

    this.parser = this.map[node.name];
    if (this.parser) {
      this.parser.parseOpen(node);
    }
    return true;
  },
  parseText: function(text) {
    if (this.parser) {
      this.parser.parseText(text);
    }
  },
  parseClose: function(name) {
    if (this.parser) {
      if (!this.parser.parseClose(name)) {
        this.parser = undefined;
      }
      return true;
    }
    switch (name) {
      case 'worksheet':
        var properties = this.map.sheetFormatPr.model;
        if (this.map.sheetPr.model && this.map.sheetPr.model.tabColor) {
          properties.tabColor = this.map.sheetPr.model.tabColor;
        }
        if (this.map.sheetPr.model && this.map.sheetPr.model.outlineProperties) {
            properties.outlineProperties = this.map.sheetPr.model.outlinePropertiesx;
        }
        var sheetProperties = {
          fitToPage: (this.map.sheetPr.model && this.map.sheetPr.model.pageSetup && this.map.sheetPr.model.pageSetup.fitToPage) || false,
          margins: this.map.pageMargins.model
        };
        var pageSetup = Object.assign(
          sheetProperties,
          this.map.pageSetup.model,
          this.map.printOptions.model
        );
        this.model = {
          dimensions: this.map.dimension.model,
          cols: this.map.cols.model,
          rows: this.map.sheetData.model,
          mergeCells: this.map.mergeCells.model,
          hyperlinks: this.map.hyperlinks.model,
          dataValidations: this.map.dataValidations.model,
          properties: properties,
          views: this.map.sheetViews.model,
          pageSetup: pageSetup,
          background: this.map.picture.model,
          drawing: this.map.drawing.model,
        };

        if (this.map.autoFilter.model) {
          this.model.autoFilter = this.map.autoFilter.model;
        }
        return false;
      default:
        // not quite sure how we get here!
        return true;
    }
  },

  reconcile: function(model, options) {
    // options.merges = new Merges();
    // options.merges.reconcile(model.mergeCells, model.rows);
    var rels = (model.relationships || []).reduce(function(h, rel) {
      h[rel.Id] = rel;
      return h;
    }, {});
    options.hyperlinkMap = (model.hyperlinks || []).reduce(function(h, hyperlink) {
      if (hyperlink.rId) {
        h[hyperlink.address] = rels[hyperlink.rId].Target;
      }
      return h;
    }, {});
    options.formulae = {};

    // compact the rows and cells
    model.rows = (model.rows && model.rows.filter(Boolean)) || [];
    model.rows.forEach(function(row) {
      row.cells = (row.cells && row.cells.filter(Boolean)) || [];
    });

    this.map.cols.reconcile(model.cols, options);
    this.map.sheetData.reconcile(model.rows, options);

    model.media = [];
    if (model.drawing) {
      var drawingRel = rels[model.drawing.rId];
      var match = drawingRel.Target.match(/\/drawings\/([a-zA-Z0-9]+)[.][a-zA-Z]{3,4}$/);
      if (match) {
        var drawingName = match[1];
        var drawing = options.drawings[drawingName];
        drawing.anchors.forEach(anchor => {
          if (anchor.medium) {
            var image = {
              type: 'image',
              imageId: anchor.medium.index,
              range: anchor.range,
            };
            model.media.push(image);
          }
        });
      }
    }

    var backgroundRel = model.background && rels[model.background.rId];
    if (backgroundRel) {
      var target = backgroundRel.Target.split('/media/')[1];
      var imageId = options.mediaIndex && options.mediaIndex[target];
      if (imageId !== undefined) {
        model.media.push({
          type: 'background',
          imageId,
        });
      }
    }

    delete model.relationships;
    delete model.hyperlinks;
  }
});

<|endoftext|>


<commit_before>// This is the JavaScript interface between CodeBox.elm and ace.js.
//
// Relevant Ports:
//   aceInTheHole -- Is where new (partial) Models to be rendered come from
//   theTurn      -- Is where what become Events for things like code updates go
//

window.initialPortValues["theTurn"] = { evt : "init"
                                      , strArg : ""
                                      , cursorArg : { row : 0 , column : 0 }
                                      , selectionArg : []
                                      , exNameArg : ""
                                      }

window.initializers.push(function (elmRuntime) {

  // Holds on to the current document name for error recovery purposes
  var exName = "";

  var Range = ace.require('ace/range').Range

  //So we can dynamically add CSS classes, which is needed to style the markers
  //Generally only touches the DOM to add new ones if absolutely needed
  var style = document.createElement('style');
  style.id = "autogenerated_css_classes";
  style.type = 'text/css';
  document.getElementsByTagName('head')[0].appendChild(style);
  var classDict = {};

  function makeCSSClass(colorStr) {
      var autogenName = ".autogenerated_class_" + colorStr
      if (!(classDict.hasOwnProperty(autogenName))) {
          style.innerHTML = style.innerHTML
                              + ".ace_marker-layer "
                              + autogenName
                              + " { background-color : " + colorStr+ "; "
                              + "position : absolute; "
                              + "z-index : 2; "
                              + "} ";
          document.getElementById("autogenerated_css_classes").innerHTML = style.innerHTML;
          classDict[autogenName] = true;
      }
      return "autogenerated_class_" + colorStr;
  }

  var editor = ace.edit("editor");
  editor.$blockScrolling = Infinity;
  editor.setTheme("ace/theme/chrome");
  editor.setFontSize(14);
  editor.getSession().setMode("ace/mode/little");

  var tryParseRun = false;

  // Don't propagate editor keystrokes to the model
  document.getElementById("editor").onkeydown = function (e) { e.stopPropagation(); }
  document.getElementById("editor").onkeyup   = function (e) { tryParseRun = true; }

  //If we reloaded from a crash, then we should do a few things differently:
  // - Set the initial text to what it was when we crashed
  // - Send an event to Elm to let Elm know that we just crashed
  var maybeError = localStorage.getItem("__ErrorSave");
  if (maybeError !== null) {
      localStorage.removeItem("__ErrorSave");
      console.log(maybeError);
      var errorObj = JSON.parse(maybeError);
      var defaultScratch = errorObj.strArg;
      elmRuntime.ports.theTurn.send(
              { evt : errorObj.evt
              , strArg : errorObj.strArg
              , cursorArg : errorObj.cursorArg
              , selectionArg : errorObj.selectionArg
              , exNameArg : errorObj.exNameArg
              }
      );
  } else {
      //Set the default scratch text to display before the first load
      var defaultScratch =
      "\n" +
      "; Write a little program below.\n" +
      "; Or choose an example from the list.\n" +
      ";\n" +
      "; Changes to this *Scratch* example will be saved and\n" +
      "; restored when navigating to and from other examples.\n" +
      "; For the remaining named examples, changes will be\n" +
      "; discarded when choosing a different example.\n" +
      "\n" +
      "(svg [(rect 'maroon' 100 15 200 15)])\n" +
      "\n";
  }
  editor.setValue(defaultScratch);
  editor.moveCursorTo(0,0);

  var markers = [];

  elmRuntime.ports.aceInTheHole.subscribe(function(codeBoxInfo) {
      //If a poke, do nothing
      if (codeBoxInfo.kind == "poke") {
          editor.resize();
          reembed(false);
          //If we should bounce an update back (force a rerender) then do so
          bounce(codeBoxInfo.bounce);
          return;
      }

      //Resize the editor window if the div size has changed
      editor.resize();

      //First, check to see what kind of request it was
      if (codeBoxInfo.kind == "assertion") {
          //If it's an assertion, install the values appropriately
          makeAssertion(codeBoxInfo);
      } else if (codeBoxInfo.kind == "saveRequest") {
          //If it's a request for the Editor state, oblige
          sendState("saveResponse");
          //For error recovery purposes
          exName = codeBoxInfo.exName;
      } else if (codeBoxInfo.kind == "runRequest") {
          //Same as above
          sendState("runResponse");
      } else if (codeBoxInfo.kind == "cleanRequest") {
        sendState("cleanResponse");
      } else if (codeBoxInfo.kind == "codeRequest") {
        sendState("codeResponse");
      } else { //We don't recognize the request; log and do nothing
          console.log("codeBoxInfo type " + codeBoxInfo.kind + " unrecognized.");
      }
  });

  var errorPrefix = "[Little Error]"; // NOTE: same as errorPrefix in Lang.elm

  //We recover from fatal errors by setting a special key in the local key/value
  // store before reloading the page. Then we check for this key on load and set
  // up appropriately if we're loading after a crash.
  window.onerror = function(msg, url, linenumber) {
    //We disallow saving to this key in Elm to avoid possible confusion
    //If the error was something that we didn't want to catch (e.g. not prefaced
    // with errorPrefix) then don't do anything
    // Checking for string containment, rather than trimming the prefix
    // because of different error strings in different browsers.

    // TODO change this back
    // if (msg.indexOf(errorPrefix) != 1) {
    if (msg.indexOf("notify function has been called synchronously!") != 1) {
        localStorage.setItem('__ErrorSave', JSON.stringify(
          { evt : msg
          , strArg : editor.getSession().getDocument().getValue()
          , cursorArg : editor.getCursorPosition()
          , selectionArg : editor.selection.getAllRanges()
          , exNameArg : exName
          }
        ));
        location.reload();
    }
  }

  //Packages and sends the Editor state appropriately
  function sendState(evt) {
    elmRuntime.ports.theTurn.send(
      { evt : evt
      , strArg : editor.getSession().getDocument().getValue()
      , cursorArg : editor.getCursorPosition()
      , selectionArg : editor.selection.getAllRanges()
      , exNameArg : ""
      }
    );
  }

  //Re-embeds the editor in the "editor" div
  function reembed(force) {
      //If the div rerendered (kept track of with a special attribute) then we
      // should copy the editor back into it
      var editorDiv = document.getElementById("editor");
      if (force || editorDiv.getAttribute("rendered") !== "true") {
        editorDiv.parentNode.replaceChild(editor.container, editorDiv);
        editorDiv.setAttribute("rendered","true");
      }
  }

  //Bounces a message back to rerender, if applicable
  function bounce(check) {
    if (check) {
        elmRuntime.ports.theTurn.send(
            { evt : "Rerender"
            , strArg : ""
            , cursorArg : editor.getCursorPosition()
            , selectionArg : []
            , exNameArg : ""
            }
        );
    }
  }

  var lastCode = ""
  // Installs all the values that we got from the assertion from Elm
  function makeAssertion(codeBoxInfo) {
      // Set the value of the code editor with what we got from Elm
      if (codeBoxInfo.code !== lastCode && !tryParseRun) {
        editor.getSession().setValue(codeBoxInfo.code, 0);
        lastCode = codeBoxInfo.code

        //Set the cursor position to what we got from Elm
        editor.moveCursorTo(codeBoxInfo.cursorPos.row, codeBoxInfo.cursorPos.column);
        //Set the selections appropriately
        //Note that we need to turn the Elm Ranges into Ace Range Objects
        editor.selection.clearSelection();
      }

      /* No need for this anymore, as assertions are not on every input
      //We need to treat the first one differently, so we must iterate
      //This is undocumented outside of the tests, see:
      // https://github.com/ajaxorg/ace/blob/0a3b002e285a41a4884ce42f94d2f68673e43b30/lib/ace/multi_select_test.js#L168
      //For the special namespacing, see:
      // http://stackoverflow.com/questions/10452869/when-i-try-to-create-a-range-object-in-ace-js-an-illegal-constructor-error-is
      var numRanges = codeBoxInfo.selections.length;
      for (var i = 0; i < numRanges; i++) {
          elmRange = codeBoxInfo.selections[i];
          var aceRange = new Range(elmRange.start.row,
                                   elmRange.start.column,
                                   elmRange.end.row,
                                   elmRange.end.column);
          if (i === 0) {
              //We only allow backwards selections if there is just one selection
              //We deduce if it's backwards by comparing the cursor position to
              // the end of the selection (which always orients itself such that
              // it's > the start in the process of constucting the Range)
              //Note that we use compareEnd here because the compare functions are
              // a little wonky - compareStart returns -1, for instance, if the
              // cursor is at the start.
              if (numRanges === 1) {
                  if (aceRange.compareEnd(codeBoxInfo.cursorPos.row,
                                          codeBoxInfo.cursorPos.column) === 1) {
                      var backwards = false;
                  } else {
                      var backwards = true;
                  }
                  editor.selection.setSelectionRange(aceRange, backwards);
              } else {
                  editor.selection.fromOrientedRange(aceRange);
              }
          } else {
              editor.selection.addRange(aceRange, false);
          }
      }
      */

      //Add the special syntax highlighting for changes and such
      //These are 'markers', see:
      //http://ace.c9.io/#nav=api&api=edit_session
      //ctl+f addMarker
      for (mi in markers) {
          editor.getSession().removeMarker(markers[mi]);
      }
      markers = [];
      for (hi in codeBoxInfo.highlights) {
          hiRange = codeBoxInfo.highlights[hi].range;
          var aceRange = new Range(hiRange.start.row - 1, //Indexing from 1 in Elm?
                                   hiRange.start.column - 1,
                                   hiRange.end.row - 1,
                                   hiRange.end.column - 1);
          var hiClass = makeCSSClass(codeBoxInfo.highlights[hi].color);
          var mid = editor.getSession().addMarker(aceRange,hiClass,"text", false);
          editor.resize();
          markers.push(mid);
      }
      editor.updateSelectionMarkers();

      // rkc 8/9/16: set 'annotations' here
      // setDummyAnnotations();

      editor.getSession().clearAnnotations();
      var annots = [];
      for (idx in codeBoxInfo.annotations) {
          annots.push(
              { row  : codeBoxInfo.annotations[idx].row
              , text : codeBoxInfo.annotations[idx].text
              , type : codeBoxInfo.annotations[idx].type_
              });
      }
      editor.getSession().setAnnotations(annots);

      // rkc 8/9/16: set 'tooltips' here
      // setDummyTooltips();

      var typeTooltip = new TokenTooltip(editor, getTooltipText);
      clearTooltips();
      for (idx in codeBoxInfo.tooltips) {
          var row = codeBoxInfo.tooltips[idx].row;
          var col = codeBoxInfo.tooltips[idx].col;
          var ann = codeBoxInfo.tooltips[idx].text;
          addTooltip(row, col, ann);
      }

      //If the div rerendered (kept track of with a special attribute) then we
      // should copy the editor back into it
      reembed(false);

      //If we should bounce an update back (force a rerender) then do so
      bounce(codeBoxInfo.bounce);

      //Remember the current document name (for error recovery purposes)
      exName = codeBoxInfo.exName;
  }

  ////////////////////////////////////////////////////////////

  // Demo: Displaying a hover annotation (for entire line)

  function setDummyAnnotations() {

    var annots = [
      { row: 0 , text: "Info!" , type: "info" }
    , { row: 1 , text: "Warning!" , type: "warning" }
    , { row: 2 , text: "Error!" , type: "error" }
    ];

    editor.getSession().setAnnotations(annots);
  }

  ////////////////////////////////////////////////////////////

  // Demo: A "tooltip" provides info when hovering over the token
  // starting at a given row/column
  //
  // clearTooltips() and addTooltip() are defined in aceTooltips.js

  function setDummyTooltips() {
    var typeTooltip = new TokenTooltip(editor, getTooltipText);
    clearTooltips();
    addTooltip(0, 0, "Don't click on me, it hurts.");
    addTooltip(0, 1, "Click on me to rewrite.");
  }

  ////////////////////////////////////////////////////////////

  // Start the pinger for live code running
  tryParseRunFunc = function () {
    if (tryParseRun) {
      tryParseRun = false;
      elmRuntime.ports.theTurn.send(
          { evt : "tryParseRun"
          , strArg : editor.getSession().getDocument().getValue()
          , cursorArg : editor.getCursorPosition()
          , selectionArg : editor.selection.getAllRanges()
          , exNameArg : ""
          }
      );
    }
    window.setTimeout(tryParseRunFunc, 150)
  }
  window.setTimeout(tryParseRunFunc, 150)

});
<commit_msg>longer delay for tryParseFunc<commit_after>// This is the JavaScript interface between CodeBox.elm and ace.js.
//
// Relevant Ports:
//   aceInTheHole -- Is where new (partial) Models to be rendered come from
//   theTurn      -- Is where what become Events for things like code updates go
//

window.initialPortValues["theTurn"] = { evt : "init"
                                      , strArg : ""
                                      , cursorArg : { row : 0 , column : 0 }
                                      , selectionArg : []
                                      , exNameArg : ""
                                      }

window.initializers.push(function (elmRuntime) {

  // Holds on to the current document name for error recovery purposes
  var exName = "";

  var Range = ace.require('ace/range').Range

  //So we can dynamically add CSS classes, which is needed to style the markers
  //Generally only touches the DOM to add new ones if absolutely needed
  var style = document.createElement('style');
  style.id = "autogenerated_css_classes";
  style.type = 'text/css';
  document.getElementsByTagName('head')[0].appendChild(style);
  var classDict = {};

  function makeCSSClass(colorStr) {
      var autogenName = ".autogenerated_class_" + colorStr
      if (!(classDict.hasOwnProperty(autogenName))) {
          style.innerHTML = style.innerHTML
                              + ".ace_marker-layer "
                              + autogenName
                              + " { background-color : " + colorStr+ "; "
                              + "position : absolute; "
                              + "z-index : 2; "
                              + "} ";
          document.getElementById("autogenerated_css_classes").innerHTML = style.innerHTML;
          classDict[autogenName] = true;
      }
      return "autogenerated_class_" + colorStr;
  }

  var editor = ace.edit("editor");
  editor.$blockScrolling = Infinity;
  editor.setTheme("ace/theme/chrome");
  editor.setFontSize(14);
  editor.getSession().setMode("ace/mode/little");

  var tryParseRun = false;

  // Don't propagate editor keystrokes to the model
  document.getElementById("editor").onkeydown = function (e) { e.stopPropagation(); }
  document.getElementById("editor").onkeyup   = function (e) { tryParseRun = true; }

  //If we reloaded from a crash, then we should do a few things differently:
  // - Set the initial text to what it was when we crashed
  // - Send an event to Elm to let Elm know that we just crashed
  var maybeError = localStorage.getItem("__ErrorSave");
  if (maybeError !== null) {
      localStorage.removeItem("__ErrorSave");
      console.log(maybeError);
      var errorObj = JSON.parse(maybeError);
      var defaultScratch = errorObj.strArg;
      elmRuntime.ports.theTurn.send(
              { evt : errorObj.evt
              , strArg : errorObj.strArg
              , cursorArg : errorObj.cursorArg
              , selectionArg : errorObj.selectionArg
              , exNameArg : errorObj.exNameArg
              }
      );
  } else {
      //Set the default scratch text to display before the first load
      var defaultScratch =
      "\n" +
      "; Write a little program below.\n" +
      "; Or choose an example from the list.\n" +
      ";\n" +
      "; Changes to this *Scratch* example will be saved and\n" +
      "; restored when navigating to and from other examples.\n" +
      "; For the remaining named examples, changes will be\n" +
      "; discarded when choosing a different example.\n" +
      "\n" +
      "(svg [(rect 'maroon' 100 15 200 15)])\n" +
      "\n";
  }
  editor.setValue(defaultScratch);
  editor.moveCursorTo(0,0);

  var markers = [];

  elmRuntime.ports.aceInTheHole.subscribe(function(codeBoxInfo) {
      //If a poke, do nothing
      if (codeBoxInfo.kind == "poke") {
          editor.resize();
          reembed(false);
          //If we should bounce an update back (force a rerender) then do so
          bounce(codeBoxInfo.bounce);
          return;
      }

      //Resize the editor window if the div size has changed
      editor.resize();

      //First, check to see what kind of request it was
      if (codeBoxInfo.kind == "assertion") {
          //If it's an assertion, install the values appropriately
          makeAssertion(codeBoxInfo);
      } else if (codeBoxInfo.kind == "saveRequest") {
          //If it's a request for the Editor state, oblige
          sendState("saveResponse");
          //For error recovery purposes
          exName = codeBoxInfo.exName;
      } else if (codeBoxInfo.kind == "runRequest") {
          //Same as above
          sendState("runResponse");
      } else if (codeBoxInfo.kind == "cleanRequest") {
        sendState("cleanResponse");
      } else if (codeBoxInfo.kind == "codeRequest") {
        sendState("codeResponse");
      } else { //We don't recognize the request; log and do nothing
          console.log("codeBoxInfo type " + codeBoxInfo.kind + " unrecognized.");
      }
  });

  var errorPrefix = "[Little Error]"; // NOTE: same as errorPrefix in Lang.elm

  //We recover from fatal errors by setting a special key in the local key/value
  // store before reloading the page. Then we check for this key on load and set
  // up appropriately if we're loading after a crash.
  window.onerror = function(msg, url, linenumber) {
    //We disallow saving to this key in Elm to avoid possible confusion
    //If the error was something that we didn't want to catch (e.g. not prefaced
    // with errorPrefix) then don't do anything
    // Checking for string containment, rather than trimming the prefix
    // because of different error strings in different browsers.

    // TODO change this back
    // if (msg.indexOf(errorPrefix) != 1) {
    if (msg.indexOf("notify function has been called synchronously!") != 1) {
        localStorage.setItem('__ErrorSave', JSON.stringify(
          { evt : msg
          , strArg : editor.getSession().getDocument().getValue()
          , cursorArg : editor.getCursorPosition()
          , selectionArg : editor.selection.getAllRanges()
          , exNameArg : exName
          }
        ));
        location.reload();
    }
  }

  //Packages and sends the Editor state appropriately
  function sendState(evt) {
    elmRuntime.ports.theTurn.send(
      { evt : evt
      , strArg : editor.getSession().getDocument().getValue()
      , cursorArg : editor.getCursorPosition()
      , selectionArg : editor.selection.getAllRanges()
      , exNameArg : ""
      }
    );
  }

  //Re-embeds the editor in the "editor" div
  function reembed(force) {
      //If the div rerendered (kept track of with a special attribute) then we
      // should copy the editor back into it
      var editorDiv = document.getElementById("editor");
      if (force || editorDiv.getAttribute("rendered") !== "true") {
        editorDiv.parentNode.replaceChild(editor.container, editorDiv);
        editorDiv.setAttribute("rendered","true");
      }
  }

  //Bounces a message back to rerender, if applicable
  function bounce(check) {
    if (check) {
        elmRuntime.ports.theTurn.send(
            { evt : "Rerender"
            , strArg : ""
            , cursorArg : editor.getCursorPosition()
            , selectionArg : []
            , exNameArg : ""
            }
        );
    }
  }

  var lastCode = ""
  // Installs all the values that we got from the assertion from Elm
  function makeAssertion(codeBoxInfo) {
      // Set the value of the code editor with what we got from Elm
      if (codeBoxInfo.code !== lastCode && !tryParseRun) {
        editor.getSession().setValue(codeBoxInfo.code, 0);
        lastCode = codeBoxInfo.code

        //Set the cursor position to what we got from Elm
        editor.moveCursorTo(codeBoxInfo.cursorPos.row, codeBoxInfo.cursorPos.column);
        //Set the selections appropriately
        //Note that we need to turn the Elm Ranges into Ace Range Objects
        editor.selection.clearSelection();
      }

      /* No need for this anymore, as assertions are not on every input
      //We need to treat the first one differently, so we must iterate
      //This is undocumented outside of the tests, see:
      // https://github.com/ajaxorg/ace/blob/0a3b002e285a41a4884ce42f94d2f68673e43b30/lib/ace/multi_select_test.js#L168
      //For the special namespacing, see:
      // http://stackoverflow.com/questions/10452869/when-i-try-to-create-a-range-object-in-ace-js-an-illegal-constructor-error-is
      var numRanges = codeBoxInfo.selections.length;
      for (var i = 0; i < numRanges; i++) {
          elmRange = codeBoxInfo.selections[i];
          var aceRange = new Range(elmRange.start.row,
                                   elmRange.start.column,
                                   elmRange.end.row,
                                   elmRange.end.column);
          if (i === 0) {
              //We only allow backwards selections if there is just one selection
              //We deduce if it's backwards by comparing the cursor position to
              // the end of the selection (which always orients itself such that
              // it's > the start in the process of constucting the Range)
              //Note that we use compareEnd here because the compare functions are
              // a little wonky - compareStart returns -1, for instance, if the
              // cursor is at the start.
              if (numRanges === 1) {
                  if (aceRange.compareEnd(codeBoxInfo.cursorPos.row,
                                          codeBoxInfo.cursorPos.column) === 1) {
                      var backwards = false;
                  } else {
                      var backwards = true;
                  }
                  editor.selection.setSelectionRange(aceRange, backwards);
              } else {
                  editor.selection.fromOrientedRange(aceRange);
              }
          } else {
              editor.selection.addRange(aceRange, false);
          }
      }
      */

      //Add the special syntax highlighting for changes and such
      //These are 'markers', see:
      //http://ace.c9.io/#nav=api&api=edit_session
      //ctl+f addMarker
      for (mi in markers) {
          editor.getSession().removeMarker(markers[mi]);
      }
      markers = [];
      for (hi in codeBoxInfo.highlights) {
          hiRange = codeBoxInfo.highlights[hi].range;
          var aceRange = new Range(hiRange.start.row - 1, //Indexing from 1 in Elm?
                                   hiRange.start.column - 1,
                                   hiRange.end.row - 1,
                                   hiRange.end.column - 1);
          var hiClass = makeCSSClass(codeBoxInfo.highlights[hi].color);
          var mid = editor.getSession().addMarker(aceRange,hiClass,"text", false);
          editor.resize();
          markers.push(mid);
      }
      editor.updateSelectionMarkers();

      // rkc 8/9/16: set 'annotations' here
      // setDummyAnnotations();

      editor.getSession().clearAnnotations();
      var annots = [];
      for (idx in codeBoxInfo.annotations) {
          annots.push(
              { row  : codeBoxInfo.annotations[idx].row
              , text : codeBoxInfo.annotations[idx].text
              , type : codeBoxInfo.annotations[idx].type_
              });
      }
      editor.getSession().setAnnotations(annots);

      // rkc 8/9/16: set 'tooltips' here
      // setDummyTooltips();

      var typeTooltip = new TokenTooltip(editor, getTooltipText);
      clearTooltips();
      for (idx in codeBoxInfo.tooltips) {
          var row = codeBoxInfo.tooltips[idx].row;
          var col = codeBoxInfo.tooltips[idx].col;
          var ann = codeBoxInfo.tooltips[idx].text;
          addTooltip(row, col, ann);
      }

      //If the div rerendered (kept track of with a special attribute) then we
      // should copy the editor back into it
      reembed(false);

      //If we should bounce an update back (force a rerender) then do so
      bounce(codeBoxInfo.bounce);

      //Remember the current document name (for error recovery purposes)
      exName = codeBoxInfo.exName;
  }

  ////////////////////////////////////////////////////////////

  // Demo: Displaying a hover annotation (for entire line)

  function setDummyAnnotations() {

    var annots = [
      { row: 0 , text: "Info!" , type: "info" }
    , { row: 1 , text: "Warning!" , type: "warning" }
    , { row: 2 , text: "Error!" , type: "error" }
    ];

    editor.getSession().setAnnotations(annots);
  }

  ////////////////////////////////////////////////////////////

  // Demo: A "tooltip" provides info when hovering over the token
  // starting at a given row/column
  //
  // clearTooltips() and addTooltip() are defined in aceTooltips.js

  function setDummyTooltips() {
    var typeTooltip = new TokenTooltip(editor, getTooltipText);
    clearTooltips();
    addTooltip(0, 0, "Don't click on me, it hurts.");
    addTooltip(0, 1, "Click on me to rewrite.");
  }

  ////////////////////////////////////////////////////////////

  // Start the pinger for live code running
  tryParseRunFunc = function () {
    if (tryParseRun) {
      tryParseRun = false;
      elmRuntime.ports.theTurn.send(
          { evt : "tryParseRun"
          , strArg : editor.getSession().getDocument().getValue()
          , cursorArg : editor.getCursorPosition()
          , selectionArg : editor.selection.getAllRanges()
          , exNameArg : ""
          }
      );
    }
    window.setTimeout(tryParseRunFunc, 500)
  }
  window.setTimeout(tryParseRunFunc, 500)

});
<|endoftext|>


<commit_before>// @flow
import { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
const events = [
	'KeyDown',
	'KeyPress',
	'KeyUp',
	'Click',
	'ContextMenu',
	'DoubleClick',
	'Drag',
	'DragEnd',
	'DragEnter',
	'DragExit',
	'DragLeave',
	'DragOver',
	'DragStart',
	'Drop',
	'Focus',
	'MouseDown',
	'MouseEnter',
	'MouseLeave',
	'MouseMove',
	'MouseOut',
	'MouseOver',
	'MouseUp',
	'PointerOver',
	'PointerEnter',
	'PointerDown',
	'PointerMove',
	'PointerUp',
	'PointerCancel',
	'PointerOut',
	'PointerLeave'
]

const aliases = {
	DoubleClick: 'dblclick'
}

const toEventName = (event: string): string =>
	(aliases[event] || event).toLowerCase()

export default class NativeListener extends Component {
	static displayName = 'NativeListener'
	static propTypes = {
		children: (props, propName) => {
			if (props[propName].length) {
				return new Error('NativeListener can only wrap one element')
			}
		},
		...events.reduce(
			(accumulator, event) => ({
				...accumulator,
				[`on${event}`]: PropTypes.func,
				[`on${event}Capture`]: PropTypes.func,
				[`stop${event}`]: PropTypes.bool
			}),
			{}
		)
	}

	componentDidMount() {
		const props = this.props
		const element = ReactDOM.findDOMNode(this)
		if (element) {
			events.forEach(event => {
				const capture = props['on' + event + 'Capture']
				const bubble = props['on' + event]
				const stop = props['stop' + event]
				if (capture && typeof capture === 'function') {
					element.addEventListener(toEventName(event), capture, true)
				}
				if (bubble && typeof bubble === 'function') {
					element.addEventListener(toEventName(event), bubble, false)
				}
				if (stop === true) {
					element.addEventListener(
						toEventName(event),
						nativeEvent => nativeEvent.stopPropagation(),
						false
					)
				}
			})
		}
	}

	render() {
		return this.props.children
	}
}
<commit_msg>Added touch and scroll events. Closes #19<commit_after>// @flow
import { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
const events = [
  'KeyDown',
  'KeyPress',
  'KeyUp',
  'Click',
  'ContextMenu',
  'DoubleClick',
  'Drag',
  'DragEnd',
  'DragEnter',
  'DragExit',
  'DragLeave',
  'DragOver',
  'DragStart',
  'Drop',
  'Focus',
  'MouseDown',
  'MouseEnter',
  'MouseLeave',
  'MouseMove',
  'MouseOut',
  'MouseOver',
  'MouseUp',
  'PointerOver',
  'PointerEnter',
  'PointerDown',
  'PointerMove',
  'PointerUp',
  'PointerCancel',
  'PointerOut',
  'PointerLeave',
  'Scroll',
  'TouchStart',
  'TouchMove',
  'TouchEnd',
  'TouchCancel'
]

const aliases = {
  DoubleClick: 'dblclick'
}

const toEventName = (event: string): string =>
  (aliases[event] || event).toLowerCase()

export default class NativeListener extends Component {
  static displayName = 'NativeListener'
  static propTypes = {
    children: (props, propName) => {
      if (props[propName].length) {
        return new Error('NativeListener can only wrap one element')
      }
    },
    ...events.reduce(
      (accumulator, event) => ({
        ...accumulator,
        [`on${event}`]: PropTypes.func,
        [`on${event}Capture`]: PropTypes.func,
        [`stop${event}`]: PropTypes.bool
      }),
      {}
    )
  }

  componentDidMount() {
    const props = this.props
    const element = ReactDOM.findDOMNode(this)
    if (element) {
      events.forEach(event => {
        const capture = props['on' + event + 'Capture']
        const bubble = props['on' + event]
        const stop = props['stop' + event]
        if (capture && typeof capture === 'function') {
          element.addEventListener(toEventName(event), capture, true)
        }
        if (bubble && typeof bubble === 'function') {
          element.addEventListener(toEventName(event), bubble, false)
        }
        if (stop === true) {
          element.addEventListener(
            toEventName(event),
            nativeEvent => nativeEvent.stopPropagation(),
            false
          )
        }
      })
    }
  }

  render() {
    return this.props.children
  }
}
<|endoftext|>