<commit_before>$(window).on('load', documentReady);

var menuTree = {};
var topMenuItemTemplate;
var sideMenuItemTemplate;
var thePath;

//Side menu template
sideMenuItemTemplate = '';
sideMenuItemTemplate += '  ';
sideMenuItemTemplate += '
';
sideMenuItemTemplate += '    ';
sideMenuItemTemplate += '   <\/div>';
sideMenuItemTemplate += '';
sideMenuItemTemplate += '
';
sideMenuItemTemplate += '
{{text}}<\/p>';
sideMenuItemTemplate += '   <\/div>';
sideMenuItemTemplate += '';
sideMenuItemTemplate += '
';
sideMenuItemTemplate += '     ';
sideMenuItemTemplate += '   <\/div>';
sideMenuItemTemplate += '  <\/a>';

//Top menu template
topMenuItemTemplate = '';
topMenuItemTemplate += '{{text}}<\/a>';

function documentReady() {
  thePath = window.location.pathname;
  thePath = thePath.substring(1, thePath.length - 1);

  getMenuFile(menuFileLoaded);

  // Top menu color change
  $(window).on("scroll", function () {
    setColorMenu();
  });

  setColorMenu();
  function setColorMenu() {
    if ($(window).scrollTop() > 0 || thePath !== '/') {
      $("#header").addClass("active");
    } else {
      //remove the background property so it comes transparent again (defined in your css)
      $("#header").removeClass("active");
    }
  }

  //Set smoothscrolling
  $('a[href^="#"]').on('click', function (e) {
    e.preventDefault();

    var target = this.hash;
    var $target = $(target);

    $('html, body').stop().animate({
      'scrollTop': $target.offset().top - 100
    }, 350, 'swing', function () {
      // window.location.hash = target;
    });
  });

  // Set mobile menu
  $('#menu-toggle').on('click', function (e) {
    $('.top-menu-items').toggleClass('open');
  });

  $('top-menu-item').on('click', function(e) {
    $('.top-menu-items').removeClass('open');
  });

  $('pre code').each(function(i, block) {
    var codeText = $(this).text();
    hljs.highlightBlock(block);

    $(this).append('<button class="copy-button" data-clipboard-text=\''+codeText+'\'>
  Copy

  Copied to clipboard
</button>');
  });

  var client = new ZeroClipboard( $('.copy-button') );

  $('.copy-button').click(function() {
    var self = this;
    $(self).addClass('clicked');

    window.setTimeout(function () {
      $(self).removeClass('clicked');
    }, 5000);

  });

}

function getMenuFile(callback) {
  $.getJSON(theBaseUrl + '/menu.json', function (data) {
    callback(data);
  });

  //unsticky menu
  $(window).scroll(function () {
    var offsetTop = $('.footer').offset().top;
    var scrollBottom = $(window).scrollTop() + $(window).height();

    if(scrollBottom >= offsetTop) {
      $('.menu').css('position', 'absolute');
      $('.menu').css('bottom', '0');

    } else {
      $('.menu').css('position', 'fixed');
      $('.menu').css('bottom', '');
    }
  });

  // Set target="_blank" on external links
  $(document.links).filter(function() {
    return this.hostname != window.location.hostname;
  }).attr('target', '_blank');

  //Initi slicknav
  var slickNavConfig = {
    label: '',
    allowParentLinks: false,
    closedSymbol: '',
    openedSymbol: '',
    brand: ''
  }

  $(function(){
    $('#mobile-menu').slicknav(slickNavConfig);
  });

  //check if emails are correct
  var inputfields = $('#mc-embedded-subscribe-form input');

  $('#mc-embedded-subscribe-form input').on('change paste keyup', function () {
    var emailValue = $(this).val();

    //
    if(isEmail(emailValue) && (emailValue !== '')) {
      $(this).parent().find('.button').removeClass('not-active');
    } else {
      $(this).parent().find('.button').addClass('not-active');
    }

  });

  function isEmail(email) {
    var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
    return regex.test(email);
  }

}

function menuFileLoaded(data) {
  //parents are set
  setParents([], data.children);

  // Find path
  findPath(data.children, thePath, function(allActive){
    allActive.forEach(function(oneActive) {
      oneActive.active = true;
    });
  });

  data.children.forEach(function (topMenuItem) {
      var html = Mustache.render(topMenuItemTemplate, topMenuItem);
      var renderedTopMenuItem = $.parseHTML(html);
      if(topMenuItem.active) {
        $(renderedTopMenuItem).addClass('active');
        topMenuItem.children && buildSideMenu(topMenuItem);
      }
      topMenuItem.visible && $('#top-menu-items').append(renderedTopMenuItem);
  });
  //build mobile menu
  buildMobileMenu(data);
}

function buildMobileMenu(data) {
  data.children.forEach(function(menuItem) {
    var mobileListItemTop = createMobileListItem(menuItem.path, menuItem.text);

    var secondLevelUl = $.parseHTML('

');
    menuItem.children && menuItem.children.forEach(function(menuItemSecond) {
      if(menuItemSecond.visible) {
        var mobileSecondListItem = createMobileListItem(menuItemSecond.path, menuItemSecond.text);
        $(secondLevelUl).append(mobileSecondListItem);

        var thirdLevelUl = $.parseHTML('

');
        menuItemSecond.children && menuItemSecond.children.forEach(function (menuItemThird) {
          if(menuItemThird.visible) {
            var mobileThirdListItem = createMobileListItem(menuItemThird.path, menuItemThird.text);
            $(thirdLevelUl).append(mobileThirdListItem);
          }
        });
        menuItemSecond.children && $(mobileSecondListItem).append(thirdLevelUl);
        }
    });
    menuItem.children && $(mobileListItemTop).append(secondLevelUl);
    menuItem.visible && $('#mobile-menu').append(mobileListItemTop);
  });
}

function createMobileListItem(href, text) {
  var html = '
'+text+'
';
  return $.parseHTML(html);
}

function setParents(parents, data) {
  //
  //

  data.forEach(function(dataPoint) {
    dataPoint.parents = parents.slice();
    //
    if(dataPoint.children) {
      var withSelfParents = dataPoint.parents.slice();
      withSelfParents.push(dataPoint);
      //
      setParents(withSelfParents, dataPoint.children);
    }
  });
}

function findPath(data, path, callback) {
  data.forEach(function(dataPoint) {
    if(dataPoint.path === path){
      var copiedArray = dataPoint.parents.slice();
      copiedArray.push(dataPoint);
      callback(copiedArray);
    }

    if(dataPoint.children) {
      findPath(dataPoint.children, path, callback);
    }
  });
}

function buildSideMenu(data) {
  data.children.forEach(function (sideMenuItem) {
      var html = Mustache.render(sideMenuItemTemplate, sideMenuItem);
      var renderedSideMenuItem = $.parseHTML(html);
      sideMenuItem.visible && $('#side-menu').append(renderedSideMenuItem);

      if(sideMenuItem.active) {
        $(renderedSideMenuItem).addClass('active');
        sideMenuItem.children && buildSubSideMenu(sideMenuItem);
      }
  })
}

function buildSubSideMenu(data) {
  data.children.forEach(function (subSideMenuItem) {
      var html = Mustache.render('
{{text}}
', subSideMenuItem);
      var renderedSubSideMenuItem = $.parseHTML(html);

      if (subSideMenuItem.active) {
        $(renderedSubSideMenuItem).addClass('active');
      }

      subSideMenuItem.visible && $('#side-menu').append(renderedSubSideMenuItem);
  })
}

<commit_msg>Slash fix 03<commit_after>$(window).on('load', documentReady);

var menuTree = {};
var topMenuItemTemplate;
var sideMenuItemTemplate;
var thePath;

//Side menu template
sideMenuItemTemplate = '';
sideMenuItemTemplate += '  ';
sideMenuItemTemplate += '
';
sideMenuItemTemplate += '    ';
sideMenuItemTemplate += '   <\/div>';
sideMenuItemTemplate += '';
sideMenuItemTemplate += '
';
sideMenuItemTemplate += '
{{text}}<\/p>';
sideMenuItemTemplate += '   <\/div>';
sideMenuItemTemplate += '';
sideMenuItemTemplate += '
';
sideMenuItemTemplate += '     ';
sideMenuItemTemplate += '   <\/div>';
sideMenuItemTemplate += '  <\/a>';

//Top menu template
topMenuItemTemplate = '';
topMenuItemTemplate += '{{text}}<\/a>';

function documentReady() {
  thePath = window.location.href.substring(theBaseUrl.length, window.location.href.length);
  thePath = thePath.substring(1, thePath.length - 1);

  getMenuFile(menuFileLoaded);

  // Top menu color change
  $(window).on("scroll", function () {
    setColorMenu();
  });

  setColorMenu();
  function setColorMenu() {
    if ($(window).scrollTop() > 0 || thePath !== '/') {
      $("#header").addClass("active");
    } else {
      //remove the background property so it comes transparent again (defined in your css)
      $("#header").removeClass("active");
    }
  }

  //Set smoothscrolling
  $('a[href^="#"]').on('click', function (e) {
    e.preventDefault();

    var target = this.hash;
    var $target = $(target);

    $('html, body').stop().animate({
      'scrollTop': $target.offset().top - 100
    }, 350, 'swing', function () {
      // window.location.hash = target;
    });
  });

  // Set mobile menu
  $('#menu-toggle').on('click', function (e) {
    $('.top-menu-items').toggleClass('open');
  });

  $('top-menu-item').on('click', function(e) {
    $('.top-menu-items').removeClass('open');
  });

  $('pre code').each(function(i, block) {
    var codeText = $(this).text();
    hljs.highlightBlock(block);

    $(this).append('<button class="copy-button" data-clipboard-text=\''+codeText+'\'>
  Copy

  Copied to clipboard
</button>');
  });

  var client = new ZeroClipboard( $('.copy-button') );

  $('.copy-button').click(function() {
    var self = this;
    $(self).addClass('clicked');

    window.setTimeout(function () {
      $(self).removeClass('clicked');
    }, 5000);

  });

}

function getMenuFile(callback) {
  $.getJSON(theBaseUrl + '/menu.json', function (data) {
    callback(data);
  });

  //unsticky menu
  $(window).scroll(function () {
    var offsetTop = $('.footer').offset().top;
    var scrollBottom = $(window).scrollTop() + $(window).height();

    if(scrollBottom >= offsetTop) {
      $('.menu').css('position', 'absolute');
      $('.menu').css('bottom', '0');

    } else {
      $('.menu').css('position', 'fixed');
      $('.menu').css('bottom', '');
    }
  });

  // Set target="_blank" on external links
  $(document.links).filter(function() {
    return this.hostname != window.location.hostname;
  }).attr('target', '_blank');

  //Initi slicknav
  var slickNavConfig = {
    label: '',
    allowParentLinks: false,
    closedSymbol: '',
    openedSymbol: '',
    brand: ''
  }

  $(function(){
    $('#mobile-menu').slicknav(slickNavConfig);
  });

  //check if emails are correct
  var inputfields = $('#mc-embedded-subscribe-form input');

  $('#mc-embedded-subscribe-form input').on('change paste keyup', function () {
    var emailValue = $(this).val();

    //
    if(isEmail(emailValue) && (emailValue !== '')) {
      $(this).parent().find('.button').removeClass('not-active');
    } else {
      $(this).parent().find('.button').addClass('not-active');
    }

  });

  function isEmail(email) {
    var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
    return regex.test(email);
  }

}

function menuFileLoaded(data) {
  //parents are set
  setParents([], data.children);

  // Find path
  findPath(data.children, thePath, function(allActive){
    allActive.forEach(function(oneActive) {
      oneActive.active = true;
    });
  });

  data.children.forEach(function (topMenuItem) {
      var html = Mustache.render(topMenuItemTemplate, topMenuItem);
      var renderedTopMenuItem = $.parseHTML(html);
      if(topMenuItem.active) {
        $(renderedTopMenuItem).addClass('active');
        topMenuItem.children && buildSideMenu(topMenuItem);
      }
      topMenuItem.visible && $('#top-menu-items').append(renderedTopMenuItem);
  });
  //build mobile menu
  buildMobileMenu(data);
}

function buildMobileMenu(data) {
  data.children.forEach(function(menuItem) {
    var mobileListItemTop = createMobileListItem(menuItem.path, menuItem.text);

    var secondLevelUl = $.parseHTML('

');
    menuItem.children && menuItem.children.forEach(function(menuItemSecond) {
      if(menuItemSecond.visible) {
        var mobileSecondListItem = createMobileListItem(menuItemSecond.path, menuItemSecond.text);
        $(secondLevelUl).append(mobileSecondListItem);

        var thirdLevelUl = $.parseHTML('

');
        menuItemSecond.children && menuItemSecond.children.forEach(function (menuItemThird) {
          if(menuItemThird.visible) {
            var mobileThirdListItem = createMobileListItem(menuItemThird.path, menuItemThird.text);
            $(thirdLevelUl).append(mobileThirdListItem);
          }
        });
        menuItemSecond.children && $(mobileSecondListItem).append(thirdLevelUl);
        }
    });
    menuItem.children && $(mobileListItemTop).append(secondLevelUl);
    menuItem.visible && $('#mobile-menu').append(mobileListItemTop);
  });
}

function createMobileListItem(href, text) {
  var html = '
'+text+'
';
  return $.parseHTML(html);
}

function setParents(parents, data) {
  //
  //

  data.forEach(function(dataPoint) {
    dataPoint.parents = parents.slice();
    //
    if(dataPoint.children) {
      var withSelfParents = dataPoint.parents.slice();
      withSelfParents.push(dataPoint);
      //
      setParents(withSelfParents, dataPoint.children);
    }
  });
}

function findPath(data, path, callback) {
  data.forEach(function(dataPoint) {
    if(dataPoint.path === path){
      var copiedArray = dataPoint.parents.slice();
      copiedArray.push(dataPoint);
      callback(copiedArray);
    }

    if(dataPoint.children) {
      findPath(dataPoint.children, path, callback);
    }
  });
}

function buildSideMenu(data) {
  data.children.forEach(function (sideMenuItem) {
      var html = Mustache.render(sideMenuItemTemplate, sideMenuItem);
      var renderedSideMenuItem = $.parseHTML(html);
      sideMenuItem.visible && $('#side-menu').append(renderedSideMenuItem);

      if(sideMenuItem.active) {
        $(renderedSideMenuItem).addClass('active');
        sideMenuItem.children && buildSubSideMenu(sideMenuItem);
      }
  })
}

function buildSubSideMenu(data) {
  data.children.forEach(function (subSideMenuItem) {
      var html = Mustache.render('
{{text}}
', subSideMenuItem);
      var renderedSubSideMenuItem = $.parseHTML(html);

      if (subSideMenuItem.active) {
        $(renderedSubSideMenuItem).addClass('active');
      }

      subSideMenuItem.visible && $('#side-menu').append(renderedSubSideMenuItem);
  })
}

<|endoftext|>


<commit_before>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Control for switching between 2 choices (A & B).
 * Choice 'A' is to the left of the switch, choice 'B' is to the right.
 * This decorates OnOffSwitch, the iOS-like on/off switch.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Dimension2 = require( 'DOT/Dimension2' );
  const InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  const Line = require( 'SCENERY/nodes/Line' );
  const LinearGradient = require( 'SCENERY/util/LinearGradient' );
  const merge = require( 'PHET_CORE/merge' );
  const Node = require( 'SCENERY/nodes/Node' );
  const OnOffSwitch = require( 'SUN/OnOffSwitch' );
  const PressListener = require( 'SCENERY/listeners/PressListener' );
  const Property = require( 'AXON/Property' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );

  class ABSwitch extends Node {

    /**
     * @param {Property.<*>} property stores the value of the current choice
     * @param {*} valueA value for choice 'A'
     * @param {Node} labelA label for choice 'A'
     * @param {*} valueB value for choice 'B'
     * @param {Node} labelB label for choice 'B'
     * @param {Object} [options]
     */
    constructor( property, valueA, labelA, valueB, labelB, options ) {

      // default option values
      options = merge( {
        switchSize: new Dimension2( 60, 30 ),
        xSpacing: 8,
        cursor: 'pointer',
        centerOnButton: false,

        // uses opacity as the default method of indicating whether a {Node} label is {boolean} enabled
        setEnabled: ( label, enabled ) => { label.opacity = enabled ? 1.0 : 0.5; },

        // pointer areas for thumb
        thumbTouchAreaXDilation: 8,
        thumbTouchAreaYDilation: 8,
        thumbMouseAreaXDilation: 0,
        thumbMouseAreaYDilation: 0,
        tandem: Tandem.REQUIRED
      }, options );

      const defaultTrackFill = new LinearGradient( 0, 0, 0, options.switchSize.height )
        .addColorStop( 0, 'rgb( 40, 40, 40 )' )
        .addColorStop( 1, 'rgb( 200, 200, 200 )' );
      options.trackFillA = options.trackFillA || defaultTrackFill;
      options.trackFillB = options.trackFillB || defaultTrackFill;
      options.thumbFill = options.thumbFill ||
                          new LinearGradient( 0, 0, 0, options.switchSize.height )
                            .addColorStop( 0, 'white' )
                            .addColorStop( 1, 'rgb( 200, 200, 200 )' );

      super();

      // property for adapting to OnOffSwitch. 'true' is 'B', the object on the 'on' end of the OnOffSwitch.
      const onProperty = new Property( valueB === property.get() );

      const onOffSwitch = new OnOffSwitch( onProperty, {
        size: options.switchSize,
        cursor: options.cursor,
        thumbFill: options.thumbFill,
        trackOnFill: options.trackFillB,
        trackOffFill: options.trackFillA,
        thumbTouchAreaXDilation: options.thumbTouchAreaXDilation,
        thumbTouchAreaYDilation: options.thumbTouchAreaYDilation,
        thumbMouseAreaXDilation: options.thumbMouseAreaXDilation,
        thumbMouseAreaYDilation: options.thumbMouseAreaYDilation,

        // not named 'onOffSwitch' by design, see https://github.com/phetsims/sun/issues/559#issuecomment-585982604
        tandem: options.tandem.createTandem( 'switch' )
      } );

      // rendering order
      this.addChild( onOffSwitch );
      this.addChild( labelA );
      this.addChild( labelB );

      // layout: 'A' on the left, 'B' on the right
      labelA.right = onOffSwitch.left - options.xSpacing;
      labelA.centerY = onOffSwitch.centerY;
      labelB.left = onOffSwitch.right + options.xSpacing;
      labelB.centerY = onOffSwitch.centerY;

      // add a horizontal strut that will cause the 'centerX' of this node to be at the center of the button
      if ( options.centerOnButton ) {
        const additionalWidth = Math.abs( labelA.width - labelB.width );
        const strut = new Line( 0, 0, this.width + additionalWidth, 0 );
        this.addChild( strut );
        strut.moveToBack();
        if ( labelA.width < labelB.width ) {
          strut.left = labelA.left - ( additionalWidth / 2 );
        }
        else {
          strut.left = labelA.left;
        }
      }

      // sync properties, listeners must be disposed
      const propertyListener = object => onProperty.set( valueB === object );
      property.link( propertyListener );

      const onPropertyListener = on => {
        property.set( on ? valueB : valueA );
        if ( options.setEnabled ) {
          options.setEnabled( labelA, !on );
          options.setEnabled( labelB, on );
        }
      };
      onProperty.link( onPropertyListener );

      // click on labels to select, must be disposed
      const aPressListener = new PressListener( {
        release: () => onProperty.set( false ),
        tandem: labelA.tandem.createTandem( 'pressListener' )
      } );

      const bPressListener = new PressListener( {
        release: () => onProperty.set( true ),
        tandem: labelB.tandem.createTandem( 'pressListener' )
      } );
      labelA.addInputListener( aPressListener );
      labelB.addInputListener( bPressListener );

      // @private - for dispose
      this.disposeABSwitch = () => {
        property.unlink( propertyListener );
        onProperty.unlink( onPropertyListener );
        labelA.removeInputListener( aPressListener );
        labelB.removeInputListener( bPressListener );
      };

      this.mutate( options );

      // support for binder documentation, stripped out in builds and only runs when ?binder is specified
      assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'ABSwitch', this );
    }

    /**
     * Make eligible for garbage collection.
     * @public
     * @override
     */
    dispose() {
      this.disposeABSwitch();
      super.dispose();
    }
  }

  return sun.register( 'ABSwitch', ABSwitch );
} );<commit_msg>factor out constants for ABSwitch default option value, #559<commit_after>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Control for switching between 2 choices (A & B).
 * Choice 'A' is to the left of the switch, choice 'B' is to the right.
 * This decorates OnOffSwitch, the iOS-like on/off switch.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Dimension2 = require( 'DOT/Dimension2' );
  const InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  const Line = require( 'SCENERY/nodes/Line' );
  const LinearGradient = require( 'SCENERY/util/LinearGradient' );
  const merge = require( 'PHET_CORE/merge' );
  const Node = require( 'SCENERY/nodes/Node' );
  const OnOffSwitch = require( 'SUN/OnOffSwitch' );
  const PressListener = require( 'SCENERY/listeners/PressListener' );
  const Property = require( 'AXON/Property' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );

  // constants
  const DEFAULT_SIZE = new Dimension2( 60, 30 );

  // Uses opacity as the default method of indicating whether a {Node} label is {boolean} enabled.
  const DEFAULT_SET_ENABLED = ( label, enabled ) => {
    label.opacity = enabled ? 1.0 : 0.5;
  };

  class ABSwitch extends Node {

    /**
     * @param {Property.<*>} property stores the value of the current choice
     * @param {*} valueA value for choice 'A'
     * @param {Node} labelA label for choice 'A'
     * @param {*} valueB value for choice 'B'
     * @param {Node} labelB label for choice 'B'
     * @param {Object} [options]
     */
    constructor( property, valueA, labelA, valueB, labelB, options ) {

      // default option values
      options = merge( {
        switchSize: DEFAULT_SIZE,
        xSpacing: 8,
        cursor: 'pointer',
        centerOnButton: false,

        // {function( Node, boolean )} method of making the switch look disabled
        setEnabled: DEFAULT_SET_ENABLED,

        // pointer areas for thumb
        thumbTouchAreaXDilation: 8,
        thumbTouchAreaYDilation: 8,
        thumbMouseAreaXDilation: 0,
        thumbMouseAreaYDilation: 0,
        tandem: Tandem.REQUIRED
      }, options );

      const defaultTrackFill = new LinearGradient( 0, 0, 0, options.switchSize.height )
        .addColorStop( 0, 'rgb( 40, 40, 40 )' )
        .addColorStop( 1, 'rgb( 200, 200, 200 )' );
      options.trackFillA = options.trackFillA || defaultTrackFill;
      options.trackFillB = options.trackFillB || defaultTrackFill;
      options.thumbFill = options.thumbFill ||
                          new LinearGradient( 0, 0, 0, options.switchSize.height )
                            .addColorStop( 0, 'white' )
                            .addColorStop( 1, 'rgb( 200, 200, 200 )' );

      super();

      // property for adapting to OnOffSwitch. 'true' is 'B', the object on the 'on' end of the OnOffSwitch.
      const onProperty = new Property( valueB === property.get() );

      const onOffSwitch = new OnOffSwitch( onProperty, {
        size: options.switchSize,
        cursor: options.cursor,
        thumbFill: options.thumbFill,
        trackOnFill: options.trackFillB,
        trackOffFill: options.trackFillA,
        thumbTouchAreaXDilation: options.thumbTouchAreaXDilation,
        thumbTouchAreaYDilation: options.thumbTouchAreaYDilation,
        thumbMouseAreaXDilation: options.thumbMouseAreaXDilation,
        thumbMouseAreaYDilation: options.thumbMouseAreaYDilation,

        // not named 'onOffSwitch' by design, see https://github.com/phetsims/sun/issues/559#issuecomment-585982604
        tandem: options.tandem.createTandem( 'switch' )
      } );

      // rendering order
      this.addChild( onOffSwitch );
      this.addChild( labelA );
      this.addChild( labelB );

      // layout: 'A' on the left, 'B' on the right
      labelA.right = onOffSwitch.left - options.xSpacing;
      labelA.centerY = onOffSwitch.centerY;
      labelB.left = onOffSwitch.right + options.xSpacing;
      labelB.centerY = onOffSwitch.centerY;

      // add a horizontal strut that will cause the 'centerX' of this node to be at the center of the button
      if ( options.centerOnButton ) {
        const additionalWidth = Math.abs( labelA.width - labelB.width );
        const strut = new Line( 0, 0, this.width + additionalWidth, 0 );
        this.addChild( strut );
        strut.moveToBack();
        if ( labelA.width < labelB.width ) {
          strut.left = labelA.left - ( additionalWidth / 2 );
        }
        else {
          strut.left = labelA.left;
        }
      }

      // sync properties, listeners must be disposed
      const propertyListener = object => onProperty.set( valueB === object );
      property.link( propertyListener );

      const onPropertyListener = on => {
        property.set( on ? valueB : valueA );
        if ( options.setEnabled ) {
          options.setEnabled( labelA, !on );
          options.setEnabled( labelB, on );
        }
      };
      onProperty.link( onPropertyListener );

      // click on labels to select, must be disposed
      const aPressListener = new PressListener( {
        release: () => onProperty.set( false ),
        tandem: labelA.tandem.createTandem( 'pressListener' )
      } );

      const bPressListener = new PressListener( {
        release: () => onProperty.set( true ),
        tandem: labelB.tandem.createTandem( 'pressListener' )
      } );
      labelA.addInputListener( aPressListener );
      labelB.addInputListener( bPressListener );

      // @private - for dispose
      this.disposeABSwitch = () => {
        property.unlink( propertyListener );
        onProperty.unlink( onPropertyListener );
        onOffSwitch.dispose();
        labelA.removeInputListener( aPressListener );
        labelB.removeInputListener( bPressListener );
      };

      this.mutate( options );

      // support for binder documentation, stripped out in builds and only runs when ?binder is specified
      assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'ABSwitch', this );
    }

    /**
     * Make eligible for garbage collection.
     * @public
     * @override
     */
    dispose() {
      this.disposeABSwitch();
      super.dispose();
    }
  }

  return sun.register( 'ABSwitch', ABSwitch );
} );<|endoftext|>


<commit_before>// Copyright 2013-2019, University of Colorado Boulder

/**
 * Scenery-based combo box. Composed of a button and a popup listbox of items.
 * The listbox is displayed when the button is pressed, and dismissed when an item is selected, the user clicks on
 * the button, or the user clicks outside the list. The list can be displayed either above or below the button.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const ComboBoxButton = require( 'SUN/ComboBoxButton' );
  const ComboBoxIO = require( 'SUN/ComboBoxIO' );
  const ComboBoxListBox = require( 'SUN/ComboBoxListBox' );
  const inherit = require( 'PHET_CORE/inherit' );
  const InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  const KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Property = require( 'AXON/Property' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Vector2 = require( 'DOT/Vector2' );

  // const
  const LIST_POSITION_VALUES = [ 'above', 'below' ]; // where the list pops up relative to the button
  const ALIGN_VALUES = [ 'left', 'right', 'center' ]; // alignment of item on button and in list

  /**
   * @param {ComboBoxItem[]} items
   * @param {Property} property
   * @param {Node} listParent node that will be used as the list's parent, use this to ensure that the list is in front of everything else
   * @param {Object} [options] object with optional properties
   * @constructor
   */
  function ComboBox( items, property, listParent, options ) {

    options = _.extend( {

      align: 'left', // see ALIGN_VALUES
      listPosition: 'below', // see LIST_POSITION_VALUES
      labelNode: null, // {Node|null} optional label, placed to the left of the combo box
      labelXSpacing: 10, // horizontal space between label and combo box
      enabledProperty: new Property( true ),
      disabledOpacity: 0.5, // {number} opacity used to make the control look disabled
      cornerRadius: 4, // {number} applied to list and button
      highlightFill: 'rgb( 245, 245, 245 )', // highlight behind items in the list

      // Margins around the edges of the button and listbox when highlight is invisible.
      // Highlight margins around the items in the list are set to 1/2 of these values.
      xMargin: 12,
      yMargin: 8,

      // button
      buttonFill: 'white',
      buttonStroke: 'black',
      buttonLineWidth: 1,

      // list
      listFill: 'white',
      listStroke: 'black',
      listLineWidth: 1,

      // phet-io
      tandem: Tandem.required,
      phetioType: ComboBoxIO,
      phetioEventType: 'user'

    }, options );

    // validate option values
    assert && assert( options.disabledOpacity > 0 && options.disabledOpacity < 1,
      'invalid disabledOpacity: ' + options.disabledOpacity );
    assert && assert( LIST_POSITION_VALUES.includes( options.listPosition ),
      'invalid listPosition: ' + options.listPosition );
    assert && assert( ALIGN_VALUES.includes( options.align ),
      'invalid align: ' + options.align );

    // Note: ComboBox cannot use ES6 class until its subtypes do
    Node.call( this );

    this.items = items; // @private
    this.enabledProperty = options.enabledProperty; // @public
    this.listPosition = options.listPosition; // @private

    // optional label
    if ( options.labelNode !== null ) {
      this.addChild( options.labelNode );
    }

    // @private the display that clickToDismissListener is added to, because the scene may change, see sun#14
    this.display = null;

    // @private button that shows the current selection
    this.button = new ComboBoxButton( property, items, {
      align: options.align,
      arrowDirection: ( options.listPosition === 'below' ) ? 'down' : 'up',
      cornerRadius: options.cornerRadius,
      xMargin: options.xMargin,
      yMargin: options.yMargin,
      baseColor: options.buttonFill,
      stroke: options.buttonStroke,
      lineWidth: options.buttonLineWidth,

      //TODO sun#314 need to pass a11yLabel?

      // phet-io
      tandem: options.tandem.createTandem( 'button' )
    } );
    this.addChild( this.button );

    // put optional label to left of button
    if ( options.labelNode ) {
      this.button.left = options.labelNode.right + options.labelXSpacing;
      this.button.centerY = options.labelNode.centerY;
    }

    // @private the popup listbox
    this.listBox = new ComboBoxListBox( property, items, this.button,
      this.hideList.bind( this ), options.tandem.createTandem( 'listBox' ), {
        align: options.align,
        highlightFill: options.highlightFill,
        xMargin: options.xMargin,
        yMargin: options.yMargin,
        cornerRadius: options.cornerRadius,
        fill: options.listFill,
        stroke: options.listStroke,
        lineWidth: options.listLineWidth,
        visible: false

        //TODO sun#314 need to any a11y options?
      } );
    listParent.addChild( this.listBox );
    this.listParent = listParent; // @private

    this.mutate( options );

    // Clicking on the button toggles visibility of the list
    this.button.addListener( () => {
      if ( !this.listBox.visible ) {
        this.showList();
      }
      else {
        this.hideList();
      }
    } );

    //TODO sun#445 why is this listener on button? this entire listener appears to be related to the list.
    // add the button accessibility listener
    this.button.addInputListener( {

      a11yclick: () => {

        //TODO sun#314 order dependency, requires that showList was called first by button listener
        if ( this.listBox.visible ) {
          this.listBox.updateFocus();
        }
      },

      // listen for escape to hide the list when focused on the button
      keydown: event => {
        if ( this.listBox.visible ) {
          if ( event.domEvent.keyCode === KeyboardUtil.KEY_ESCAPE ) {
            this.hideList();
          }
        }
      }
    } );

    // @private listener for 'click outside to dismiss'
    // TODO sun#314 handle this logic for a11y too, perhaps on by monitoring the focusout event on the display's root PDOM element
    this.clickToDismissListener = {
      down: event => {

        //TODO scenery#927 is trail.nodes public? should we add Trail.containsNode?
        // Ignore if we click over the button, since the button will handle hiding the list.
        if ( event.trail.nodes.indexOf( this.button ) === -1 ) {
          this.hideList();
        }
      }
    };

    // enable/disable the combo box
    const enabledObserver = enabled => {
      this.pickable = enabled;
      this.opacity = enabled ? 1.0 : options.disabledOpacity;
    };
    this.enabledProperty.link( enabledObserver );

    // @private called by dispose
    this.disposeComboBox = () => {

      if ( this.display && this.display.hasInputListener( this.clickToDismissListener ) ) {
        this.display.removeInputListener( this.clickToDismissListener );
      }

      if ( this.enabledProperty.hasListener( enabledObserver ) ) {
        this.enabledProperty.unlink( enabledObserver );
      }

      // dispose of subcomponents
      this.listBox.dispose();
      this.button.dispose();
    };

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'ComboBox', this );
  }

  sun.register( 'ComboBox', ComboBox );

  // Note: ComboBox cannot use ES6 class until its subtypes do
  return inherit( Node, ComboBox, {

    // @public - Provide dispose() on the prototype for ease of subclassing.
    dispose() {
      this.disposeComboBox();
      Node.prototype.dispose.call( this );
    },

    // @public
    setEnabled( enabled ) { this.enabledProperty.value = enabled; },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled() { return this.enabledProperty.value; },
    get enabled() { return this.getEnabled(); },

    /**
     * Shows the combo box list.
     * @public
     */
    showList() {
      if ( !this.listBox.visible ) {
        this.phetioStartEvent( 'popupShown' );

        this.moveList();
        this.listBox.moveToFront();
        this.listBox.visible = true;

        assert && assert( !this.display, 'unexpected display' );
        this.display = this.getUniqueTrail().rootNode().getRootedDisplays()[ 0 ];
        this.display.addInputListener( this.clickToDismissListener );

        this.phetioEndEvent();
      }
    },

    /**
     * Hides the combo box list.
     * @public
     */
    hideList() {
      if ( this.listBox.visible ) {
        this.phetioStartEvent( 'popupHidden' );

        if ( this.display && this.display.hasInputListener( this.clickToDismissListener ) ) {
          this.display.removeInputListener( this.clickToDismissListener );
          this.display = null;
        }

        this.listBox.visible = false;

        this.phetioEndEvent();
      }
    },

    /**
     * Handles the coordinate transform required to make the list pop up near the button.
     * @private
     */
    moveList() {
      if ( this.listPosition === 'above' ) {
        const pButtonGlobal = this.localToGlobalPoint( new Vector2( this.button.left, this.button.top ) );
        const pButtonLocal = this.listParent.globalToLocalPoint( pButtonGlobal );
        this.listBox.left = pButtonLocal.x;
        this.listBox.bottom = pButtonLocal.y;
      }
      else {
        const pButtonGlobal = this.localToGlobalPoint( new Vector2( this.button.left, this.button.bottom ) );
        const pButtonLocal = this.listParent.globalToLocalPoint( pButtonGlobal );
        this.listBox.left = pButtonLocal.x;
        this.listBox.top = pButtonLocal.y;
      }
    }
  } );
} );<commit_msg>rename methods to showListbox and hideListbox #445<commit_after>// Copyright 2013-2019, University of Colorado Boulder

