<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * General dialog type
 * Used to live at 'JOIST/Dialog'. Moved to 'SUN/Dialog' on 4/10/2018
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' );
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var AlignBox = require( 'SCENERY/nodes/AlignBox' );
  var DialogIO = require( 'SUN/DialogIO' );
  var Display = require( 'SCENERY/display/Display' );
  var FullScreen = require( 'SCENERY/util/FullScreen' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  var Panel = require( 'SUN/Panel' );
  var Path = require( 'SCENERY/nodes/Path' );
  var RectangularButtonView = require( 'SUN/buttons/RectangularButtonView' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var SunA11yStrings = require( 'SUN/SunA11yStrings' );
  var Tandem = require( 'TANDEM/Tandem' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // strings
  var closeString = SunA11yStrings.close.value;

  // constants
  var CLOSE_BUTTON_WIDTH = 14;

  /**
   * @param {Node} content - The content to display inside the dialog (not including the title)
   * @param {Object} [options]
   * @constructor
   */
  function Dialog( content, options ) {

    var self = this;

    options = _.extend( {

      /* Margins and spacing:
       ____________________________________________________________________________
      |                                     |                          |           |
      |                                     |                          closeButton |
      |                                     topMargin                  TopMargin   |
      |                                     |                         _|___        |
      |                  ___________________|____________________    |     |       |
      |--------l--------|                                        |-x-|  X  |---c---|
      |        e        |   Title                                | S |_____|   l   |
      |        f        |________________________________________| P           o   |
      |        t        |   |                                    | a           s   |
      |        M        |   ySpacing                             | c           e   |
      |        a        |___|____________________________________| i           B   |
      |        r        |                                        | n           u   |
      |        g        |   Content                              | g           t   |
      |        i        |                                        |             t   |
      |        n        |                                        |             o   |
      |                 |                                        |             n   |
      |                 |                                        |             R   |
      |                 |                                        |             i   |
      |                 |                                        |             g   |
      |                 |                                        |             h   |
      |                 |                                        |             M   |
      |                 |________________________________________|             a   |
      |                                     |                                  r   |
      |                                     |                                  g   |
      |                                     bottomMargin                       i   |
      |                                     |                                  n   |
      |_____________________________________|______________________________________|
       */

      xSpacing: 10, // {number} how far the title and content is placed to the left of the close button
      ySpacing: 10, // {number} vertical space between title and content
      topMargin: 15, // {number} margin above content, or above title if provided
      bottomMargin: 15, // {number} margin below content
      leftMargin: null, // {number|null} margin to the left of the content.  If null, this is computed so that we have
      // the same margins on the left and right of the content.
      closeButtonTopMargin: 10, // {number} margin above the close button
      closeButtonRightMargin: 10, // {number} margin to the right of the close button

      // more Dialog-specific options
      modal: true, // {boolean} modal dialogs prevent interaction with the rest of the sim while open
      title: null, // {Node|null} title to be displayed at top
      titleAlign: 'center', // horizontal alignment of the title: {string} left, right or center

      // {function} which sets the dialog's position in global coordinates. called as
      // layoutStrategy( dialog, simBounds, screenBounds, scale )
      layoutStrategy: Dialog.DEFAULT_LAYOUT_STRATEGY,

      // close button options
      closeButtonListener: function() { self.hide(); },
      closeButtonTouchAreaXDilation: 0,
      closeButtonTouchAreaYDilation: 0,
      closeButtonMouseAreaXDilation: 0,
      closeButtonMouseAreaYDilation: 0,

      // {function|null} called after the dialog is shown, see https://github.com/phetsims/joist/issues/478
      showCallback: null,

      // {function|null} called after the dialog is hidden, see https://github.com/phetsims/joist/issues/478
      hideCallback: null,

      // pass through to Panel options
      cornerRadius: 10, // {number} radius of the dialog's corners
      resize: true, // {boolean} whether to resize if content's size changes
      fill: 'white', // {string|Color}
      stroke: 'black', // {string|Color}
      backgroundPickable: true,
      tandem: Tandem.optional,
      phetioType: DialogIO,
      phetioReadOnly: false, // default to false so it can pass it through to the close button
      phetioState: false, // default to false so it can pass it through to the close button

      // a11y options
      tagName: 'div',
      ariaRole: 'dialog',
      focusOnCloseNode: null // {Node} receives focus on close, if null focus returns to element that had focus on open
    }, options );

    assert && assert( options.xMargin === undefined, 'Dialog sets xMargin' );
    options.xMargin = 0;
    assert && assert( options.yMargin === undefined, 'Dialog sets yMargin' );
    options.yMargin = 0;

    // if left margin is specified in options, use it. otherwise, set it to make the left right gutters symmetrical
    if ( options.leftMargin === null ) {
      options.leftMargin = options.xSpacing + CLOSE_BUTTON_WIDTH + options.closeButtonRightMargin;
    }

    // @private (read-only)
    this.isModal = options.modal;

    // @private
    this.showCallback = options.showCallback;
    this.hideCallback = options.hideCallback;

    // see https://github.com/phetsims/joist/issues/293
    assert && assert( this.isModal, 'Non-modal dialogs not currently supported' );

    // @private - whether the dialog is showing
    this.isShowing = false;

    // create close button
    var closeButton = new CloseButton( {

      iconLength: CLOSE_BUTTON_WIDTH,
      listener: function() {
        options.closeButtonListener();

        // if listener was fired because of accessibility
        if ( closeButton.buttonModel.isA11yClicking() ) {
          self.focusActiveElement();
        }
      },

      // phet-io
      tandem: options.tandem.createTandem( 'closeButton' ),
      phetioReadOnly: options.phetioReadOnly, // match the readOnly of the Dialog
      phetioState: options.phetioState, // match the state transfer of the Dialog

      // a11y
      tagName: 'button',
      innerContent: closeString
    } );

    // touch/mouse areas for the close button
    closeButton.touchArea = closeButton.bounds.dilatedXY(
      options.closeButtonTouchAreaXDilation,
      options.closeButtonTouchAreaYDilation
    );
    closeButton.mouseArea = closeButton.bounds.dilatedXY(
      options.closeButtonMouseAreaXDilation,
      options.closeButtonMouseAreaYDilation
    );

    // @private (a11y)
    this.closeButton = closeButton;

    // Align content, title, and close button using spacing and margin options

    // align content and title (if provided) vertically
    var contentAndTitle = new VBox( {
      children: options.title ? [ options.title, content ] : [ content ],
      spacing: options.ySpacing,
      align: options.titleAlign
    } );

    // add topMargin, bottomMargin, and leftMargin
    var contentAndTitleWithMargins = new AlignBox( contentAndTitle, {
      topMargin: options.topMargin,
      bottomMargin: options.bottomMargin,
      leftMargin: options.leftMargin
    } );

    // add closeButtonTopMargin and closeButtonRightMargin
    var closeButtonWithMargins = new AlignBox( closeButton, {
      topMargin: options.closeButtonTopMargin,
      rightMargin: options.closeButtonRightMargin
    } );

    // create content for Panel
    var dialogContent = new HBox( {
      children: [ contentAndTitleWithMargins, closeButtonWithMargins ],
      spacing: options.xSpacing,
      align: 'top'
    } );

    Panel.call( this, dialogContent, options );

    var sim = window.phet.joist.sim;

    // @private
    this.updateLayout = function() {
      options.layoutStrategy( self, sim.boundsProperty.value, sim.screenBoundsProperty.value, sim.scaleProperty.value );
    };

    this.updateLayout();

    // @private
    this.sim = sim;

    // a11y - set the order of content, close button first so remaining content can be read from top to bottom
    // with virtual cursor
    this.accessibleOrder = [ closeButton, options.title, content ].filter( function( node ) {
      return node !== undefined;
    } );

    // a11y - set the aria-labelledby relation so that whenever focus enters the dialog the title is read
    if ( options.title && options.title.tagName ) {
      this.addAriaLabelledbyAssociation( {
        thisElementName: AccessiblePeer.PRIMARY_SIBLING,
        otherNode: options.title,
        otherElementName: AccessiblePeer.PRIMARY_SIBLING
      } );
    }

    // must be removed on dispose
    this.sim.resizedEmitter.addListener( this.updateLayout );

    // @private (a11y) - the active element when the dialog is shown, tracked so that focus can be restored on close
    this.activeElement = options.focusOnCloseNode || null;

    // a11y - close the dialog when pressing "escape"
    var escapeListener = {
      keydown: function( event ) {
        if ( event.keyCode === KeyboardUtil.KEY_ESCAPE ) {
          event.preventDefault();
          self.hide();
          self.focusActiveElement();
        }
        else if ( event.keyCode === KeyboardUtil.KEY_TAB && FullScreen.isFullScreen() ) {

          // prevent a particular bug in Windows 7/8.1 Firefox where focus gets trapped in the document
          // when the navigation bar is hidden and there is only one focusable element in the DOM
          // see https://bugzilla.mozilla.org/show_bug.cgi?id=910136
          var activeId = Display.focus.trail.getUniqueId();
          var noNextFocusable = AccessibilityUtil.getNextFocusable().id === activeId;
          var noPreviousFocusable = AccessibilityUtil.getPreviousFocusable().id === activeId;

          if ( noNextFocusable && noPreviousFocusable ) {
            event.preventDefault();
          }
        }
      }
    };
    this.addAccessibleInputListener( escapeListener );

    // @private - to be called on dispose()
    this.disposeDialog = function() {
      self.sim.resizedEmitter.removeListener( self.updateLayout );
      self.removeAccessibleInputListener( escapeListener );

      closeButton.dispose();

      // remove dialog content from scene graph, but don't dispose because Panel
      // needs to remove listeners on the content in its dispose()
      dialogContent.removeAllChildren();
      dialogContent.detach();
    };
  }

  sun.register( 'Dialog', Dialog );

  // @private
  Dialog.DEFAULT_LAYOUT_STRATEGY = function( dialog, simBounds, screenBounds, scale ) {

    // The size is set in the Sim.topLayer, but we need to update the location here
    dialog.center = simBounds.center.times( 1.0 / scale );
  };

  inherit( Panel, Dialog, {

    // @public
    show: function() {
      if ( !this.isShowing ) {
        window.phet.joist.sim.showPopup( this, this.isModal );
        this.isShowing = true;

        // a11y - store the currently active element before hiding all other accessible content
        // so that the active element isn't blurred
        this.activeElement = this.activeElement || Display.focusedNode;
        this.setAccessibleViewsVisible( false );

        // In case the window size has changed since the dialog was hidden, we should try layout out again.
        // See https://github.com/phetsims/joist/issues/362
        this.updateLayout();

        // Do this last
        this.showCallback && this.showCallback();
      }
    },

    /**
     * Hide the dialog.  If you create a new dialog next time you show(), be sure to dispose this
     * dialog instead.
     * @public
     */
    hide: function() {
      if ( this.isShowing ) {

        window.phet.joist.sim.hidePopup( this, this.isModal );
        this.isShowing = false;

        // a11y - when the dialog is hidden, make all ScreenView content visible to assistive technology
        this.setAccessibleViewsVisible( true );

        // Do this last
        this.hideCallback && this.hideCallback();
      }
    },

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {
      this.hide();
      this.disposeDialog();
      Panel.prototype.dispose.call( this );
    },

    /**
     * Hide or show all accessible content related to the sim ScreenViews, navigation bar, and alert content. Instead
     * of using setVisible, we have to remove the subtree of accessible content from each view element in order to
     * prevent an IE11 bug where content remains invisible in the accessibility tree, see
     * https://github.com/phetsims/john-travoltage/issues/247
     *
     * @param {boolean} visible
     */
    setAccessibleViewsVisible: function( visible ) {
      for ( var i = 0; i < this.sim.screens.length; i++ ) {
        this.sim.screens[ i ].view.accessibleVisible = visible;
      }
      this.sim.navigationBar.accessibleVisible = visible;
      this.sim.homeScreen && this.sim.homeScreen.view.setAccessibleVisible( visible );

      // workaround for a strange Edge bug where this child of the navigation bar remains visible,
      // see https://github.com/phetsims/a11y-research/issues/30
      if ( this.sim.navigationBar.keyboardHelpButton ) {
        this.sim.navigationBar.keyboardHelpButton.accessibleVisible = visible;
      }
    },

    /**
     * If there is an active element, focus it.  Should almost always be called after the Dialog has been closed.
     *
     * @public
     * @a11y
     */
    focusActiveElement: function() {
      this.activeElement && this.activeElement.focus();
    },

    /**
     * Place keyboard focus on the close button, useful when opening the dialog with an accessibility interaction.
     * @public
     */
    focusCloseButton: function() {
      this.closeButton.focus();
    }
  } );

  /**
   * The close button for Dialog
   * A flat x
   *
   * @param {Object} [options] - see RectangularPushButton
   * @constructor
   */
  function CloseButton( options ) {
    options = _.extend( {
      iconLength: 7,
      baseColor: 'transparent',
      buttonAppearanceStrategy: RectangularButtonView.FlatAppearanceStrategy,
      xMargin: 0,
      yMargin: 0,
      listener: null // {function} called when the button is pressed
    }, options );

    // close button shape, an 'X'
    var closeButtonShape = new Shape()
      .moveTo( -options.iconLength / 2, -options.iconLength / 2 )
      .lineTo( options.iconLength / 2, options.iconLength / 2 )
      .moveTo( options.iconLength / 2, -options.iconLength / 2 )
      .lineTo( -options.iconLength / 2, options.iconLength / 2 );

    assert && assert( !options.content, 'Dialog.CloseButton sets content' );

    options.content = new Path( closeButtonShape, {
      stroke: 'black',
      lineCap: 'round',
      lineWidth: 2
    } );

    RectangularPushButton.call( this, options );
  }

  sun.register( 'Dialog.CloseButton', CloseButton );

  inherit( RectangularPushButton, CloseButton );

  return Dialog;
} );<commit_msg>fix update check error, https://github.com/phetsims/joist/issues/527<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * General dialog type
 * Used to live at 'JOIST/Dialog'. Moved to 'SUN/Dialog' on 4/10/2018
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' );
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var AlignBox = require( 'SCENERY/nodes/AlignBox' );
  var DialogIO = require( 'SUN/DialogIO' );
  var Display = require( 'SCENERY/display/Display' );
  var FullScreen = require( 'SCENERY/util/FullScreen' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  var Panel = require( 'SUN/Panel' );
  var Path = require( 'SCENERY/nodes/Path' );
  var RectangularButtonView = require( 'SUN/buttons/RectangularButtonView' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var SunA11yStrings = require( 'SUN/SunA11yStrings' );
  var Tandem = require( 'TANDEM/Tandem' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // strings
  var closeString = SunA11yStrings.close.value;

  // constants
  var CLOSE_BUTTON_WIDTH = 14;

  /**
   * @param {Node} content - The content to display inside the dialog (not including the title)
   * @param {Object} [options]
   * @constructor
   */
  function Dialog( content, options ) {

    var self = this;

    options = _.extend( {

      /* Margins and spacing:
       ____________________________________________________________________________
      |                                     |                          |           |
      |                                     |                          closeButton |
      |                                     topMargin                  TopMargin   |
      |                                     |                         _|___        |
      |                  ___________________|____________________    |     |       |
      |--------l--------|                                        |-x-|  X  |---c---|
      |        e        |   Title                                | S |_____|   l   |
      |        f        |________________________________________| P           o   |
      |        t        |   |                                    | a           s   |
      |        M        |   ySpacing                             | c           e   |
      |        a        |___|____________________________________| i           B   |
      |        r        |                                        | n           u   |
      |        g        |   Content                              | g           t   |
      |        i        |                                        |             t   |
      |        n        |                                        |             o   |
      |                 |                                        |             n   |
      |                 |                                        |             R   |
      |                 |                                        |             i   |
      |                 |                                        |             g   |
      |                 |                                        |             h   |
      |                 |                                        |             M   |
      |                 |________________________________________|             a   |
      |                                     |                                  r   |
      |                                     |                                  g   |
      |                                     bottomMargin                       i   |
      |                                     |                                  n   |
      |_____________________________________|______________________________________|
       */

      xSpacing: 10, // {number} how far the title and content is placed to the left of the close button
      ySpacing: 10, // {number} vertical space between title and content
      topMargin: 15, // {number} margin above content, or above title if provided
      bottomMargin: 15, // {number} margin below content
      leftMargin: null, // {number|null} margin to the left of the content.  If null, this is computed so that we have
      // the same margins on the left and right of the content.
      closeButtonTopMargin: 10, // {number} margin above the close button
      closeButtonRightMargin: 10, // {number} margin to the right of the close button

      // more Dialog-specific options
      modal: true, // {boolean} modal dialogs prevent interaction with the rest of the sim while open
      title: null, // {Node|null} title to be displayed at top
      titleAlign: 'center', // horizontal alignment of the title: {string} left, right or center

      // {function} which sets the dialog's position in global coordinates. called as
      // layoutStrategy( dialog, simBounds, screenBounds, scale )
      layoutStrategy: Dialog.DEFAULT_LAYOUT_STRATEGY,

      // close button options
      closeButtonListener: function() { self.hide(); },
      closeButtonTouchAreaXDilation: 0,
      closeButtonTouchAreaYDilation: 0,
      closeButtonMouseAreaXDilation: 0,
      closeButtonMouseAreaYDilation: 0,

      // {function|null} called after the dialog is shown, see https://github.com/phetsims/joist/issues/478
      showCallback: null,

      // {function|null} called after the dialog is hidden, see https://github.com/phetsims/joist/issues/478
      hideCallback: null,

      // pass through to Panel options
      cornerRadius: 10, // {number} radius of the dialog's corners
      resize: true, // {boolean} whether to resize if content's size changes
      fill: 'white', // {string|Color}
      stroke: 'black', // {string|Color}
      backgroundPickable: true,
      tandem: Tandem.optional,
      phetioType: DialogIO,
      phetioReadOnly: false, // default to false so it can pass it through to the close button
      phetioState: false, // default to false so it can pass it through to the close button

      // a11y options
      tagName: 'div',
      ariaRole: 'dialog',
      focusOnCloseNode: null // {Node} receives focus on close, if null focus returns to element that had focus on open
    }, options );

    assert && assert( options.xMargin === undefined, 'Dialog sets xMargin' );
    options.xMargin = 0;
    assert && assert( options.yMargin === undefined, 'Dialog sets yMargin' );
    options.yMargin = 0;

    // if left margin is specified in options, use it. otherwise, set it to make the left right gutters symmetrical
    if ( options.leftMargin === null ) {
      options.leftMargin = options.xSpacing + CLOSE_BUTTON_WIDTH + options.closeButtonRightMargin;
    }

    // @private (read-only)
    this.isModal = options.modal;

    // @private
    this.showCallback = options.showCallback;
    this.hideCallback = options.hideCallback;

    // see https://github.com/phetsims/joist/issues/293
    assert && assert( this.isModal, 'Non-modal dialogs not currently supported' );

    // @protected - whether the dialog is showing
    this.isShowing = false;

    // create close button
    var closeButton = new CloseButton( {

      iconLength: CLOSE_BUTTON_WIDTH,
      listener: function() {
        options.closeButtonListener();

        // if listener was fired because of accessibility
        if ( closeButton.buttonModel.isA11yClicking() ) {
          self.focusActiveElement();
        }
      },

      // phet-io
      tandem: options.tandem.createTandem( 'closeButton' ),
      phetioReadOnly: options.phetioReadOnly, // match the readOnly of the Dialog
      phetioState: options.phetioState, // match the state transfer of the Dialog

      // a11y
      tagName: 'button',
      innerContent: closeString
    } );

    // touch/mouse areas for the close button
    closeButton.touchArea = closeButton.bounds.dilatedXY(
      options.closeButtonTouchAreaXDilation,
      options.closeButtonTouchAreaYDilation
    );
    closeButton.mouseArea = closeButton.bounds.dilatedXY(
      options.closeButtonMouseAreaXDilation,
      options.closeButtonMouseAreaYDilation
    );

    // @private (a11y)
    this.closeButton = closeButton;

    // Align content, title, and close button using spacing and margin options

    // align content and title (if provided) vertically
    var contentAndTitle = new VBox( {
      children: options.title ? [ options.title, content ] : [ content ],
      spacing: options.ySpacing,
      align: options.titleAlign
    } );

    // add topMargin, bottomMargin, and leftMargin
    var contentAndTitleWithMargins = new AlignBox( contentAndTitle, {
      topMargin: options.topMargin,
      bottomMargin: options.bottomMargin,
      leftMargin: options.leftMargin
    } );

    // add closeButtonTopMargin and closeButtonRightMargin
    var closeButtonWithMargins = new AlignBox( closeButton, {
      topMargin: options.closeButtonTopMargin,
      rightMargin: options.closeButtonRightMargin
    } );

    // create content for Panel
    var dialogContent = new HBox( {
      children: [ contentAndTitleWithMargins, closeButtonWithMargins ],
      spacing: options.xSpacing,
      align: 'top'
    } );

    Panel.call( this, dialogContent, options );

    var sim = window.phet.joist.sim;

    // @private
    this.updateLayout = function() {
      options.layoutStrategy( self, sim.boundsProperty.value, sim.screenBoundsProperty.value, sim.scaleProperty.value );
    };

    this.updateLayout();

    // @private
    this.sim = sim;

    // a11y - set the order of content, close button first so remaining content can be read from top to bottom
    // with virtual cursor
    this.accessibleOrder = [ closeButton, options.title, content ].filter( function( node ) {
      return node !== undefined;
    } );

    // a11y - set the aria-labelledby relation so that whenever focus enters the dialog the title is read
    if ( options.title && options.title.tagName ) {
      this.addAriaLabelledbyAssociation( {
        thisElementName: AccessiblePeer.PRIMARY_SIBLING,
        otherNode: options.title,
        otherElementName: AccessiblePeer.PRIMARY_SIBLING
      } );
    }

    // must be removed on dispose
    this.sim.resizedEmitter.addListener( this.updateLayout );

    // @private (a11y) - the active element when the dialog is shown, tracked so that focus can be restored on close
    this.activeElement = options.focusOnCloseNode || null;

    // a11y - close the dialog when pressing "escape"
    var escapeListener = {
      keydown: function( event ) {
        if ( event.keyCode === KeyboardUtil.KEY_ESCAPE ) {
          event.preventDefault();
          self.hide();
          self.focusActiveElement();
        }
        else if ( event.keyCode === KeyboardUtil.KEY_TAB && FullScreen.isFullScreen() ) {

          // prevent a particular bug in Windows 7/8.1 Firefox where focus gets trapped in the document
          // when the navigation bar is hidden and there is only one focusable element in the DOM
          // see https://bugzilla.mozilla.org/show_bug.cgi?id=910136
          var activeId = Display.focus.trail.getUniqueId();
          var noNextFocusable = AccessibilityUtil.getNextFocusable().id === activeId;
          var noPreviousFocusable = AccessibilityUtil.getPreviousFocusable().id === activeId;

          if ( noNextFocusable && noPreviousFocusable ) {
            event.preventDefault();
          }
        }
      }
    };
    this.addAccessibleInputListener( escapeListener );

    // @private - to be called on dispose()
    this.disposeDialog = function() {
      self.sim.resizedEmitter.removeListener( self.updateLayout );
      self.removeAccessibleInputListener( escapeListener );

      closeButton.dispose();

      // remove dialog content from scene graph, but don't dispose because Panel
      // needs to remove listeners on the content in its dispose()
      dialogContent.removeAllChildren();
      dialogContent.detach();
    };
  }

  sun.register( 'Dialog', Dialog );

  // @private
  Dialog.DEFAULT_LAYOUT_STRATEGY = function( dialog, simBounds, screenBounds, scale ) {

    // The size is set in the Sim.topLayer, but we need to update the location here
    dialog.center = simBounds.center.times( 1.0 / scale );
  };

  inherit( Panel, Dialog, {

    // @public
    show: function() {
      if ( !this.isShowing ) {
        window.phet.joist.sim.showPopup( this, this.isModal );
        this.isShowing = true;

        // a11y - store the currently active element before hiding all other accessible content
        // so that the active element isn't blurred
        this.activeElement = this.activeElement || Display.focusedNode;
        this.setAccessibleViewsVisible( false );

        // In case the window size has changed since the dialog was hidden, we should try layout out again.
        // See https://github.com/phetsims/joist/issues/362
        this.updateLayout();

        // Do this last
        this.showCallback && this.showCallback();
      }
    },

    /**
     * Hide the dialog.  If you create a new dialog next time you show(), be sure to dispose this
     * dialog instead.
     * @public
     */
    hide: function() {
      if ( this.isShowing ) {

        window.phet.joist.sim.hidePopup( this, this.isModal );
        this.isShowing = false;

        // a11y - when the dialog is hidden, make all ScreenView content visible to assistive technology
        this.setAccessibleViewsVisible( true );

        // Do this last
        this.hideCallback && this.hideCallback();
      }
    },

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {
      this.hide();
      this.disposeDialog();
      Panel.prototype.dispose.call( this );
    },

    /**
     * Hide or show all accessible content related to the sim ScreenViews, navigation bar, and alert content. Instead
     * of using setVisible, we have to remove the subtree of accessible content from each view element in order to
     * prevent an IE11 bug where content remains invisible in the accessibility tree, see
     * https://github.com/phetsims/john-travoltage/issues/247
     *
     * @param {boolean} visible
     */
    setAccessibleViewsVisible: function( visible ) {
      for ( var i = 0; i < this.sim.screens.length; i++ ) {
        this.sim.screens[ i ].view.accessibleVisible = visible;
      }
      this.sim.navigationBar.accessibleVisible = visible;
      this.sim.homeScreen && this.sim.homeScreen.view.setAccessibleVisible( visible );

      // workaround for a strange Edge bug where this child of the navigation bar remains visible,
      // see https://github.com/phetsims/a11y-research/issues/30
      if ( this.sim.navigationBar.keyboardHelpButton ) {
        this.sim.navigationBar.keyboardHelpButton.accessibleVisible = visible;
      }
    },

    /**
     * If there is an active element, focus it.  Should almost always be called after the Dialog has been closed.
     *
     * @public
     * @a11y
     */
    focusActiveElement: function() {
      this.activeElement && this.activeElement.focus();
    },

    /**
     * Place keyboard focus on the close button, useful when opening the dialog with an accessibility interaction.
     * @public
     */
    focusCloseButton: function() {
      this.closeButton.focus();
    }
  } );

  /**
   * The close button for Dialog
   * A flat x
   *
   * @param {Object} [options] - see RectangularPushButton
   * @constructor
   */
  function CloseButton( options ) {
    options = _.extend( {
      iconLength: 7,
      baseColor: 'transparent',
      buttonAppearanceStrategy: RectangularButtonView.FlatAppearanceStrategy,
      xMargin: 0,
      yMargin: 0,
      listener: null // {function} called when the button is pressed
    }, options );

    // close button shape, an 'X'
    var closeButtonShape = new Shape()
      .moveTo( -options.iconLength / 2, -options.iconLength / 2 )
      .lineTo( options.iconLength / 2, options.iconLength / 2 )
      .moveTo( options.iconLength / 2, -options.iconLength / 2 )
      .lineTo( -options.iconLength / 2, options.iconLength / 2 );

    assert && assert( !options.content, 'Dialog.CloseButton sets content' );

    options.content = new Path( closeButtonShape, {
      stroke: 'black',
      lineCap: 'round',
      lineWidth: 2
    } );

    RectangularPushButton.call( this, options );
  }

  sun.register( 'Dialog.CloseButton', CloseButton );

  inherit( RectangularPushButton, CloseButton );

  return Dialog;
} );<|endoftext|>


<commit_before>import { renderToString } from 'react-dom/server';
import { getDataFromTree } from 'react-apollo';
import { ServerStyleSheet } from 'styled-components';

import { wrapWithApolloProvider } from 'react/apollo';

export default client => (Component, props = {}) => {
  const sheet = new ServerStyleSheet();
  const WrappedComponent = wrapWithApolloProvider(client)(Component, props);

  return getDataFromTree(WrappedComponent)
    .then(() => {
      const html = renderToString(sheet.collectStyles(WrappedComponent));
      const styles = sheet.getStyleTags();
      const state = client.extract();

      return {
        html,
        state,
        styles,
      };
    });
};
<commit_msg>Forces ssr to always resolve<commit_after>import { renderToString } from 'react-dom/server';
import { getDataFromTree } from 'react-apollo';
import { ServerStyleSheet } from 'styled-components';

import { wrapWithApolloProvider } from 'react/apollo';

export default client => (Component, props = {}) => {
  const sheet = new ServerStyleSheet();
  const WrappedComponent = wrapWithApolloProvider(client)(Component, props);

  const resolve = () => {
    const html = renderToString(sheet.collectStyles(WrappedComponent));
    const styles = sheet.getStyleTags();
    const state = client.extract();

    return {
      html,
      state,
      styles,
    };
  };

  return getDataFromTree(WrappedComponent)
    .then(resolve)
    .catch(resolve);
};
<|endoftext|>


<commit_before>var countbutton = document.getElementById('wordcount_button');
var topwordsbutton = document.getElementById('topwords_button');
var toplettersbutton = document.getElementById('topletters_button');

var input_area = document.getElementById('words_textarea');

input_area.onkeyup = function() {
  topWords()
}

countbutton.onclick = function() {
  wordCount();
  input_area.onkeyup = function() {
    wordCount()
  }
};

topwordsbutton.onclick = function() {
  topWords();
  input_area.onkeyup = function() {
    topWords()
  }
};

toplettersbutton.onclick = function() {
  topletters();
  input_area.onkeyup = function() {
    topletters()
  }
};

function wordCount() {

  document.getElementById('all_text').innerHTML = null;

  var words = document.getElementById("words_textarea").value.trim().match(/\w+/g);

  var para = document.createElement("p");

  if (words.length > 1) {
    var count = document.createTextNode('Your document is ' + words.length + ' words long.');
  } else if (words.length = 1) {
    var count = document.createTextNode('Your document is ' + words.length + ' word long.');
  } else if (words[0] === "") {
    var count = document.createTextNode('Your document is 0 words long.');
  }

  para.appendChild(count);

  var element = document.getElementById("all_text");
  element.appendChild(para);
}

function topWords() {

  document.getElementById('all_text').innerHTML = null;

  var words = document.getElementById("words_textarea").value.trim().match(/\w+/g);

  var counts = [];

  for (i = 0; i <= words.length - 1; i++) {
    var found = false;
    var current_word = words[i].toLowerCase();
    var irrelevant = ['aboard', 'about', 'above', 'across', 'after', 'against', 'along', 'around', 'before', 'behind', 'below', 'beneath', 'beside', 'between', 'beyond', 'during', 'except', 'following', 'inside', 'minus', 'onto', 'opposite', 'outside', 'round', 'since', 'through', 'toward', 'under', 'underneath', 'unlike', 'until', 'upon', 'without', 'as', 'at', 'but', 'by', 'down', 'for', 'from', 'in', 'into', 'like', 'near', 'next', 'of', 'off', 'on', 'onto', 'out', 'over', 'past', 'plus', 'minus', 'since', 'than', 'to', 'up', 'with'];

    if ($.inArray(current_word, irrelevant) == -1) {
      for (n = 0; n <= counts.length - 1; n++) {
        if (counts[n][0] == current_word) {
          counts[n][1] += 1;
          found = true;
          break;
        }
      }

      if (found == false) {
        counts.push([current_word, 1]);
      }
    }
  }

  var sorted = counts.sort(function(a, b) {
    return b[1] - a[1]
  });

  function createTable() {
    var element = document.getElementById("all_text");

    var tbl = document.createElement('table');

    tbl.style.width = '100%';

    tbl.setAttribute('border', '1');

    var tbdy = document.createElement('tbody');

    var tr = document.createElement('tr');
    var word = document.createElement('td');
    word.innerHTML = "Word"
    var countword = document.createElement('td');
    countword.innerHTML = "Count"
    var suggestions = document.createElement('td');
    suggestions.innerHTML = "Suggestions"

    tr.appendChild(word)
    tr.appendChild(countword)
    tr.appendChild(suggestions)
    tbdy.appendChild(tr);
    tbl.appendChild(tbdy);
    element.appendChild(tbl)

    for (i = 0; i <= counts.length - 1; i++) {

      if (counts[i][1] < 3) {

        var tr = document.createElement('tr');
        var word = document.createElement('td');
        word.innerHTML = counts[i][0]
        var countword = document.createElement('td');
        countword.innerHTML = counts[i][1]
        var suggestion = document.createElement('td');

        tr.appendChild(word)
        tr.appendChild(countword)
        tr.appendChild(suggestion)
        tbdy.appendChild(tr)

      }

      if (counts[i][1] >= 3) {
        var rowIndex = 1;

        $.ajax({
          type: 'GET',
          url: 'http://words.bighugelabs.com/api/2/f5915b0e669a698049a43e63af4704af/' + counts[i][0] + '/json?callback=?',
          dataType: 'json',
          success: function(data) {

            var parsedWord = this.url.split('/')[6];

            var tr = document.createElement('tr');
            var word = document.createElement('td');
            word.innerHTML = parsedWord

            var countword = document.createElement('td');

            for (i = 0; i <= counts.length - 1; i++) {
              if (parsedWord === counts[i][0]) {
                countword.innerHTML = counts[i][1]
                break
              }
            }

            var suggestion = document.createElement('td');

            thesaurusHandling(data,suggestion)

            if (rowIndex > 1) {
              tr.insertBefore(word, tr.childNodes[rowIndex])
              tr.insertBefore(countword, tr.childNodes[rowIndex])
              tr.insertBefore(suggestion, tr.childNodes[rowIndex])
              tbdy.insertBefore(tr, tbdy.childNodes[rowIndex])
              rowIndex += 1
            } else {
              tr.insertBefore(word, tr.childNodes[rowIndex])
              tr.insertBefore(suggestion, tr.childNodes[rowIndex])
              tr.insertBefore(countword, tr.childNodes[rowIndex])
              tbdy.insertBefore(tr, tbdy.childNodes[rowIndex])
              rowIndex += 1
            }
          }
        });
      }
    }
    tbl.appendChild(tbdy)
    element.appendChild(tbl)
  }
  createTable()
};

function thesaurusHandling(data,suggestion) {

  var options = ['noun', 'verb', 'adverb', 'adjective']

  for (i = 0; i <= options.length - 1; i++) {

    var thisWordType = options[i]

    if (typeof data[thisWordType] != "undefined") {

      if (typeof data[thisWordType].syn != "undefined" && typeof data[thisWordType].sim != "undefined") {
        suggestion.innerHTML = suggestion.innerHTML + "" + thisWordType + ": synonyms " + data[thisWordType].syn.slice(0, 3) + " similar " + data[thisWordType].sim.slice(0, 3) + "\n"
      } else if (typeof data[thisWordType].syn != "undefined" && typeof data[thisWordType].sim === "undefined") {
        suggestion.innerHTML = suggestion.innerHTML + "" + thisWordType + ": synonyms " + data[thisWordType].syn.slice(0, 3) + "\n"
      } else if (typeof data[thisWordType].syn === "undefined" && typeof data[thisWordType].sim != "undefined") {
        suggestion.innerHTML = suggestion.innerHTML + "" + thisWordType + ": similar " + data[thisWordType].sim.slice(0, 3) + "\n"
      }
    }
  }
}

function topletters() {

  document.getElementById('all_text').innerHTML = null;

  var letters = document.getElementById("words_textarea").value.trim().match(/[A-z]/g);

  var counts = [];

  for (i = 0; i <= letters.length - 1; i++) {
    var found = false;
    var current_letter = letters[i].toLowerCase();

    for (n = 0; n <= counts.length - 1; n++) {
      if (counts[n][0] == current_letter) {
        counts[n][1] += 1;
        found = true;
        break;
      }
    }

    if (found == false) {
      counts.push([current_letter, 1]);
    }
  }

  var sorted = counts.sort(function(a, b) {
    return b[1] - a[1]
  });

  function createTable() {
    var element = document.getElementById("all_text");

    var tbl = document.createElement('table');

    tbl.style.width = '100%';

    tbl.setAttribute('border', '1');

    var tbdy = document.createElement('tbody');

    var tr = document.createElement('tr');
    var word = document.createElement('td');
    word.innerHTML = "Character"
    var countword = document.createElement('td');
    countword.innerHTML = "Count"
    tr.appendChild(word)
    tr.appendChild(countword)
    tbdy.appendChild(tr);
    tbl.appendChild(tbdy);
    element.appendChild(tbl)

    for (i = 0; i <= counts.length - 1; i++) {
      var tr = document.createElement('tr');
      var word = document.createElement('td');
      word.innerHTML = counts[i][0]
      var countword = document.createElement('td');
      countword.innerHTML = counts[i][1]
      tr.appendChild(word)
      tr.appendChild(countword)
      tbdy.appendChild(tr);
    }
    tbl.appendChild(tbdy);
    element.appendChild(tbl)
  }
  createTable()
}
<commit_msg>linebreak fixed for suggestions column<commit_after>var countbutton = document.getElementById('wordcount_button');
var topwordsbutton = document.getElementById('topwords_button');
var toplettersbutton = document.getElementById('topletters_button');

var input_area = document.getElementById('words_textarea');

input_area.onkeyup = function() {
  topWords()
}

countbutton.onclick = function() {
  wordCount();
  input_area.onkeyup = function() {
    wordCount()
  }
};

topwordsbutton.onclick = function() {
  topWords();
  input_area.onkeyup = function() {
    topWords()
  }
};

toplettersbutton.onclick = function() {
  topletters();
  input_area.onkeyup = function() {
    topletters()
  }
};

function wordCount() {

  document.getElementById('all_text').innerHTML = null;

  var words = document.getElementById("words_textarea").value.trim().match(/\w+/g);

  var para = document.createElement("p");

  if (words.length > 1) {
    var count = document.createTextNode('Your document is ' + words.length + ' words long.');
  } else if (words.length = 1) {
    var count = document.createTextNode('Your document is ' + words.length + ' word long.');
  } else if (words[0] === "") {
    var count = document.createTextNode('Your document is 0 words long.');
  }

  para.appendChild(count);

  var element = document.getElementById("all_text");
  element.appendChild(para);
}

function topWords() {

  document.getElementById('all_text').innerHTML = null;

  var words = document.getElementById("words_textarea").value.trim().match(/\w+/g);

  var counts = [];

  for (i = 0; i <= words.length - 1; i++) {
    var found = false;
    var current_word = words[i].toLowerCase();
    var irrelevant = ['aboard', 'about', 'above', 'across', 'after', 'against', 'along', 'around', 'before', 'behind', 'below', 'beneath', 'beside', 'between', 'beyond', 'during', 'except', 'following', 'inside', 'minus', 'onto', 'opposite', 'outside', 'round', 'since', 'through', 'toward', 'under', 'underneath', 'unlike', 'until', 'upon', 'without', 'as', 'at', 'but', 'by', 'down', 'for', 'from', 'in', 'into', 'like', 'near', 'next', 'of', 'off', 'on', 'onto', 'out', 'over', 'past', 'plus', 'minus', 'since', 'than', 'to', 'up', 'with'];

    if ($.inArray(current_word, irrelevant) == -1) {
      for (n = 0; n <= counts.length - 1; n++) {
        if (counts[n][0] == current_word) {
          counts[n][1] += 1;
          found = true;
          break;
        }
      }

      if (found == false) {
        counts.push([current_word, 1]);
      }
    }
  }

  var sorted = counts.sort(function(a, b) {
    return b[1] - a[1]
  });

  function createTable() {
    var element = document.getElementById("all_text");

    var tbl = document.createElement('table');

    tbl.style.width = '100%';

    tbl.setAttribute('border', '1');

    var tbdy = document.createElement('tbody');

    var tr = document.createElement('tr');
    var word = document.createElement('td');
    word.innerHTML = "Word"
    var countword = document.createElement('td');
    countword.innerHTML = "Count"
    var suggestions = document.createElement('td');
    suggestions.innerHTML = "Suggestions"

    tr.appendChild(word)
    tr.appendChild(countword)
    tr.appendChild(suggestions)
    tbdy.appendChild(tr);
    tbl.appendChild(tbdy);
    element.appendChild(tbl)

    for (i = 0; i <= counts.length - 1; i++) {

      if (counts[i][1] < 3) {

        var tr = document.createElement('tr');
        var word = document.createElement('td');
        word.innerHTML = counts[i][0]
        var countword = document.createElement('td');
        countword.innerHTML = counts[i][1]
        var suggestion = document.createElement('td');

        tr.appendChild(word)
        tr.appendChild(countword)
        tr.appendChild(suggestion)
        tbdy.appendChild(tr)

      }

      if (counts[i][1] >= 3) {
        var rowIndex = 1;

        $.ajax({
          type: 'GET',
          url: 'http://words.bighugelabs.com/api/2/f5915b0e669a698049a43e63af4704af/' + counts[i][0] + '/json?callback=?',
          dataType: 'json',
          success: function(data) {

            var parsedWord = this.url.split('/')[6];

            var tr = document.createElement('tr');
            var word = document.createElement('td');
            word.innerHTML = parsedWord

            var countword = document.createElement('td');

            for (i = 0; i <= counts.length - 1; i++) {
              if (parsedWord === counts[i][0]) {
                countword.innerHTML = counts[i][1]
                break
              }
            }

            var suggestion = document.createElement('td');

            thesaurusHandling(data,suggestion)

            if (rowIndex > 1) {
              tr.insertBefore(word, tr.childNodes[rowIndex])
              tr.insertBefore(countword, tr.childNodes[rowIndex])
              tr.insertBefore(suggestion, tr.childNodes[rowIndex])
              tbdy.insertBefore(tr, tbdy.childNodes[rowIndex])
              rowIndex += 1
            } else {
              tr.insertBefore(word, tr.childNodes[rowIndex])
              tr.insertBefore(suggestion, tr.childNodes[rowIndex])
              tr.insertBefore(countword, tr.childNodes[rowIndex])
              tbdy.insertBefore(tr, tbdy.childNodes[rowIndex])
              rowIndex += 1
            }
          }
        });
      }
    }
    tbl.appendChild(tbdy)
    element.appendChild(tbl)
  }
  createTable()
};

function thesaurusHandling(data,suggestion) {

  var options = ['noun', 'verb', 'adverb', 'adjective']

  for (i = 0; i <= options.length - 1; i++) {

    var thisWordType = options[i]

    if (typeof data[thisWordType] != "undefined") {

      if (typeof data[thisWordType].syn != "undefined" && typeof data[thisWordType].sim != "undefined") {
        suggestion.innerHTML = suggestion.innerHTML + "" + thisWordType + ": synonyms " + data[thisWordType].syn.slice(0, 3) + " similar " + data[thisWordType].sim.slice(0, 3) + "

"
      } else if (typeof data[thisWordType].syn != "undefined" && typeof data[thisWordType].sim === "undefined") {
        suggestion.innerHTML = suggestion.innerHTML + "" + thisWordType + ": synonyms " + data[thisWordType].syn.slice(0, 3) + "

"
      } else if (typeof data[thisWordType].syn === "undefined" && typeof data[thisWordType].sim != "undefined") {
        suggestion.innerHTML = suggestion.innerHTML + "" + thisWordType + ": similar " + data[thisWordType].sim.slice(0, 3) + "

"
      }
    }
  }
}

function topletters() {

  document.getElementById('all_text').innerHTML = null;

  var letters = document.getElementById("words_textarea").value.trim().match(/[A-z]/g);

  var counts = [];

  for (i = 0; i <= letters.length - 1; i++) {
    var found = false;
    var current_letter = letters[i].toLowerCase();

    for (n = 0; n <= counts.length - 1; n++) {
      if (counts[n][0] == current_letter) {
        counts[n][1] += 1;
        found = true;
        break;
      }
    }

    if (found == false) {
      counts.push([current_letter, 1]);
    }
  }

  var sorted = counts.sort(function(a, b) {
    return b[1] - a[1]
  });

  function createTable() {
    var element = document.getElementById("all_text");

    var tbl = document.createElement('table');

    tbl.style.width = '100%';

    tbl.setAttribute('border', '1');

    var tbdy = document.createElement('tbody');

    var tr = document.createElement('tr');
    var word = document.createElement('td');
    word.innerHTML = "Character"
    var countword = document.createElement('td');
    countword.innerHTML = "Count"
    tr.appendChild(word)
    tr.appendChild(countword)
    tbdy.appendChild(tr);
    tbl.appendChild(tbdy);
    element.appendChild(tbl)

    for (i = 0; i <= counts.length - 1; i++) {
      var tr = document.createElement('tr');
      var word = document.createElement('td');
      word.innerHTML = counts[i][0]
      var countword = document.createElement('td');
      countword.innerHTML = counts[i][1]
      tr.appendChild(word)
      tr.appendChild(countword)
      tbdy.appendChild(tr);
    }
    tbl.appendChild(tbdy);
    element.appendChild(tbl)
  }
  createTable()
}
<|endoftext|>


<commit_before>u.template = function(template, json, _options) {
	// u.bug("### TEMPLATE ###");
	// u.bug("template typeof:" + typeof(template));
	// u.bug("json typeof:" + typeof(json));
	// console.log(json);

	var string = "";
	var template_string = "";
	var clone, container, item_template, dom, node_list, type_template, type_parent;

	// settings
	// append results to node
	var append_to_node = false;

	// apply parameters
	if (obj(_options)) {
		var _argument;
		for (_argument in _options) {
			switch (_argument) {
				case "append": 	append_to_node = _options[_argument];			break;
			}
		}
	}

	// Identify template type, depending on template content
	// IE 7 doesn't accept constructor to identify node, using nodeName as replacement

	// HTML Object
	if(obj(template) && typeof(template.nodeName) != "undefined") {
//		u.bug("HTML template")

		type_template = "HTML";
	}
	// JSON Object
	else if(obj(template) && JSON.stringify(template)) {
//		u.bug("JSON template")

		type_template = "JSON";
	}
	// JSON String
	else if(str(template) && template.match(/^(\{|\[)/)) {
//		u.bug("JSON string template")

		type_template = "JSON_STRING";
	}
	// HTML String
	else if(str(template) && template.match(/^<.+>$/)) {
//		u.bug("HTML string template")

		type_template = "HTML_STRING";
	}
	// plain string
	else if(str(template)) {
//		u.bug("plain string template")

		type_template = "STRING";
	}

	// Prepare template string (for string replacement/manipulation)

	// HTML node or HTML STRING
	if(type_template == "HTML_STRING" || type_template == "HTML") {
//		u.bug("TEMPLATE:" + template);

		// HTML node
		if(type_template == "HTML") {
			clone = template.cloneNode(true);
			u.rc(clone, "template");

			if(template.nodeName == "LI") {
				type_parent = "ul";
				container = document.createElement(type_parent);
			}
			else if(template.nodeName == "TR") {
				type_parent = "table";
				container = document.createElement("table").appendChild(document.createElement("tbody"));
			}
			else {
				type_parent = "div";
				container = document.createElement("div");
			}

			container.appendChild(clone);
			template_string = container.innerHTML;
			template_string = template_string.replace(/href\=\"([^\"]+)\"/g, function(string) {return decodeURIComponent(string);});
			template_string = template_string.replace(/src\=\"([^\"]+)\"/g, function(string) {return decodeURIComponent(string);});
		}

		// string based (HTML string)
		else {

			if(template.match(/^<li/i)) {
				type_parent = "ul";
			}
			else if(template.match(/^<tr/i)) {
				type_parent = "table";
			}
			else {
				type_parent = "div";
			}

			template_string = template;
		}
	}
	// JSON object string
	else if(type_template == "JSON") {

		template_string = JSON.stringify(template).replace(/^{/g, "MAN_JSON_START").replace(/}$/g, "MAN_JSON_END");
	}
	// string based (JSON string)
	else if(type_template == "JSON_STRING") {

		template_string = template.replace(/^{/g, "MAN_JSON_START").replace(/}$/g, "MAN_JSON_END");
	}
	// string based (PLAIN string)
	else if(type_template == "STRING") {

		template_string = template;
	}

	// is JSON object or array of objects
	if(obj(json) && ((json.length == undefined && Object.keys(json).length) || json.length)) {

//		console.log("OBJECT:", json, json["pattern"]);

		// multiple results
		if(json.length) {
//			u.bug("multiple results");

			// use _item (WITH UNDERSCORE) to help IE, where item will be interpreted as item()
			for(_item in json) {

				if(json.hasOwnProperty(_item)) {

					item_template = template_string;

					// look for functions and conditional statements
					// TODO: not finalized
	// 				if(item_template.match(/\{\{/)) {
	//
	//
	// 					// first get expression to evaluate
	// //					look for ?
	// //					 and :
	//
	// //					alert(item_template)
	// 					expression = item_template.match(/\{\{(.+?)\}/g);
	//
	// 					//{{checked} ? checked="checked" : }
	//
	// //					expression = template.match(/\{\{(.+?)\}[\s\?]+(.+?)[\s\:]+(.+?)\}/);
	// //					item_template = item_template.replace();
	//
	//
	// 					u.bug("contains if:" + expression)
	//
	//
	//
	// 					if(expression.length && json[item][expression[1]]) {
	// 						u.bug("true")
	// 					}
	// 				}
					// item_template = template.replace(/\{{(.+?)\}/g, "");
					// 						u.bug("temp:" + string)
					// 						if(string.match("{{")) {
					// 							u.bug("function")
					// 						}

											//}
											//else

					string += item_template.replace(/\{(.+?)\}/g, function(string) {

						// if(string == "{children}") {
						// 	if(json[_item].children && json[_item].children.length) {
						// 		var parent_node = template.parentNode.nodeName.toLowerCase();
						// 		var parent_class = template.parentNode.className;
						// 		return '<'+parent_node+' class="'+parent_class+'">'+u.template(template, json[_item].children)+'</'+parent_node+'>';
						// 	}
						// 	else {
						// 		return "";
						// 	}
						// }
						// else

						var key = string.toString().replace(/[\{\}]/g, "");

						// clean up strings
						if(str(json[_item][key]) && json[_item][key]) {
							return json[_item][key].toString().replace(/(\\|\"|\')/g, "\\$1").replace(/\n/, "\\n");
						}
						// return numbers correct
						else if(typeof(json[_item][key]) == "number") {
							// Insert NUM marker, to be replaced with real number
							// (not encapsulated in quotes) before string is returned as JSON
							return "MAN_NUM" + json[_item][key] + "MAN_NUM";
						}
						// return booleans correct
						else if(typeof(json[_item][key]) == "boolean") {
							// Insert BOOL marker, to be replaced with real boolean
							// (not encapsulated in quotes) before string is returned as JSON
							return "MAN_BOOL" + json[_item][key] + "MAN_BOOL";
						}
						// return null correct
						else if(json[_item][key] === null) {
							// Insert NULL marker, to be replaced with null or empty string
							// (depending on template type)
							return "MAN_NULL";
						}
						// return objects correct
						else if(obj(json[_item][key])) {
							// Insert OBJ marker, to be replaced with real object
							// (not encapsulated in quotes) before string is returned as JSON
							return "MAN_OBJ" + JSON.stringify(json[_item][key]).replace(/(\"|\')/g, "\\$1") + "MAN_OBJ";
						}
						else {
							return "";
						}
					});
				}

			}
		}
		// only one result (or empty array)
		else {
			// u.bug("single result");

			string += template_string.replace(/\{(.+?)\}/g, function(string) {

				var key = string.toString().replace(/[\{\}]/g, "");

				// clean up strings
				if(str(json[key]) && json[key]) {
					return json[key].replace(/(\\|\"|\')/g, "\\$1").replace(/\n/, "\\n");
				}
				// return numbers correct
				else if(typeof(json[key]) == "number") {
					// Insert NUM marker, to be replaced with real number
					// (not encapsulated in quotes) before string is returned as JSON
					return "MAN_NUM" + json[key] + "MAN_NUM";
				}
				// return booleans correct
				else if(typeof(json[key]) == "boolean") {
					// Insert BOOL marker, to be replaced with real boolean
					// (not encapsulated in quotes) before string is returned as JSON
					return "MAN_BOOL" + json[key] + "MAN_BOOL";
				}
				// return null correct
				else if(json[key] === null) {
					// Insert NULL marker, to be replaced with null or empty string
					// (depending on template type)
					return "MAN_NULL";
				}
				// return objects correct
				else if(obj(json[key])) {
					// Insert OBJ marker, to be replaced with real object
					// (not encapsulated in quotes) before string is returned as JSON
					return "MAN_OBJ" + JSON.stringify(json[key]).replace(/(\"|\')/g, "\\$1") + "MAN_OBJ";
				}
				else {
					return "";
				}
			});
		}
	}
	// else {
	// 	console.log("no results");
	// }

	// Post process string for MARKERS
	// Prepare final return object/string

	// html strings or objects
	if(type_template == "HTML_STRING" || type_template == "HTML") {

		// BOOLEANS + NUMBERS
		string = string.replace(/MAN_(BOOL|NUM)(.+?(?=MAN_(BOOL|NUM)))MAN_(BOOL|NUM)/g, "$2");

		// null value = empty string
		string = string.replace(/MAN_NULL/g, "");

		// OBJECTS
		string = string.replace(/MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ/g, function(string) {
			// remove marker
			string = string.replace(/MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ/g, "$1");
			// unescape quotes
			return string.replace(/\\(\\|"|')/g, "$1");
		});

		// unescape quotes when outputting to HTML
		string = string.replace(/\\(\\|"|')/g, "$1");

		// IE <=9 doesn't support table.innerHTML, so wrap node in div and innerHTML entire table
		if(type_parent == "table") {
			dom = document.createElement("div");
			dom.innerHTML = "

"+string+"

";
			dom = u.qs("tbody", dom);
		}
		else {
			dom = document.createElement(type_parent);
			dom.innerHTML = string;
		}

		// should children be appended to node automatically
		if(append_to_node) {
			node_list = [];

			// doing innerHTML cancels events on header - instead use dom
			while(dom.childNodes.length) {
				node_list.push(u.ae(append_to_node, dom.childNodes[0]));
			}
			// return array of appended nodes
			return node_list;
		}

		// return childNodes object
		return dom.childNodes;
	}

	// json strings or objects
	else if(type_template == "JSON_STRING" || type_template == "JSON") {

		// BOOLEANS + NUMBERS
		string = string.replace(/[\"]?MAN_(BOOL|NUM)(.+?(?=MAN_(BOOL|NUM)))MAN_(BOOL|NUM)[\"]?/g, "$2");

		// null value = null
		string = string.replace(/[\"]?MAN_NULL[\"]?/g, "null");

		// OBJECTS
		string = string.replace(/[\"]?MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ[\"]?/g, function(string) {
			// remove marker
			string = string.replace(/[\"]?MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ[\"]?/g, "$1");
			// unescape quotes
			return string.replace(/\\("|')/g, "$1");
		});

		return eval("["+string.replace(/MAN_JSON_START/g, "{").replace(/MAN_JSON_END/g, "},")+"]");
	}

	// plain string
	else if(type_template == "STRING") {

		// BOOLEANS + NUMBERS
		string = string.replace(/MAN_(BOOL|NUM)(.+?(?=MAN_(BOOL|NUM)))MAN_(BOOL|NUM)/g, "$2");

		// null value = empty string
		string = string.replace(/MAN_NULL/g, "");

		// OBJECTS
		string = string.replace(/MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ/g, function(string) {
			// remove marker
			string = string.replace(/MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ/g, "$1");
			// unescape quotes
			return string.replace(/\\(\\|"|')/g, "$1");
		});

		// remove any double escapes
		return string.replace(/\\(\\|"|')/g, "$1");
	}

}

<commit_msg>Handle multiple newlines<commit_after>u.template = function(template, json, _options) {
	// u.bug("### TEMPLATE ###");
	// u.bug("template typeof:" + typeof(template));
	// u.bug("json typeof:" + typeof(json));
	// console.log(json);

	var string = "";
	var template_string = "";
	var clone, container, item_template, dom, node_list, type_template, type_parent;

	// settings
	// append results to node
	var append_to_node = false;

	// apply parameters
	if (obj(_options)) {
		var _argument;
		for (_argument in _options) {
			switch (_argument) {
				case "append": 	append_to_node = _options[_argument];			break;
			}
		}
	}

	// Identify template type, depending on template content
	// IE 7 doesn't accept constructor to identify node, using nodeName as replacement

	// HTML Object
	if(obj(template) && typeof(template.nodeName) != "undefined") {
//		u.bug("HTML template")

		type_template = "HTML";
	}
	// JSON Object
	else if(obj(template) && JSON.stringify(template)) {
//		u.bug("JSON template")

		type_template = "JSON";
	}
	// JSON String
	else if(str(template) && template.match(/^(\{|\[)/)) {
//		u.bug("JSON string template")

		type_template = "JSON_STRING";
	}
	// HTML String
	else if(str(template) && template.match(/^<.+>$/)) {
//		u.bug("HTML string template")

		type_template = "HTML_STRING";
	}
	// plain string
	else if(str(template)) {
//		u.bug("plain string template")

		type_template = "STRING";
	}

	// Prepare template string (for string replacement/manipulation)

	// HTML node or HTML STRING
	if(type_template == "HTML_STRING" || type_template == "HTML") {
//		u.bug("TEMPLATE:" + template);

		// HTML node
		if(type_template == "HTML") {
			clone = template.cloneNode(true);
			u.rc(clone, "template");

			if(template.nodeName == "LI") {
				type_parent = "ul";
				container = document.createElement(type_parent);
			}
			else if(template.nodeName == "TR") {
				type_parent = "table";
				container = document.createElement("table").appendChild(document.createElement("tbody"));
			}
			else {
				type_parent = "div";
				container = document.createElement("div");
			}

			container.appendChild(clone);
			template_string = container.innerHTML;
			template_string = template_string.replace(/href\=\"([^\"]+)\"/g, function(string) {return decodeURIComponent(string);});
			template_string = template_string.replace(/src\=\"([^\"]+)\"/g, function(string) {return decodeURIComponent(string);});
		}

		// string based (HTML string)
		else {

			if(template.match(/^<li/i)) {
				type_parent = "ul";
			}
			else if(template.match(/^<tr/i)) {
				type_parent = "table";
			}
			else {
				type_parent = "div";
			}

			template_string = template;
		}
	}
	// JSON object string
	else if(type_template == "JSON") {

		template_string = JSON.stringify(template).replace(/^{/g, "MAN_JSON_START").replace(/}$/g, "MAN_JSON_END");
	}
	// string based (JSON string)
	else if(type_template == "JSON_STRING") {

		template_string = template.replace(/^{/g, "MAN_JSON_START").replace(/}$/g, "MAN_JSON_END");
	}
	// string based (PLAIN string)
	else if(type_template == "STRING") {

		template_string = template;
	}

	// is JSON object or array of objects
	if(obj(json) && ((json.length == undefined && Object.keys(json).length) || json.length)) {

//		console.log("OBJECT:", json, json["pattern"]);

		// multiple results
		if(json.length) {
//			u.bug("multiple results");

			// use _item (WITH UNDERSCORE) to help IE, where item will be interpreted as item()
			for(_item in json) {

				if(json.hasOwnProperty(_item)) {

					item_template = template_string;

					// look for functions and conditional statements
					// TODO: not finalized
	// 				if(item_template.match(/\{\{/)) {
	//
	//
	// 					// first get expression to evaluate
	// //					look for ?
	// //					 and :
	//
	// //					alert(item_template)
	// 					expression = item_template.match(/\{\{(.+?)\}/g);
	//
	// 					//{{checked} ? checked="checked" : }
	//
	// //					expression = template.match(/\{\{(.+?)\}[\s\?]+(.+?)[\s\:]+(.+?)\}/);
	// //					item_template = item_template.replace();
	//
	//
	// 					u.bug("contains if:" + expression)
	//
	//
	//
	// 					if(expression.length && json[item][expression[1]]) {
	// 						u.bug("true")
	// 					}
	// 				}
					// item_template = template.replace(/\{{(.+?)\}/g, "");
					// 						u.bug("temp:" + string)
					// 						if(string.match("{{")) {
					// 							u.bug("function")
					// 						}

											//}
											//else

					string += item_template.replace(/\{(.+?)\}/g, function(string) {

						// if(string == "{children}") {
						// 	if(json[_item].children && json[_item].children.length) {
						// 		var parent_node = template.parentNode.nodeName.toLowerCase();
						// 		var parent_class = template.parentNode.className;
						// 		return '<'+parent_node+' class="'+parent_class+'">'+u.template(template, json[_item].children)+'</'+parent_node+'>';
						// 	}
						// 	else {
						// 		return "";
						// 	}
						// }
						// else

						var key = string.toString().replace(/[\{\}]/g, "");

						// clean up strings
						if(str(json[_item][key]) && json[_item][key]) {
							return json[_item][key].toString().replace(/(\\|\"|\')/g, "\\$1").replace(/\n/g, "\\n");
						}
						// return numbers correct
						else if(typeof(json[_item][key]) == "number") {
							// Insert NUM marker, to be replaced with real number
							// (not encapsulated in quotes) before string is returned as JSON
							return "MAN_NUM" + json[_item][key] + "MAN_NUM";
						}
						// return booleans correct
						else if(typeof(json[_item][key]) == "boolean") {
							// Insert BOOL marker, to be replaced with real boolean
							// (not encapsulated in quotes) before string is returned as JSON
							return "MAN_BOOL" + json[_item][key] + "MAN_BOOL";
						}
						// return null correct
						else if(json[_item][key] === null) {
							// Insert NULL marker, to be replaced with null or empty string
							// (depending on template type)
							return "MAN_NULL";
						}
						// return objects correct
						else if(obj(json[_item][key])) {
							// Insert OBJ marker, to be replaced with real object
							// (not encapsulated in quotes) before string is returned as JSON
							return "MAN_OBJ" + JSON.stringify(json[_item][key]).replace(/(\"|\')/g, "\\$1") + "MAN_OBJ";
						}
						else {
							return "";
						}
					});
				}

			}
		}
		// only one result (or empty array)
		else {
			// u.bug("single result");

			string += template_string.replace(/\{(.+?)\}/g, function(string) {

				var key = string.toString().replace(/[\{\}]/g, "");

				// clean up strings
				if(str(json[key]) && json[key]) {
					return json[key].replace(/(\\|\"|\')/g, "\\$1").replace(/\n/g, "\\n");
				}
				// return numbers correct
				else if(typeof(json[key]) == "number") {
					// Insert NUM marker, to be replaced with real number
					// (not encapsulated in quotes) before string is returned as JSON
					return "MAN_NUM" + json[key] + "MAN_NUM";
				}
				// return booleans correct
				else if(typeof(json[key]) == "boolean") {
					// Insert BOOL marker, to be replaced with real boolean
					// (not encapsulated in quotes) before string is returned as JSON
					return "MAN_BOOL" + json[key] + "MAN_BOOL";
				}
				// return null correct
				else if(json[key] === null) {
					// Insert NULL marker, to be replaced with null or empty string
					// (depending on template type)
					return "MAN_NULL";
				}
				// return objects correct
				else if(obj(json[key])) {
					// Insert OBJ marker, to be replaced with real object
					// (not encapsulated in quotes) before string is returned as JSON
					return "MAN_OBJ" + JSON.stringify(json[key]).replace(/(\"|\')/g, "\\$1") + "MAN_OBJ";
				}
				else {
					return "";
				}
			});
		}
	}
	// else {
	// 	console.log("no results");
	// }

	// Post process string for MARKERS
	// Prepare final return object/string

	// html strings or objects
	if(type_template == "HTML_STRING" || type_template == "HTML") {

		// BOOLEANS + NUMBERS
		string = string.replace(/MAN_(BOOL|NUM)(.+?(?=MAN_(BOOL|NUM)))MAN_(BOOL|NUM)/g, "$2");

		// null value = empty string
		string = string.replace(/MAN_NULL/g, "");

		// OBJECTS
		string = string.replace(/MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ/g, function(string) {
			// remove marker
			string = string.replace(/MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ/g, "$1");
			// unescape quotes
			return string.replace(/\\(\\|"|')/g, "$1");
		});

		// unescape quotes when outputting to HTML
		string = string.replace(/\\(\\|"|')/g, "$1");

		// IE <=9 doesn't support table.innerHTML, so wrap node in div and innerHTML entire table
		if(type_parent == "table") {
			dom = document.createElement("div");
			dom.innerHTML = "

"+string+"

";
			dom = u.qs("tbody", dom);
		}
		else {
			dom = document.createElement(type_parent);
			dom.innerHTML = string;
		}

		// should children be appended to node automatically
		if(append_to_node) {
			node_list = [];

			// doing innerHTML cancels events on header - instead use dom
			while(dom.childNodes.length) {
				node_list.push(u.ae(append_to_node, dom.childNodes[0]));
			}
			// return array of appended nodes
			return node_list;
		}

		// return childNodes object
		return dom.childNodes;
	}

	// json strings or objects
	else if(type_template == "JSON_STRING" || type_template == "JSON") {

		// BOOLEANS + NUMBERS
		string = string.replace(/[\"]?MAN_(BOOL|NUM)(.+?(?=MAN_(BOOL|NUM)))MAN_(BOOL|NUM)[\"]?/g, "$2");

		// null value = null
		string = string.replace(/[\"]?MAN_NULL[\"]?/g, "null");

		// OBJECTS
		string = string.replace(/[\"]?MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ[\"]?/g, function(string) {
			// remove marker
			string = string.replace(/[\"]?MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ[\"]?/g, "$1");
			// unescape quotes
			return string.replace(/\\("|')/g, "$1");
		});

		return eval("["+string.replace(/MAN_JSON_START/g, "{").replace(/MAN_JSON_END/g, "},")+"]");
	}

	// plain string
	else if(type_template == "STRING") {

		// BOOLEANS + NUMBERS
		string = string.replace(/MAN_(BOOL|NUM)(.+?(?=MAN_(BOOL|NUM)))MAN_(BOOL|NUM)/g, "$2");

		// null value = empty string
		string = string.replace(/MAN_NULL/g, "");

		// OBJECTS
		string = string.replace(/MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ/g, function(string) {
			// remove marker
			string = string.replace(/MAN_OBJ(.+?(?=MAN_OBJ))MAN_OBJ/g, "$1");
			// unescape quotes
			return string.replace(/\\(\\|"|')/g, "$1");
		});

		// remove any double escapes
		return string.replace(/\\(\\|"|')/g, "$1");
	}

}

<|endoftext|>


<commit_before><commit_msg>no draw on host<commit_after><|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var cluster = require('../')
  , http = require('http');

var body = 'Hello World'
  , len = body.length;
var server = http.createServer(function(req, res){
  res.writeHead(200, { 'Content-Length': len });
  res.end(body);
});

cluster(server)
  .set('working directory', '/')
  .listen(3000);<commit_msg>set workers for basic example<commit_after>
/**
 * Module dependencies.
 */

var cluster = require('../')
  , http = require('http');

var body = 'Hello World'
  , len = body.length;
var server = http.createServer(function(req, res){
  res.writeHead(200, { 'Content-Length': len });
  res.end(body);
});

cluster(server)
  .set('workers', 4)
  .set('working directory', '/')
  .listen(3000);<|endoftext|>


<commit_before><commit_msg>First adjustments to smartboard.js to fit to new project and way of doing things<commit_after><|endoftext|>


<commit_before>/*
    A small election system
*/

var core = undefined;

var votingVotings = new Array();

function countVotes(chid)
{
    var votingResult = [];
    var votingResultMsg = "";
    if(Object.size(votingVotings[chid].voters) == 0)
    {
        votingResultMsg = "No one voted :confused:";
    } else {
        var voters = votingVotings[chid].voters;
        for(var i=0; i<votingVotings[chid].votingVariants.length; i++)
        {
            votingResult[i] = new Array();
            votingResult[i].title = votingVotings[chid].votingVariants[i];
            votingResult[i].votes = 0;
        }
        for(v in voters)
        {
            var voteTo = voters[v];
            votingResult[voteTo].votes += 1;
        }
        for(i in votingResult)
        {
            votingResultMsg += (parseInt(i, 10)+1) + ") " + votingResult[i].title + " --> **" + votingResult[i].votes + "**\n";
        }
    }
    return votingResultMsg;
}

var voting = function(bot, message, args)
{
    var chid = message.channel.id;
    if(typeof(votingVotings[message.channel.id])==='undefined')
    {
        votingVotings[chid] = new Array();
        votingVotings[chid].votingInProcess = false;
    }
    core.foxylogInfo("====Voting mechanism====");
    //on "start <variants>" begin vote counts
    if(args.indexOf("start ") != -1)
    {
        core.foxylogInfo("--start--");
        if(votingVotings[chid].votingInProcess)
        {
            message.reply("Another voting in process! Finish this voting and then you will be able to start new one!", core.msgSendError);
            return;
        }
        var variants = args.slice(6);
        if(variants.trim()=="")
        {
            message.reply("Nothing to vote!", core.msgSendError);
            return;
        }
        votingVotings[chid].voters = [];//If user is here - ignore next votes. Revoting is not allowed
        votingVotings[chid].votingVariants = variants.split(";");
        var voteMsg = "**Voting variants:**\n"
        for(var i=0;i<votingVotings[chid].votingVariants.length; i++)
        {
            votingVotings[message.channel.id].votingVariants[i] = votingVotings[chid].votingVariants[i].trim();
            voteMsg += " **" + (i+1) + ")** " + votingVotings[chid].votingVariants[i] + "\n";
        }

        voteMsg += "\nTo vote type __/foxy vote **N**__. N is a number of variant. Re-voting is allowed."

        votingVotings[chid].votingInProcess = true;
        message.channel.send("Voting started!\n\n" + voteMsg).catch(core.msgSendError);
    }
    //on "stats" show result
    else
    if(args.indexOf("stats") != -1)
    {
        core.foxylogInfo("--stats--");
        if(!votingVotings[chid].votingInProcess)
        {
            message.channel.send("No votings in this channel! Type **/foxy help voting** to learn how to work with voting.").catch(core.msgSendError);
            return;
        }
        var votingResultMsg = countVotes(chid);
        message.channel.send("**Current voting state**:\n" + votingResultMsg).catch(core.msgSendError);
    }
    //on "stop" abort voting process and show result
    else
    if((args.indexOf("stop") != -1) || (args.indexOf("end") != -1))
    {
        core.foxylogInfo("--stop--");
        if(!votingVotings[chid].votingInProcess)
        {
            message.reply("No voting in this channel to stop!", core.msgSendError);
            return;
        }
        votingVotings[chid].votingInProcess = false;
        var votingResultMsg = countVotes(chid);
        message.channel.send("**Voting stopped!**\n" + votingResultMsg).catch(core.msgSendError);
    } else {
    //on "<number of variant>" add voter
        core.foxylogInfo("--vote--");
        if(!votingVotings[chid].votingInProcess)
        {
            message.reply("No votings to vote! Type **/foxy help voting** to learn how to work with voting.", core.msgSendError);
            return;
        }
        core.foxylogInfo("Got vote: " + args.trim() );
        var vote = parseInt(args.trim(), 10);
        if( (vote != NaN) )
        {
            if( (vote > 0) && (vote <= votingVotings[chid].votingVariants.length) )
            {
                core.foxylogInfo("Vote remembered: " + vote);
                votingVotings[chid].voters[message.author.id] = (vote-1);
            } else {
                message.reply("Out of range!, Vote variant from 1 to " + (votingVotings[chid].votingVariants.length), core.msgSendError);
            }
        } else {
            //foxylogInfo("Vote invalid: " + vote);
            message.reply("Unknown command! Accepted commands are **start**, **stats**, **stop**, or integer of the variant!", core.msgSendError);
        }
    }
}

// Initialize plugin and here you can add custom Foxy's commands
function registerCommands(/*bot_commands.js module*/ foxyCore)
{
    core = foxyCore;
    core.addCMD(["voting",   voting,      "Wanna choice some? Let's vote!\n"+
                                          "__*Syntax:*__:\n\n"+
                                          " **/foxy voting start __Bonana; Sausidge; Apple; Chicken; Fried mice__**\n" +
                                          "__Start a voting with a list of variants (there are must be splited with semicolons!)__\n\n" +
                                          " **/foxy vote __<variant ID>__**\n__Do Vote for any variant you are prefer (from 1 to N)__\n\n" +
                                          " **/foxy voting stats**\n__Print a result without aborting of the voting__\n\n" +
                                          " **/foxy voting stop**\n **/foxy voting end**\n__Stop voting and print a result__\n", [], true]);

    core.addSynonimOf("voting", "vote");
    core.addSynonimOf("voting", "votes");
    core.addSynonimOf("voting", "votings");
}

module.exports =
{
    registerCommands:   registerCommands,
};
<commit_msg>Election Committee: Added on-vote reactions to confirm acception of a votes<commit_after>/*
    A small election system
*/

var core = undefined;

var votingVotings = new Array();

function countVotes(chid)
{
    var votingResult = [];
    var votingResultMsg = "";
    if(Object.size(votingVotings[chid].voters) == 0)
    {
        votingResultMsg = "No one voted :confused:";
    } else {
        var voters = votingVotings[chid].voters;
        for(var i=0; i<votingVotings[chid].votingVariants.length; i++)
        {
            votingResult[i] = new Array();
            votingResult[i].title = votingVotings[chid].votingVariants[i];
            votingResult[i].votes = 0;
        }
        for(v in voters)
        {
            var voteTo = voters[v];
            votingResult[voteTo].votes += 1;
        }
        for(i in votingResult)
        {
            votingResultMsg += (parseInt(i, 10)+1) + ") " + votingResult[i].title + " --> **" + votingResult[i].votes + "**\n";
        }
    }
    return votingResultMsg;
}

var voting = function(bot, message, args)
{
    var chid = message.channel.id;
    if(typeof(votingVotings[message.channel.id])==='undefined')
    {
        votingVotings[chid] = new Array();
        votingVotings[chid].votingInProcess = false;
    }
    core.foxylogInfo("====Voting mechanism====");
    //on "start <variants>" begin vote counts
    if(args.indexOf("start ") != -1)
    {
        core.foxylogInfo("--start--");
        if(votingVotings[chid].votingInProcess)
        {
            message.reply("Another voting in process! Finish this voting and then you will be able to start new one!", core.msgSendError);
            return;
        }
        var variants = args.slice(6);
        if(variants.trim()=="")
        {
            message.reply("Nothing to vote!", core.msgSendError);
            return;
        }
        votingVotings[chid].voters = [];//If user is here - ignore next votes. Revoting is not allowed
        votingVotings[chid].votingVariants = variants.split(";");
        var voteMsg = "**Voting variants:**\n"
        for(var i=0;i<votingVotings[chid].votingVariants.length; i++)
        {
            votingVotings[message.channel.id].votingVariants[i] = votingVotings[chid].votingVariants[i].trim();
            voteMsg += " **" + (i+1) + ")** " + votingVotings[chid].votingVariants[i] + "\n";
        }

        voteMsg += "\nTo vote type __/foxy vote **N**__. N is a number of variant. Re-voting is allowed."

        votingVotings[chid].votingInProcess = true;
        message.channel.send("Voting started!\n\n" + voteMsg).catch(core.msgSendError);
    }
    //on "stats" show result
    else
    if(args.indexOf("stats") != -1)
    {
        core.foxylogInfo("--stats--");
        if(!votingVotings[chid].votingInProcess)
        {
            message.channel.send("No votings in this channel! Type **/foxy help voting** to learn how to work with voting.").catch(core.msgSendError);
            return;
        }
        var votingResultMsg = countVotes(chid);
        message.channel.send("**Current voting state**:\n" + votingResultMsg).catch(core.msgSendError);
    }
    //on "stop" abort voting process and show result
    else
    if((args.indexOf("stop") != -1) || (args.indexOf("end") != -1))
    {
        core.foxylogInfo("--stop--");
        if(!votingVotings[chid].votingInProcess)
        {
            message.reply("No voting in this channel to stop!", core.msgSendError);
            return;
        }
        votingVotings[chid].votingInProcess = false;
        var votingResultMsg = countVotes(chid);
        message.channel.send("**Voting stopped!**\n" + votingResultMsg).catch(core.msgSendError);
    } else {
    //on "<number of variant>" add voter
        core.foxylogInfo("--vote--");
        if(!votingVotings[chid].votingInProcess)
        {
            message.reply("No votings to vote! Type **/foxy help voting** to learn how to work with voting.", core.msgSendError);
            return;
        }
        core.foxylogInfo("Got vote: " + args.trim() );
        var vote = parseInt(args.trim(), 10);
        if( (vote != NaN) )
        {
            if( (vote > 0) && (vote <= votingVotings[chid].votingVariants.length) )
            {
                core.foxylogInfo("Vote remembered: " + vote);
                if(typeof(votingVotings[chid].voters[message.author.id]) != 'undefined')
                    message.react("🔄");//Mark re-voting
                votingVotings[chid].voters[message.author.id] = (vote - 1);
                message.react("✅");//Mark vote as accepted
            } else {
                message.reply("Out of range!, Vote variant from 1 to " + (votingVotings[chid].votingVariants.length), core.msgSendError);
            }
        } else {
            //foxylogInfo("Vote invalid: " + vote);
            message.reply("Unknown command! Accepted commands are **start**, **stats**, **stop**, or integer of the variant!", core.msgSendError);
        }
    }
}

// Initialize plugin and here you can add custom Foxy's commands
function registerCommands(/*bot_commands.js module*/ foxyCore)
{
    core = foxyCore;
    core.addCMD(["voting",   voting,      "Wanna choice some? Let's vote!\n"+
                                          "__*Syntax:*__:\n\n"+
                                          " **/foxy voting start __Bonana; Sausidge; Apple; Chicken; Fried mice__**\n" +
                                          "__Start a voting with a list of variants (there are must be splited with semicolons!)__\n\n" +
                                          " **/foxy vote __<variant ID>__**\n__Do Vote for any variant you are prefer (from 1 to N)__\n\n" +
                                          " **/foxy voting stats**\n__Print a result without aborting of the voting__\n\n" +
                                          " **/foxy voting stop**\n **/foxy voting end**\n__Stop voting and print a result__\n", [], true]);

    core.addSynonimOf("voting", "vote");
    core.addSynonimOf("voting", "votes");
    core.addSynonimOf("voting", "votings");
}

module.exports =
{
    registerCommands:   registerCommands,
};
<|endoftext|>