/**
 * Scenery-based combo box. Composed of a button and a popup listbox of items.
 * The listbox is displayed when the button is pressed, and dismissed when an item is selected, the user clicks on
 * the button, or the user clicks outside the list. The list can be displayed either above or below the button.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const ComboBoxButton = require( 'SUN/ComboBoxButton' );
  const ComboBoxIO = require( 'SUN/ComboBoxIO' );
  const ComboBoxListBox = require( 'SUN/ComboBoxListBox' );
  const inherit = require( 'PHET_CORE/inherit' );
  const InstanceRegistry = require( 'PHET_CORE/documentation/InstanceRegistry' );
  const KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Property = require( 'AXON/Property' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Vector2 = require( 'DOT/Vector2' );

  // const
  const LIST_POSITION_VALUES = [ 'above', 'below' ]; // where the list pops up relative to the button
  const ALIGN_VALUES = [ 'left', 'right', 'center' ]; // alignment of item on button and in list

  /**
   * @param {ComboBoxItem[]} items
   * @param {Property} property
   * @param {Node} listParent node that will be used as the list's parent, use this to ensure that the list is in front of everything else
   * @param {Object} [options] object with optional properties
   * @constructor
   */
  function ComboBox( items, property, listParent, options ) {

    options = _.extend( {

      align: 'left', // see ALIGN_VALUES
      listPosition: 'below', // see LIST_POSITION_VALUES
      labelNode: null, // {Node|null} optional label, placed to the left of the combo box
      labelXSpacing: 10, // horizontal space between label and combo box
      enabledProperty: new Property( true ),
      disabledOpacity: 0.5, // {number} opacity used to make the control look disabled
      cornerRadius: 4, // {number} applied to list and button
      highlightFill: 'rgb( 245, 245, 245 )', // highlight behind items in the list

      // Margins around the edges of the button and listbox when highlight is invisible.
      // Highlight margins around the items in the list are set to 1/2 of these values.
      xMargin: 12,
      yMargin: 8,

      // button
      buttonFill: 'white',
      buttonStroke: 'black',
      buttonLineWidth: 1,

      // list
      listFill: 'white',
      listStroke: 'black',
      listLineWidth: 1,

      // phet-io
      tandem: Tandem.required,
      phetioType: ComboBoxIO,
      phetioEventType: 'user'

    }, options );

    // validate option values
    assert && assert( options.disabledOpacity > 0 && options.disabledOpacity < 1,
      'invalid disabledOpacity: ' + options.disabledOpacity );
    assert && assert( LIST_POSITION_VALUES.includes( options.listPosition ),
      'invalid listPosition: ' + options.listPosition );
    assert && assert( ALIGN_VALUES.includes( options.align ),
      'invalid align: ' + options.align );

    // Note: ComboBox cannot use ES6 class until its subtypes do
    Node.call( this );

    this.items = items; // @private
    this.enabledProperty = options.enabledProperty; // @public
    this.listPosition = options.listPosition; // @private

    // optional label
    if ( options.labelNode !== null ) {
      this.addChild( options.labelNode );
    }

    // @private the display that clickToDismissListener is added to, because the scene may change, see sun#14
    this.display = null;

    // @private button that shows the current selection
    this.button = new ComboBoxButton( property, items, {
      align: options.align,
      arrowDirection: ( options.listPosition === 'below' ) ? 'down' : 'up',
      cornerRadius: options.cornerRadius,
      xMargin: options.xMargin,
      yMargin: options.yMargin,
      baseColor: options.buttonFill,
      stroke: options.buttonStroke,
      lineWidth: options.buttonLineWidth,

      //TODO sun#314 need to pass a11yLabel?

      // phet-io
      tandem: options.tandem.createTandem( 'button' )
    } );
    this.addChild( this.button );

    // put optional label to left of button
    if ( options.labelNode ) {
      this.button.left = options.labelNode.right + options.labelXSpacing;
      this.button.centerY = options.labelNode.centerY;
    }

    // @private the popup listbox
    this.listBox = new ComboBoxListBox( property, items, this.button,
      this.hideListbox.bind( this ), options.tandem.createTandem( 'listBox' ), {
        align: options.align,
        highlightFill: options.highlightFill,
        xMargin: options.xMargin,
        yMargin: options.yMargin,
        cornerRadius: options.cornerRadius,
        fill: options.listFill,
        stroke: options.listStroke,
        lineWidth: options.listLineWidth,
        visible: false

        //TODO sun#314 need to any a11y options?
      } );
    listParent.addChild( this.listBox );
    this.listParent = listParent; // @private

    this.mutate( options );

    // Clicking on the button toggles visibility of the list
    this.button.addListener( () => {
      if ( !this.listBox.visible ) {
        this.showListbox();
      }
      else {
        this.hideListbox();
      }
    } );

    //TODO sun#445 why is this listener on button? this entire listener appears to be related to the list.
    // add the button accessibility listener
    this.button.addInputListener( {

      a11yclick: () => {

        //TODO sun#314 order dependency, requires that showListbox was called first by button listener
        if ( this.listBox.visible ) {
          this.listBox.updateFocus();
        }
      },

      // listen for escape to hide the list when focused on the button
      keydown: event => {
        if ( this.listBox.visible ) {
          if ( event.domEvent.keyCode === KeyboardUtil.KEY_ESCAPE ) {
            this.hideListbox();
          }
        }
      }
    } );

    // @private listener for 'click outside to dismiss'
    // TODO sun#314 handle this logic for a11y too, perhaps on by monitoring the focusout event on the display's root PDOM element
    this.clickToDismissListener = {
      down: event => {

        //TODO scenery#927 is trail.nodes public? should we add Trail.containsNode?
        // Ignore if we click over the button, since the button will handle hiding the list.
        if ( event.trail.nodes.indexOf( this.button ) === -1 ) {
          this.hideListbox();
        }
      }
    };

    // enable/disable the combo box
    const enabledObserver = enabled => {
      this.pickable = enabled;
      this.opacity = enabled ? 1.0 : options.disabledOpacity;
    };
    this.enabledProperty.link( enabledObserver );

    // @private called by dispose
    this.disposeComboBox = () => {

      if ( this.display && this.display.hasInputListener( this.clickToDismissListener ) ) {
        this.display.removeInputListener( this.clickToDismissListener );
      }

      if ( this.enabledProperty.hasListener( enabledObserver ) ) {
        this.enabledProperty.unlink( enabledObserver );
      }

      // dispose of subcomponents
      this.listBox.dispose();
      this.button.dispose();
    };

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'ComboBox', this );
  }

  sun.register( 'ComboBox', ComboBox );

  // Note: ComboBox cannot use ES6 class until its subtypes do
  return inherit( Node, ComboBox, {

    // @public - Provide dispose() on the prototype for ease of subclassing.
    dispose() {
      this.disposeComboBox();
      Node.prototype.dispose.call( this );
    },

    // @public
    setEnabled( enabled ) { this.enabledProperty.value = enabled; },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled() { return this.enabledProperty.value; },
    get enabled() { return this.getEnabled(); },

    /**
     * Shows the listbox.
     * @public
     */
    showListbox() {
      if ( !this.listBox.visible ) {
        this.phetioStartEvent( 'listboxShown' );

        this.moveList();
        this.listBox.moveToFront();
        this.listBox.visible = true;

        assert && assert( !this.display, 'unexpected display' );
        this.display = this.getUniqueTrail().rootNode().getRootedDisplays()[ 0 ];
        this.display.addInputListener( this.clickToDismissListener );

        this.phetioEndEvent();
      }
    },

    /**
     * Hides the listbox.
     * @public
     */
    hideListbox() {
      if ( this.listBox.visible ) {
        this.phetioStartEvent( 'listboxHidden' );

        if ( this.display && this.display.hasInputListener( this.clickToDismissListener ) ) {
          this.display.removeInputListener( this.clickToDismissListener );
          this.display = null;
        }

        this.listBox.visible = false;

        this.phetioEndEvent();
      }
    },

    /**
     * Handles the coordinate transform required to make the listbox pop up near the button.
     * @private
     */
    moveList() {
      if ( this.listPosition === 'above' ) {
        const pButtonGlobal = this.localToGlobalPoint( new Vector2( this.button.left, this.button.top ) );
        const pButtonLocal = this.listParent.globalToLocalPoint( pButtonGlobal );
        this.listBox.left = pButtonLocal.x;
        this.listBox.bottom = pButtonLocal.y;
      }
      else {
        const pButtonGlobal = this.localToGlobalPoint( new Vector2( this.button.left, this.button.bottom ) );
        const pButtonLocal = this.listParent.globalToLocalPoint( pButtonGlobal );
        this.listBox.left = pButtonLocal.x;
        this.listBox.top = pButtonLocal.y;
      }
    }
  } );
} );<|endoftext|>


<commit_before>// Copyright 2017-2019, University of Colorado Boulder

/**
 * Class for an item that is listed in the PhetMenu
 * See PhetMenu.js for more information
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' );
  const EventType = require( 'TANDEM/EventType' );
  const FireListener = require( 'SCENERY/listeners/FireListener' );
  const FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  const inherit = require( 'PHET_CORE/inherit' );
  const merge = require( 'PHET_CORE/merge' );
  const Node = require( 'SCENERY/nodes/Node' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Text = require( 'SCENERY/nodes/Text' );

  // the check mark used for toggle-able menu items
  const CHECK_MARK_NODE = new FontAwesomeNode( 'check', {
    fill: 'rgba(0,0,0,0.7)',
    scale: 0.4
  } );

  // constants
  const FONT_SIZE = 18;
  const HIGHLIGHT_COLOR = '#a6d2f4';
  const MAX_ITEM_WIDTH = 400;
  const CHECK_PADDING = 2;  // padding between the check and text
  const CHECK_OFFSET = CHECK_MARK_NODE.width + CHECK_PADDING; // offset that includes the check mark's width and padding
  const LEFT_X_MARGIN = 2;
  const RIGHT_X_MARGIN = 5;
  const Y_MARGIN = 3;
  const CORNER_RADIUS = 5;

  /**
   * @param {Number} width - the width of the menu item
   * @param {Number} height - the height of the menu item
   * @param {Function} closeCallback - called when closing the dialog that the menu item opened
   * @param {String} text
   * @param {Function} callback
   * @param {Object} [options]
   * @constructor
   */
  function MenuItem( width, height, closeCallback, text, callback, options ) {
    const self = this;

    // Extend the object with defaults.
    options = merge( {
      cursor: 'pointer',
      tandem: Tandem.optional,
      textFill: 'black',

      // phet-io
      phetioDocumentation: 'Item buttons shown in a popup menu',
      phetioEventType: EventType.USER,

      // a11y
      tagName: 'button',

      // @param {Event} - Only called after PDOM interaction and called AFTER closeCallback, use this to move focus to
      // a particular Node in the document. By default focus is moved to the top of the document after a MenuItem
      // action since the PhET Menu closes after activation
      handleFocusCallback: event => {

        // limit search of next focusable to root accessible HTML element
        const rootElement = phet.joist.display.accessibleDOMElement;
        AccessibilityUtil.getNextFocusable( rootElement ).focus();
      },
      containerTagName: 'li',
      containerAriaRole: 'none', // this is required for JAWS to handle focus correctly, see https://github.com/phetsims/john-travoltage/issues/225
      innerContent: text,
      ariaRole: 'menuitem'
    }, options );

    Node.call( this );

    const textNode = new Text( text, {
      font: new PhetFont( FONT_SIZE ),
      fill: options.textFill,
      maxWidth: MAX_ITEM_WIDTH
    } );

    const highlight = new Rectangle( 0, 0, width + LEFT_X_MARGIN + RIGHT_X_MARGIN + CHECK_OFFSET,
      height + Y_MARGIN + Y_MARGIN, CORNER_RADIUS, CORNER_RADIUS );

    this.addChild( highlight );
    this.addChild( textNode );

    textNode.left = highlight.left + LEFT_X_MARGIN + CHECK_OFFSET; // text is left aligned
    textNode.centerY = highlight.centerY;

    this.addInputListener( {
      enter: function() { highlight.fill = HIGHLIGHT_COLOR; },
      exit: function() { highlight.fill = null; }
    } );

    const fire = function( event ) {
      closeCallback( event );
      callback( event );
    };

    this.addInputListener( new FireListener( {
      tandem: options.tandem.createTandem( 'inputListener' ),
      fire: fire
    } ) );

    // @public (sun)
    this.separatorBefore = options.separatorBefore;

    // if there is a check-mark property, add the check mark and hook up visibility changes
    let checkListener;
    if ( options.checkedProperty ) {
      const checkMarkWrapper = new Node( {
        children: [ CHECK_MARK_NODE ],
        right: textNode.left - CHECK_PADDING,
        centerY: textNode.centerY
      } );
      checkListener = function( isChecked ) {
        checkMarkWrapper.visible = isChecked;
      };
      options.checkedProperty.link( checkListener );
      this.addChild( checkMarkWrapper );
    }

    // a11y - activate the item when selected with the keyboard
    const clickListener = {
      click: function( event ) {
        fire( event );
        options.handleFocusCallback( event );
      }
    };
    this.addInputListener( clickListener );

    this.mutate( options );

    // @private - dispose the menu item
    this.disposeMenuItem = function() {
      if ( options.checkedProperty ) {
        options.checkedProperty.unlink( checkListener );
      }

      self.removeInputListener( clickListener );
    };
  }

  sun.register( 'MenuItem', MenuItem );

  return inherit( Node, MenuItem, {

    // @public - dispose the menu item when it will no longer be used.
    dispose: function() {
      this.disposeMenuItem();
      Node.prototype.dispose.call( this );
    }
  } );
} );<commit_msg>separate options in item descriptor, add defaults in MenuItem, https://github.com/phetsims/joist/issues/590<commit_after>// Copyright 2017-2019, University of Colorado Boulder

/**
 * Class for an item that is listed in the PhetMenu
 * See PhetMenu.js for more information
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' );
  const EventType = require( 'TANDEM/EventType' );
  const FireListener = require( 'SCENERY/listeners/FireListener' );
  const FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  const inherit = require( 'PHET_CORE/inherit' );
  const merge = require( 'PHET_CORE/merge' );
  const Node = require( 'SCENERY/nodes/Node' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Text = require( 'SCENERY/nodes/Text' );

  // the check mark used for toggle-able menu items
  const CHECK_MARK_NODE = new FontAwesomeNode( 'check', {
    fill: 'rgba(0,0,0,0.7)',
    scale: 0.4
  } );

  // constants
  const FONT_SIZE = 18;
  const HIGHLIGHT_COLOR = '#a6d2f4';
  const MAX_ITEM_WIDTH = 400;
  const CHECK_PADDING = 2;  // padding between the check and text
  const CHECK_OFFSET = CHECK_MARK_NODE.width + CHECK_PADDING; // offset that includes the check mark's width and padding
  const LEFT_X_MARGIN = 2;
  const RIGHT_X_MARGIN = 5;
  const Y_MARGIN = 3;
  const CORNER_RADIUS = 5;

  /**
   * @param {Number} width - the width of the menu item
   * @param {Number} height - the height of the menu item
   * @param {Function} closeCallback - called when closing the dialog that the menu item opened
   * @param {String} text
   * @param {Function} callback
   * @param {Object} [options]
   * @constructor
   */
  function MenuItem( width, height, closeCallback, text, callback, options ) {
    const self = this;

    // Extend the object with defaults.
    options = merge( {
      cursor: 'pointer',
      textFill: 'black',

      // if there should be a horizontal separator between this MenuItem and the one immediately previous
      separatorBefore: false,

      // {Property.<boolean>} - if provided add a checkmark next to the MenuItem text whenever this Property is true.
      checkedProperty: null,

      // phet-io
      tandem: Tandem.optional,
      phetioDocumentation: 'Item buttons shown in a popup menu',
      phetioEventType: EventType.USER,

      // a11y
      tagName: 'button',

      // @param {Event} - Only called after PDOM interaction and called AFTER closeCallback, use this to move focus to
      // a particular Node in the document. By default focus is moved to the top of the document after a MenuItem
      // action since the PhET Menu closes after activation
      handleFocusCallback: event => {

        // limit search of next focusable to root accessible HTML element
        const rootElement = phet.joist.display.accessibleDOMElement;
        AccessibilityUtil.getNextFocusable( rootElement ).focus();
      },
      containerTagName: 'li',
      containerAriaRole: 'none', // this is required for JAWS to handle focus correctly, see https://github.com/phetsims/john-travoltage/issues/225
      innerContent: text,
      ariaRole: 'menuitem'
    }, options );

    Node.call( this );

    const textNode = new Text( text, {
      font: new PhetFont( FONT_SIZE ),
      fill: options.textFill,
      maxWidth: MAX_ITEM_WIDTH
    } );

    const highlight = new Rectangle( 0, 0, width + LEFT_X_MARGIN + RIGHT_X_MARGIN + CHECK_OFFSET,
      height + Y_MARGIN + Y_MARGIN, CORNER_RADIUS, CORNER_RADIUS );

    this.addChild( highlight );
    this.addChild( textNode );

    textNode.left = highlight.left + LEFT_X_MARGIN + CHECK_OFFSET; // text is left aligned
    textNode.centerY = highlight.centerY;

    this.addInputListener( {
      enter: function() { highlight.fill = HIGHLIGHT_COLOR; },
      exit: function() { highlight.fill = null; }
    } );

    const fire = function( event ) {
      closeCallback( event );
      callback( event );
    };

    this.addInputListener( new FireListener( {
      tandem: options.tandem.createTandem( 'inputListener' ),
      fire: fire
    } ) );

    // @public (sun)
    this.separatorBefore = options.separatorBefore;

    // if there is a check-mark property, add the check mark and hook up visibility changes
    let checkListener;
    if ( options.checkedProperty ) {
      const checkMarkWrapper = new Node( {
        children: [ CHECK_MARK_NODE ],
        right: textNode.left - CHECK_PADDING,
        centerY: textNode.centerY
      } );
      checkListener = function( isChecked ) {
        checkMarkWrapper.visible = isChecked;
      };
      options.checkedProperty.link( checkListener );
      this.addChild( checkMarkWrapper );
    }

    // a11y - activate the item when selected with the keyboard
    const clickListener = {
      click: function( event ) {
        fire( event );
        options.handleFocusCallback( event );
      }
    };
    this.addInputListener( clickListener );

    this.mutate( options );

    // @private - dispose the menu item
    this.disposeMenuItem = function() {
      if ( options.checkedProperty ) {
        options.checkedProperty.unlink( checkListener );
      }

      self.removeInputListener( clickListener );
    };
  }

  sun.register( 'MenuItem', MenuItem );

  return inherit( Node, MenuItem, {

    // @public - dispose the menu item when it will no longer be used.
    dispose: function() {
      this.disposeMenuItem();
      Node.prototype.dispose.call( this );
    }
  } );
} );<|endoftext|>


<commit_before>/**
 * @class Home to RSS-related facilities.
 */
Util.RSS = {

}

/**
 * @class A replacement for Util.RSS_Reader that doesn't suck quite so much.
 *
 * @constructor Creates a new RSS 2.0 feed reader for the given URL.
 * @param	url	The URL of the RSS feed. You may pass in a function returning the URI instead
 *				of the URL itself. To permit the chunking of results, this function must accept
 *				two parameters: the offset to start on will be passed in as the first parameter
 *				and the number of items to retrieve will be passed in as the second.
 *
 * @author Eric Naeseth
 */
Util.RSS.Reader = function RSSReader(url)
{
	this.url = url;

	var offset = 0;
	var listeners = {
		load: [],
		error: [],
		timeout: []
	};
	var aborted = false;

	this.feed = null;

	function handle_result(document)
	{
		if (aborted || !document)
			return;

		var rss = document.documentElement;
		var channel = (function() {
			try {
				return rss.getElementsByTagName('channel')[0];
			} catch (e) {
				handle_error('RSS feed lacks a channel element!', 0);
			}
		})();
		var items = rss.getElementsByTagName('item');

		function get_text(node)
		{
			var text = null;

			for (var i = 0; i < node.childNodes.length; i++) {
				var child = node.childNodes[i];
				if (child.nodeType == Util.Node.TEXT_NODE) {
					if (text)
						text = text + child.nodeValue;
					else
						text = child.nodeValue;
				}
			}

			return text || '';
		}

		function get_text_child(container, name)
		{
			var nodes = container.getElementsByTagName(name);
			return (nodes.length == 0)
				? null
				: get_text(nodes[0]);
		}

		function to_number(text)
		{
			return (!text || text.length == 0)
				? null
				: new Number(text);
		}

		function to_date(text)
		{
			return (text && text.length > 0)
				? new Date(text)
				: null;
		}

		if (!this.feed) {
			this.feed = new Util.RSS.Feed();
			this.feed.version = rss.getAttribute('version');

			this.feed.channel = new Util.RSS.Channel();
			var channel_object = this.feed.channel;
			channel_object.title = get_text_child(channel, 'title');
			channel_object.link = get_text_child(channel, 'link');
			channel_object.description = get_text_child(channel, 'description');
			channel_object.language = get_text_child(channel, 'language');
			channel_object.copyright = get_text_child(channel, 'copyright');
			channel_object.managing_editor = get_text_child(channel, 'managingEditor');
			channel_object.webmaster = get_text_child(channel, 'webMaster');
			channel_object.publication_date = to_date(get_text_child(channel, 'pubDate'));
			channel_object.last_build_date = to_date(get_text_child(channel, 'lastBuildDate'));
			channel_object.category = get_text_child(channel, 'category');
			channel_object.generator = get_text_child(channel, 'generator');
			channel_object.docs = get_text_child(channel, 'docs');
			channel_object.time_to_live = to_number(get_text_child(channel, 'ttl'));
			channel_object.rating = get_text_child(channel, 'rating');
		}

		var new_items = [];
		var item_elements = channel.getElementsByTagName('item');

		function get_source(node)
		{
			try {
				return {
					name: get_text(node),
					url: node.getAttribute('url')
				};
			} catch (e) {
				return null;
			}
		}

		function get_enclosure(node)
		{
			try {
				return {
					url: node.getAttribute('url'),
					length: to_number(node.getAttribute('length')),
					type: node.getAttribute('type')
				};
			} catch (e) {
				return null;
			}
		}

		for (var i = 0; i < item_elements.length; i++) {
			var item = item_elements[i];
			var item_object = new Util.RSS.Item();

			for (var j = 0; j < item.childNodes.length; j++) {
				var node = item.childNodes[j];

				if (node.nodeType != Util.Node.ELEMENT_NODE)
					continue;

				var nn = node.nodeName;
				if (nn == 'pubDate') {
					item_object.publication_date = to_date(get_text(node));
				} else if (nn == 'source') {
					item_object.source = get_source(node);
				} else if (nn == 'enclosure') {
					item_object.enclosure = get_enclosure(node);
				} else {
					item_object[nn] = get_text(node);
				}
			}

			new_items.push(item_object);
			this.feed.items.push(item_object);
		}

		offset += i;

		listeners.load.each(function(l) {
			l(this.feed, new_items);
		}.bind(this));
	}

	function handle_error(message, code)
	{
		if (aborted)
			return;

		listeners.error.each(function(l) {
			l(message, code);
		});
	}

	function handle_timeout()
	{
		listeners.timeout.each(function (l) {
			l('Operation timed out.', 0);
		});
	}

	/**
	 * Adds an event listener.
	 */
	this.add_event_listener = function(type, func)
	{
		if (!listeners[type]) {
			throw new Error('Unknown listener type "' + type + '".');
		}

		listeners[type].push(func);
		return true;
	}

	/**
	 * Loads items from the feed. If the "num" parameter is provided and the URL has been set up
	 * to support chunking (see description of the construtor), only requests that many items.
	 */
	this.load = function(num, timeout)
	{
		if (!num)
			var num = null;
		if (!timeout)
			var timeout = null;

		aborted = false;

		var url = (typeof(this.url) == 'function')
			? (num ? this.url(offset, num) : this.url())
			: this.url;

		this.request = new Util.Request(url, {
			method: 'GET',
			timeout: timeout,

			on_success: function(req, t) {
				if (aborted)
					return;
				if (!(t.responseXML && t.responseXML.documentElement.nodeName == 'rss')) {
					handle_error('Server did not respond with an RSS document.', 0);
				}
				handle_result.call(this, t.responseXML);
			}.bind(this),

			on_failure: function(req, transport) {
				handle_error(req.get_status_text(), req.get_status());
			},

			on_abort: function(req, transport) {
				aborted = true;
			},

			on_timeout: function(req, transport) {
				if (listeners.timeout.length > 0) {
					aborted = true;
					handle_timeout();
				} else {
					handle_error(req.get_status_text(), req.get_status());
					aborted = true;
				}
			}
		});
	}
}

/**
 * @constructor Creates a new feed object.
 *
 * @class An RSS feed.
 * @author Eric Naeseth
 */
Util.RSS.Feed = function RSSFeed()
{
	this.version = null;
	this.channel = null;
	this.items = [];
}

/**
 * @constructor Creates a new channel object.
 *
 * @class An RSS channel.
 * @author Eric Naeseth
 */
Util.RSS.Channel = function RSSChannel()
{
	// required elements
	this.title = null;
	this.link = null;
	this.description = null;

	// optional elements
	this.language = null;
	this.copyright = null;
	this.managing_editor = null;
	this.webmaster = null;
	this.publication_date = null;
	this.last_build_date = null;
	this.category = null;
	this.generator = null;
	this.docs = null;
	this.cloud = null;
	this.time_to_live = null;
	this.image = null;
	this.rating = null;
	this.text_input = null;
	this.skip_hours = null;
	this.skip_days = null;
}

/**
 * @constructor Creates a new feed object.
 *
 * @class An RSS feed.
 * @author Eric Naeseth
 */
Util.RSS.Item = function RSSItem()
{
	this.title = null;
	this.link = null;
	this.description = null;
	this.author = null;
	this.category = null;
	this.comments = null;
	this.enclosure = null;
	this.guid = null;
	this.publication_date = null;
	this.source = null;
}<commit_msg>Added more debugging information to Util.RSS.<commit_after>/**
 * @class Home to RSS-related facilities.
 */
Util.RSS = {

}

/**
 * @class A replacement for Util.RSS_Reader that doesn't suck quite so much.
 *
 * @constructor Creates a new RSS 2.0 feed reader for the given URL.
 * @param	url	The URL of the RSS feed. You may pass in a function returning the URI instead
 *				of the URL itself. To permit the chunking of results, this function must accept
 *				two parameters: the offset to start on will be passed in as the first parameter
 *				and the number of items to retrieve will be passed in as the second.
 *
 * @author Eric Naeseth
 */
Util.RSS.Reader = function RSSReader(url)
{
	this.url = url;

	var offset = 0;
	var listeners = {
		load: [],
		error: [],
		timeout: []
	};
	var aborted = false;

	this.feed = null;

	function handle_result(document)
	{
		if (aborted || !document)
			return;

		var rss = document.documentElement;
		var channel = (function() {
			try {
				return rss.getElementsByTagName('channel')[0];
			} catch (e) {
				handle_error('RSS feed lacks a channel element!', 0);
			}
		})();
		var items = rss.getElementsByTagName('item');

		function get_text(node)
		{
			var text = null;

			for (var i = 0; i < node.childNodes.length; i++) {
				var child = node.childNodes[i];
				if (child.nodeType == Util.Node.TEXT_NODE) {
					if (text)
						text = text + child.nodeValue;
					else
						text = child.nodeValue;
				}
			}

			return text || '';
		}

		function get_text_child(container, name)
		{
			var nodes = container.getElementsByTagName(name);
			return (nodes.length == 0)
				? null
				: get_text(nodes[0]);
		}

		function to_number(text)
		{
			return (!text || text.length == 0)
				? null
				: new Number(text);
		}

		function to_date(text)
		{
			return (text && text.length > 0)
				? new Date(text)
				: null;
		}

		if (!this.feed) {
			this.feed = new Util.RSS.Feed();
			this.feed.version = rss.getAttribute('version');

			this.feed.channel = new Util.RSS.Channel();
			var channel_object = this.feed.channel;
			channel_object.title = get_text_child(channel, 'title');
			channel_object.link = get_text_child(channel, 'link');
			channel_object.description = get_text_child(channel, 'description');
			channel_object.language = get_text_child(channel, 'language');
			channel_object.copyright = get_text_child(channel, 'copyright');
			channel_object.managing_editor = get_text_child(channel, 'managingEditor');
			channel_object.webmaster = get_text_child(channel, 'webMaster');
			channel_object.publication_date = to_date(get_text_child(channel, 'pubDate'));
			channel_object.last_build_date = to_date(get_text_child(channel, 'lastBuildDate'));
			channel_object.category = get_text_child(channel, 'category');
			channel_object.generator = get_text_child(channel, 'generator');
			channel_object.docs = get_text_child(channel, 'docs');
			channel_object.time_to_live = to_number(get_text_child(channel, 'ttl'));
			channel_object.rating = get_text_child(channel, 'rating');
		}

		var new_items = [];
		var item_elements = channel.getElementsByTagName('item');

		function get_source(node)
		{
			try {
				return {
					name: get_text(node),
					url: node.getAttribute('url')
				};
			} catch (e) {
				return null;
			}
		}

		function get_enclosure(node)
		{
			try {
				return {
					url: node.getAttribute('url'),
					length: to_number(node.getAttribute('length')),
					type: node.getAttribute('type')
				};
			} catch (e) {
				return null;
			}
		}

		for (var i = 0; i < item_elements.length; i++) {
			var item = item_elements[i];
			var item_object = new Util.RSS.Item();

			for (var j = 0; j < item.childNodes.length; j++) {
				var node = item.childNodes[j];

				if (node.nodeType != Util.Node.ELEMENT_NODE)
					continue;

				var nn = node.nodeName;
				if (nn == 'pubDate') {
					item_object.publication_date = to_date(get_text(node));
				} else if (nn == 'source') {
					item_object.source = get_source(node);
				} else if (nn == 'enclosure') {
					item_object.enclosure = get_enclosure(node);
				} else {
					item_object[nn] = get_text(node);
				}
			}

			new_items.push(item_object);
			this.feed.items.push(item_object);
		}

		offset += i;

		listeners.load.each(function(l) {
			l(this.feed, new_items);
		}.bind(this));
	}

	function handle_error(message, code)
	{
		if (aborted)
			return;

		listeners.error.each(function(l) {
			l(message, code);
		});
	}

	function handle_timeout()
	{
		listeners.timeout.each(function (l) {
			l('Operation timed out.', 0);
		});
	}

	/**
	 * Adds an event listener.
	 */
	this.add_event_listener = function add_rss_event_listener(type, func)
	{
		if (!listeners[type]) {
			throw new Error('Unknown listener type "' + type + '".');
		}

		listeners[type].push(func);
		return true;
	}

	/**
	 * Loads items from the feed. If the "num" parameter is provided and the URL has been set up
	 * to support chunking (see description of the construtor), only requests that many items.
	 */
	this.load = function load_rss_feed(num, timeout)
	{
		if (!num)
			var num = null;
		if (!timeout)
			var timeout = null;

		aborted = false;

		var url = (typeof(this.url) == 'function')
			? (num ? this.url(offset, num) : this.url())
			: this.url;

		this.request = new Util.Request(url, {
			method: 'GET',
			timeout: timeout,

			on_success: function(req, t) {
				if (aborted)
					return;
				if (!(t.responseXML && t.responseXML.documentElement.nodeName == 'rss')) {
					handle_error('Server did not respond with an RSS document.', 0);
				}
				handle_result.call(this, t.responseXML);
			}.bind(this),

			on_failure: function(req, transport) {
				handle_error(req.get_status_text(), req.get_status());
			},

			on_abort: function(req, transport) {
				aborted = true;
			},

			on_timeout: function(req, transport) {
				if (listeners.timeout.length > 0) {
					aborted = true;
					handle_timeout();
				} else {
					handle_error(req.get_status_text(), req.get_status());
					aborted = true;
				}
			}
		});
	}
}

/**
 * @constructor Creates a new feed object.
 *
 * @class An RSS feed.
 * @author Eric Naeseth
 */
Util.RSS.Feed = function RSSFeed()
{
	this.version = null;
	this.channel = null;
	this.items = [];
}

/**
 * @constructor Creates a new channel object.
 *
 * @class An RSS channel.
 * @author Eric Naeseth
 */
Util.RSS.Channel = function RSSChannel()
{
	// required elements
	this.title = null;
	this.link = null;
	this.description = null;

	// optional elements
	this.language = null;
	this.copyright = null;
	this.managing_editor = null;
	this.webmaster = null;
	this.publication_date = null;
	this.last_build_date = null;
	this.category = null;
	this.generator = null;
	this.docs = null;
	this.cloud = null;
	this.time_to_live = null;
	this.image = null;
	this.rating = null;
	this.text_input = null;
	this.skip_hours = null;
	this.skip_days = null;
}

/**
 * @constructor Creates a new feed object.
 *
 * @class An RSS feed.
 * @author Eric Naeseth
 */
Util.RSS.Item = function RSSItem()
{
	this.title = null;
	this.link = null;
	this.description = null;
	this.author = null;
	this.category = null;
	this.comments = null;
	this.enclosure = null;
	this.guid = null;
	this.publication_date = null;
	this.source = null;
}<|endoftext|>


<commit_before>import { Observable } from 'rxjs/Observable';
import ActionTypes from '../const/ActionTypes.js';
import fetchApi from '../util/fetchApi.js';

function fetchNumbersStart() {
  return {
    type: 'FETCH_NUMBERS_START'
  };
}

function fetchNumbersSuccess(res) {
  return {
    type: 'FETCH_NUMBERS_SUCCESS',
    payload: res
  };
}

function fetchNumbersFail(error) {
  return {
    type: 'FETCH_NUMBERS_FAIL',
    error
  };
}

function fetchNumbers(action$) {
  return action$.ofType(ActionTypes.FETCH_NUMBERS)
    .mergeMap(() =>
      Observable.concat(
        Observable.of(fetchNumbersStart()),
        Observable.fromPromise(fetchApi({ url: 'http://127.0.0.1:4000/numbers' }))
          .map(fetchNumbersSuccess)
      )
      .catch(err => Observable.of(fetchNumbersFail(err)))
    );
}

export default fetchNumbers;
<commit_msg>fetch<commit_after>import { Observable } from 'rxjs/Observable';
import ActionTypes from '../const/ActionTypes.js';
import fetchApi from '../util/fetchApi.js';

function fetchNumbersStart() {
  return {
    type: 'FETCH_NUMBERS_START'
  };
}

function fetchNumbersSuccess(res) {
  return {
    type: 'FETCH_NUMBERS_SUCCESS',
    payload: res
  };
}

function fetchNumbersFail(error) {
  return {
    type: 'FETCH_NUMBERS_FAIL',
    error
  };
}

function fetchNumbers(action$) {
  return action$.ofType(ActionTypes.FETCH_NUMBERS)
    .mergeMap(() =>
      Observable.concat(
        Observable.of(fetchNumbersStart()),
        Observable.fromPromise(fetchApi({ url: 'http://127.0.0.1:4000/numbers' }))
          .mergeMap(res => {
            return Observable.concat(
              Observable.of({type: '1'}),
              Observable.of({type: '2'}),
              Observable.of({type: '3'}),
            );
          })
      )
      .catch(err => Observable.of(fetchNumbersFail(err)))
    );
}

export default fetchNumbers;
<|endoftext|>


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

    var App = Backbone.Layout.extend({

        template: '#app',

        /**
         * @type Dingbat.View.Navigation
         */
        Navigation: null,

        /**
         * @type Dingbat.View.Form
         */
        Form: null,

        /**
         * @type Dingbat.View.Footer
         */
        Footer: null,

        /**
         * @type Dingbat.View.List
         */
        List: null,

        /**
         * @type Dingbat.View.Sidebar
         */
        Sidebar: null,

        /**
         * @type Dingbat.Collection.Cards
         */
        Cards: null,

        /**
         * @type Dingbat.Collection.Tasks
         */
        Tasks: null,

        /**
         * @type Dingbat.Collection.List
         */
        CardTasks: null,

        /**
         * @type {Dingbat.Router}
         */
        Router: null,

        loadCards: function() {
            this.Cards.fetch();
        },

        run: function() {
            // create collections
            this.Cards     = new Dingbat.Collection.Cards();
            this.Tasks     = new Dingbat.Collection.Tasks();
            this.CardTasks = new Dingbat.Collection.List();

            // create views
            this.Form       = new Dingbat.View.Form();
            this.List       = new Dingbat.View.List();
            this.Footer     = new Dingbat.View.Footer();
            this.Navigation = new Dingbat.View.Navigation();
            this.Sidebar    = new Dingbat.View.Sidebar();

            // set listener
            this.Navigation.setListener();
            this.CardTasks.setListener();
            this.List.setListener();

            // set views to layout/application
            this.setView('.form', this.Form);
            this.setView('.list', this.List);
            this.setView('footer', this.Footer);
            this.setView('.navigation', this.Navigation);

            // hide views
            this.Navigation.$el.hide();
            this.Form.$el.hide();
            this.List.$el.hide();
            this.Footer.$el.hide();

            this.listenToOnce(this.Tasks, 'sync', this.loadCards);
            this.listenToOnce(this.Cards, 'sync', this.showApp);
            this.listenToOnce(this.Cards, 'sync', this.startRouter);

            // load tasks
            this.Tasks.fetch();
        },

        showApp: function() {
            this.$('#app-loader').hide();
            this.Navigation.$el.fadeIn();
            this.Form.$el.slideDown();
            this.List.$el.fadeIn();
            this.Footer.$el.fadeIn();
            this.Sidebar.render().$el.appendTo('body').hide().fadeIn();
        },

        startRouter: function() {
            this.Router = new Dingbat.Router();
            Backbone.history.start();
        }

    });

    Dingbat.App = new App();
    Dingbat.App.run();

})

<commit_msg>select tasks input after starting app<commit_after>$(function() {
    'use strict';

    var App = Backbone.Layout.extend({

        template: '#app',

        /**
         * @type Dingbat.View.Navigation
         */
        Navigation: null,

        /**
         * @type Dingbat.View.Form
         */
        Form: null,

        /**
         * @type Dingbat.View.Footer
         */
        Footer: null,

        /**
         * @type Dingbat.View.List
         */
        List: null,

        /**
         * @type Dingbat.View.Sidebar
         */
        Sidebar: null,

        /**
         * @type Dingbat.Collection.Cards
         */
        Cards: null,

        /**
         * @type Dingbat.Collection.Tasks
         */
        Tasks: null,

        /**
         * @type Dingbat.Collection.List
         */
        CardTasks: null,

        /**
         * @type {Dingbat.Router}
         */
        Router: null,

        loadCards: function() {
            this.Cards.fetch();
        },

        run: function() {
            // create collections
            this.Cards     = new Dingbat.Collection.Cards();
            this.Tasks     = new Dingbat.Collection.Tasks();
            this.CardTasks = new Dingbat.Collection.List();

            // create views
            this.Form       = new Dingbat.View.Form();
            this.List       = new Dingbat.View.List();
            this.Footer     = new Dingbat.View.Footer();
            this.Navigation = new Dingbat.View.Navigation();
            this.Sidebar    = new Dingbat.View.Sidebar();

            // set listener
            this.Navigation.setListener();
            this.CardTasks.setListener();
            this.List.setListener();

            // set views to layout/application
            this.setView('.form', this.Form);
            this.setView('.list', this.List);
            this.setView('footer', this.Footer);
            this.setView('.navigation', this.Navigation);

            // hide views
            this.Navigation.$el.hide();
            this.Form.$el.hide();
            this.List.$el.hide();
            this.Footer.$el.hide();

            this.listenToOnce(this.Tasks, 'sync', this.loadCards);
            this.listenToOnce(this.Cards, 'sync', this.showApp);
            this.listenToOnce(this.Cards, 'sync', this.startRouter);

            // load tasks
            this.Tasks.fetch();
        },

        showApp: function() {
            this.$('#app-loader').hide();
            this.Navigation.$el.fadeIn();
            this.Form.$el.fadeIn();
            this.List.$el.fadeIn();
            this.Footer.$el.fadeIn();
            this.Sidebar.render().$el.appendTo('body').hide().fadeIn();

            // focus form for adding tasks
            this.Form.$(':text').focus();
        },

        startRouter: function() {
            this.Router = new Dingbat.Router();
            Backbone.history.start();
        }

    });

    Dingbat.App = new App();
    Dingbat.App.run();

})

<|endoftext|>


<commit_before>function anim(){
	$(".animsition").animsition({

    		inClass               :   'fade-in-left-sm',
    		outClass              :   'fade-out-left-sm',
    		inDuration            :    1500,
    		outDuration           :    800,
    		linkElement           :   '.animsition-link',
    		loading               :    true,
    		loadingParentElement  :   'body',
    		loadingClass          :   'animsition-loading',
    		unSupportCss          : [ 'animation-duration',
                              '-webkit-animation-duration',
                              '-o-animation-duration'
                            ],
    		overlay               :   false,
    		overlayClass          :   'animsition-overlay-slide',
    		overlayParentElement  :   'body'
	});
}
function fade() {
  setTimeout(function() {
    $('.fade').fadeTo("slow", 1);
  }, 1500);
}
function hov(){
  $(".projects > a").addClass("fx");
  $(".about > a").addClass("fx");
  $(".contact > a").addClass("fx");
  $(".blog > a").addClass("fx");
  $(".home > a").addClass("fx");
  $(".resume > a").addClass("fx");
  $(".projects > a").hover(function(){
    $(".j").css("color", "#000");
    }, function(){
    $(".j").css("color", "#A39082");
  });
  $(".about > a").hover(function(){
    $(".t").css("color", "#000");
    }, function(){
    $(".t").css("color", "#A39082");
  });
  $(".contact > a").hover(function(){
    $(".n").css("color", "#000");
    }, function(){
    $(".n").css("color", "#A39082");
  });
  $(".blog > a").hover(function(){
    $(".l").css("color", "#000");
    }, function(){
    $(".l").css("color", "#946E4F");
  });
  $(".home > a").hover(function(){
    $(this).addClass("fx");
    $(".e").css("color", "#000");
    }, function(){
    $(".e").css("color", "#946E4F");
  });
  $(".resume > a").hover(function(){
    $(".e2").css("color", "#000");
    }, function(){
    $(".e2").css("color", "#946E4F");
  });
}
function chain(){
  $.when(fade()).then(hov());
}
$(document).ready(function () {
	$.when(anim()).then(chain());
});
<commit_msg>work plz?<commit_after>function anim(){
	$(".animsition").animsition({

    		inClass               :   'fade-in-left-sm',
    		outClass              :   'fade-out-left-sm',
    		inDuration            :    1500,
    		outDuration           :    800,
    		linkElement           :   '.animsition-link',
    		loading               :    true,
    		loadingParentElement  :   'body',
    		loadingClass          :   'animsition-loading',
    		unSupportCss          : [ 'animation-duration',
                              '-webkit-animation-duration',
                              '-o-animation-duration'
                            ],
    		overlay               :   false,
    		overlayClass          :   'animsition-overlay-slide',
    		overlayParentElement  :   'body'
	});
}
function fade() {
  setTimeout(function() {
    $('.fade').fadeTo(600, 1);
  }, 1500);
}
function hov(){
  setTimeout(function() {
    $(".projects > a").addClass("fx");
    $(".about > a").addClass("fx");
    $(".contact > a").addClass("fx");
    $(".blog > a").addClass("fx");
    $(".home > a").addClass("fx");
    $(".resume > a").addClass("fx");
    $(".projects > a").hover(function(){
      $(".j").css("color", "#000");
      }, function(){
      $(".j").css("color", "#A39082");
    });
    $(".about > a").hover(function(){
      $(".t").css("color", "#000");
      }, function(){
      $(".t").css("color", "#A39082");
    });
    $(".contact > a").hover(function(){
      $(".n").css("color", "#000");
      }, function(){
      $(".n").css("color", "#A39082");
    });
    $(".blog > a").hover(function(){
      $(".l").css("color", "#000");
      }, function(){
      $(".l").css("color", "#946E4F");
    });
    $(".home > a").hover(function(){
      $(".e").css("color", "#000");
      }, function(){
      $(".e").css("color", "#946E4F");
    });
    $(".resume > a").hover(function(){
      $(".e2").css("color", "#000");
      }, function(){
      $(".e2").css("color", "#946E4F");
    });
  }, 600);
}
function chain(){
  $.when(fade()).then(hov());
}
$(document).ready(function () {
	$.when(anim()).then(chain());
});
<|endoftext|>