<commit_before>/**
 * Reveal module.
 * @module foundation.reveal
 * @requires foundation.util.keyboard
 * @requires foundation.util.size-and-collision
 * @requires foundation.util.triggers
 * @requires foundation.util.mediaQuery
 */
!function(Foundation, $) {
  'use strict';

  /**
   * Creates a new instance of Reveal.
   * @class
   * @fires Reveal#init
   * @param {jQuery} element - jQuery object to use for the modal.
   * @param {Object} options - optional parameters.
   */

  function Reveal(element, options) {
    this.$element = element;
    this.options = $.extend({}, Reveal.defaults, this.$element.data(), options);
    this._init();

    Foundation.registerPlugin(this);
    Foundation.Keyboard.register('Reveal', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ESCAPE': 'close',
      'TAB': 'tab_forward',
      'SHIFT_TAB': 'tab_backward'
    });
    // /**
    //  * Fires when the plugin has been successfuly initialized.
    //  * @event Reveal#init
    //  */
    // this.$element.trigger('init.zf.reveal');
  }

  Reveal.defaults = {
    animationIn: '',
    animationOut: '',
    showDelay: 0,
    hideDelay: 0,
    closeOnClick: true,
    closeOnEsc: true,
    multipleOpened: false,
    vOffset: 100,
    hOffset: 0,
    fullScreen: false,
    btmOffsetPct: 10,
    overlay: true,
    resetOnClose: false
  };

  /**
   * Initializes the modal by adding the overlay and close buttons, (if selected).
   * @private
   */
  Reveal.prototype._init = function(){
    this.id = this.$element.attr('id');
    this.isActive = false;

    this.$anchor = $('[data-open="' + this.id + '"]').length ? $('[data-open="' + this.id + '"]') : $('[data-toggle="' + this.id + '"]');

    if(this.$anchor.length){
      var anchorId = this.$anchor[0].id || Foundation.GetYoDigits(6, 'reveal');

      this.$anchor.attr({
        'aria-controls': this.id,
        'id': anchorId,
        'aria-haspopup': true,
        'tabindex': 0
      });
      this.$element.attr({'aria-labelledby': anchorId});
    }

    // this.options.fullScreen = this.$element.hasClass('full');
    if(this.options.fullScreen || this.$element.hasClass('full')){
      this.options.fullScreen = true;
      this.options.overlay = false;
    }
    if(this.options.overlay){
      this.$overlay = this._makeOverlay(this.id);
    }

    this.$element.attr({
        'role': 'dialog',
        'aria-hidden': true,
        'data-yeti-box': this.id,
        'data-resize': this.id
    });

    this._events();
  };

  /**
   * Creates an overlay div to display behind the modal.
   * @private
   */
  Reveal.prototype._makeOverlay = function(id){
    var $overlay = $('

')
                    .addClass('reveal-overlay')
                    .attr({'tabindex': -1, 'aria-hidden': true})
                    .appendTo('body');
    if(this.options.closeOnClick){
      $overlay.attr({
        'data-close': id
      });
    }
    return $overlay;
  };

  /**
   * Adds event handlers for the modal.
   * @private
   */
  Reveal.prototype._events = function(){
    var _this = this;

    this.$element.on({
      'open.zf.trigger': this._open.bind(this),
      'close.zf.trigger': this._close.bind(this),
      'toggle.zf.trigger': this.toggle.bind(this),
      'resizeme.zf.trigger': function(){
        if(_this.$element.is(':visible')){
          _this._setPosition(function(){});
        }
      }
    });

    if(this.$anchor.length){
      this.$anchor.on('keydown.zf.reveal', function(e){
        if(e.which === 13 || e.which === 32){
          e.stopPropagation();
          e.preventDefault();
          _this._open();
        }
      });
    }

    if(this.options.closeOnClick && this.options.overlay){
      this.$overlay.off('.zf.reveal').on('click.zf.reveal', this._close.bind(this));
    }
  };
  /**
   * Sets the position of the modal before opening
   * @param {Function} cb - a callback function to execute when positioning is complete.
   * @private
   */
  Reveal.prototype._setPosition = function(cb){
    var eleDims = Foundation.Box.GetDimensions(this.$element);
    var elePos = this.options.fullScreen ? 'reveal full' : (eleDims.height >= (0.5 * eleDims.windowDims.height)) ? 'reveal' : 'center';

    if(elePos === 'reveal full'){
      console.log('full');
      //set to full height/width
      this.$element
          .offset(Foundation.Box.GetOffsets(this.$element, null, elePos, this.options.vOffset))
          .css({
            'height': eleDims.windowDims.height,
            'width': eleDims.windowDims.width
          });
    }else if(!Foundation.MediaQuery.atLeast('medium') || !Foundation.Box.ImNotTouchingYou(this.$element, null, true, false)){
      //if smaller than medium, resize to 100% width minus any custom L/R margin
      this.$element
          .css({
            'width': eleDims.windowDims.width - (this.options.hOffset * 2)
          })
          .offset(Foundation.Box.GetOffsets(this.$element, null, 'center', this.options.vOffset, this.options.hOffset));
      //flag a boolean so we can reset the size after the element is closed.
      this.changedSize = true;
    }else{
      this.$element
          .css({
            'max-height': eleDims.windowDims.height - (this.options.vOffset * (this.options.btmOffsetPct / 100 + 1)),
            'width': ''
          })
          .offset(Foundation.Box.GetOffsets(this.$element, null, elePos, this.options.vOffset));
          //the max height based on a percentage of vertical offset plus vertical offset
    }

    cb();
  };

  /**
   * Opens the modal controlled by `this.$anchor`, and closes all others by default.
   * @fires Reveal#closeAll
   * @fires Reveal#open
   */
  Reveal.prototype._open = function(){
    var _this = this;
    this.isActive = true;
    //make element invisible, but remove display: none so we can get size and positioning
    this.$element
        .css({'visibility': 'hidden'})
        .show()
        .scrollTop(0);

    this._setPosition(function(){
      _this.$element.hide()
                   .css({'visibility': ''});
      if(!_this.options.multipleOpened){
        /**
         * Fires immediately before the modal opens.
         * Closes any other modals that are currently open
         * @event Reveal#closeAll
         */
        _this.$element.trigger('closeme.zf.reveal', _this.id);
      }
      if(_this.options.animationIn){
        if(_this.options.overlay){
          Foundation.Motion.animateIn(_this.$overlay, 'fade-in', function(){
            Foundation.Motion.animateIn(_this.$element, _this.options.animationIn, function(){
            });
          });
        }else{
          Foundation.Motion.animateIn(_this.$element, _this.options.animationIn, function(){
          });
        }
      }else{
        if(_this.options.overlay){
          _this.$overlay.show(0, function(){
            _this.$element.show(_this.options.showDelay, function(){
            });
          });
        }else{
          _this.$element.show(_this.options.showDelay, function(){
          });
        }
      }
    });

    // handle accessibility
    this.$element.attr({'aria-hidden': false}).attr('tabindex', -1).focus()
    /**
     * Fires when the modal has successfully opened.
     * @event Reveal#open
     */
                 .trigger('open.zf.reveal');

    $('body').addClass('is-reveal-open')
             .attr({'aria-hidden': (this.options.overlay || this.options.fullScreen) ? true : false});
    setTimeout(function(){
      _this._extraHandlers();
      // Foundation.reflow();
    }, 0);
  };

  /**
   * Adds extra event handlers for the body and window if necessary.
   * @private
   */
  Reveal.prototype._extraHandlers = function(){
    var _this = this;
    var visibleFocusableElements = this.$element.find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]').filter(function() {
      if (!$(this).is(':visible') || $(this).attr('tabindex') < 0){ return false; }//only have visible elements and those that have a tabindex greater or equal 0
      return true;
    });

    if(!this.options.overlay && this.options.closeOnClick && !this.options.fullScreen){
      $('body').on('click.zf.reveal', function(e){
        // if()
          _this._close();
      });
    }
    if(this.options.closeOnEsc){
      $(window).on('keydown.zf.reveal', function(e){
        if (visibleFocusableElements.length === 0) { // no focusable elements inside the modal at all, prevent tabbing in general
          e.preventDefault();
        }
        Foundation.Keyboard.handleKey(e, _this, {
          close: function() {
            if (this.options.closeOnEsc) {
              this._close();
            }
          }
        });
      });
    }

    // lock focus within modal while tabbing
    this.$element.on('keydown.zf.reveal', function(e) {
      var $target = $(this);
      // handle keyboard event with keyboard util
      Foundation.Keyboard.handleKey(e, _this, {
        tab_forward: function() {
          if (this.$element.find(':focus').is(visibleFocusableElements.eq(-1))) { // left modal downwards, setting focus to first element
            visibleFocusableElements.eq(0).focus();
            e.preventDefault();
          }
        },
        tab_backward: function() {
          if (this.$element.find(':focus').is(visibleFocusableElements.eq(0)) || this.$element.is(':focus')) { // left modal upwards, setting focus to last element
            visibleFocusableElements.eq(-1).focus();
            e.preventDefault();
          }
        },
        open: function() {
          if ($target.is(visibleFocusableElements)) { // dont't trigger if acual element has focus (i.e. inputs, links, ...)
            this._open();
          }
        },
        close: function() {
          if (this.options.closeOnEsc) {
            this._close();
          }
        }
      });
      if (visibleFocusableElements.length === 0) { // no focusable elements inside the modal at all, prevent tabbing in general
        e.preventDefault();
      }
    });

  };

  /**
   * Closes the modal
   * @fires Reveal#closed
   */
  Reveal.prototype._close = function(){
    if(!this.isActive || !this.$element.is(':visible')){
      return false;
    }
    var _this = this;

    if(this.options.animationOut){
      Foundation.Motion.animateOut(this.$element, this.options.animationOut, function(){
        if(_this.options.overlay){
          Foundation.Motion.animateOut(_this.$overlay, 'fade-out', function(){
          });
        }
      });
    }else{
      this.$element.hide(_this.options.hideDelay, function(){
        if(_this.options.overlay){
          _this.$overlay.hide(0, function(){
          });
        }
      });
    }
    //conditionals to remove extra event listeners added on open
    if(this.options.closeOnEsc){
      $(window).off('keydown.zf.reveal');
    }
    if(!this.options.overlay && this.options.closeOnClick){
      $('body').off('click.zf.reveal');
    }
    this.$element.off('keydown.zf.reveal');

    //if the modal changed size, reset it
    if(this.changedSize){
      this.$element.css({
        'height': '',
        'width': ''
      });
    }

    $('body').removeClass('is-reveal-open').attr({'aria-hidden': false, 'tabindex': ''});

    /**
    * Resets the modal content
    * This prevents a running video to keep going in the background
    */
    if(this.options.resetOnClose) {
      this.$element.html(this.$element.html());
    }

    this.isActive = false;
    this.$element.attr({'aria-hidden': true})
    /**
     * Fires when the modal is done closing.
     * @event Reveal#closed
     */
                 .trigger('closed.zf.reveal');
  };

  Reveal.prototype.toggle = function(){
    if(this.isActive){
      this._close();
    }else{
      this._open();
    }
  };

  /**
   * Destroys an instance of a modal.
   * @fires Reveal#destroyed
   */
  Reveal.prototype.destroy = function() {
    if(this.options.overlay){
      this.$overlay.hide().off();
    }
    this.$element.hide();
    this.$anchor.off();

    Foundation.unregisterPlugin(this);

    /**
     * Fires when the plugin has been destroyed.
     * @event Reveal#destroyed
     */
    // this.$element.trigger('destroyed.zf.reveal');
  };

  Foundation.plugin(Reveal);

  // Exports for AMD/Browserify
  if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
    module.exports = Reveal;
  if (typeof define === 'function')
    define(['foundation'], function() {
      return Reveal;
    });

}(Foundation, jQuery);
<commit_msg>adds docs comments to reveal.js<commit_after>/**
 * Reveal module.
 * @module foundation.reveal
 * @requires foundation.util.keyboard
 * @requires foundation.util.size-and-collision
 * @requires foundation.util.triggers
 * @requires foundation.util.mediaQuery
 */
!function(Foundation, $) {
  'use strict';

  /**
   * Creates a new instance of Reveal.
   * @class
   * @param {jQuery} element - jQuery object to use for the modal.
   * @param {Object} options - optional parameters.
   */

  function Reveal(element, options) {
    this.$element = element;
    this.options = $.extend({}, Reveal.defaults, this.$element.data(), options);
    this._init();

    Foundation.registerPlugin(this);
    Foundation.Keyboard.register('Reveal', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ESCAPE': 'close',
      'TAB': 'tab_forward',
      'SHIFT_TAB': 'tab_backward'
    });
  }

  Reveal.defaults = {
    /**
     * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide.
     * @option
     * @example 'slide-in-left'
     */
    animationIn: '',
    /**
     * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide.
     * @option
     * @example 'slide-out-right'
     */
    animationOut: '',
    /**
     * Time, in ms, to delay the opening of a modal after a click if no animation used.
     * @option
     * @example 10
     */
    showDelay: 0,
    /**
     * Time, in ms, to delay the closing of a modal after a click if no animation used.
     * @option
     * @example 10
     */
    hideDelay: 0,
    /**
     * Allows a click on the body/overlay to close the modal.
     * @option
     * @example true
     */
    closeOnClick: true,
    /**
     * Allows the modal to close if the user presses the `ESCAPE` key.
     * @option
     * @example true
     */
    closeOnEsc: true,
    /**
     * If true, allows multiple modals to be displayed at once.
     * @option
     * @example false
     */
    multipleOpened: false,
    /**
     * Distance, in pixels, the modal should push down from the top of the screen.
     * @option
     * @example 100
     */
    vOffset: 100,
    /**
     * Distance, in pixels, the modal should push in from the side of the screen.
     * @option
     * @example 0
     */
    hOffset: 0,
    /**
     * Allows the modal to be fullscreen, completely blocking out the rest of the view. JS checks for this as well.
     * @option
     * @example false
     */
    fullScreen: false,
    /**
     * Percentage of screen height the modal should push up from the bottom of the view.
     * @option
     * @example 10
     */
    btmOffsetPct: 10,
    /**
     * Allows the modal to generate an overlay div, which will cover the view when modal opens.
     * @option
     * @example true
     */
    overlay: true,
    /**
     * Allows the modal to remove and reinject markup on close. Should be true if using video elements w/o using provider's api.
     * @option
     * @example false
     */
    resetOnClose: false
  };

  /**
   * Initializes the modal by adding the overlay and close buttons, (if selected).
   * @private
   */
  Reveal.prototype._init = function(){
    this.id = this.$element.attr('id');
    this.isActive = false;

    this.$anchor = $('[data-open="' + this.id + '"]').length ? $('[data-open="' + this.id + '"]') : $('[data-toggle="' + this.id + '"]');

    if(this.$anchor.length){
      var anchorId = this.$anchor[0].id || Foundation.GetYoDigits(6, 'reveal');

      this.$anchor.attr({
        'aria-controls': this.id,
        'id': anchorId,
        'aria-haspopup': true,
        'tabindex': 0
      });
      this.$element.attr({'aria-labelledby': anchorId});
    }

    // this.options.fullScreen = this.$element.hasClass('full');
    if(this.options.fullScreen || this.$element.hasClass('full')){
      this.options.fullScreen = true;
      this.options.overlay = false;
    }
    if(this.options.overlay){
      this.$overlay = this._makeOverlay(this.id);
    }

    this.$element.attr({
        'role': 'dialog',
        'aria-hidden': true,
        'data-yeti-box': this.id,
        'data-resize': this.id
    });

    this._events();
  };

  /**
   * Creates an overlay div to display behind the modal.
   * @private
   */
  Reveal.prototype._makeOverlay = function(id){
    var $overlay = $('

')
                    .addClass('reveal-overlay')
                    .attr({'tabindex': -1, 'aria-hidden': true})
                    .appendTo('body');
    if(this.options.closeOnClick){
      $overlay.attr({
        'data-close': id
      });
    }
    return $overlay;
  };

  /**
   * Adds event handlers for the modal.
   * @private
   */
  Reveal.prototype._events = function(){
    var _this = this;

    this.$element.on({
      'open.zf.trigger': this.open.bind(this),
      'close.zf.trigger': this.close.bind(this),
      'toggle.zf.trigger': this.toggle.bind(this),
      'resizeme.zf.trigger': function(){
        if(_this.$element.is(':visible')){
          _this._setPosition(function(){});
        }
      }
    });

    if(this.$anchor.length){
      this.$anchor.on('keydown.zf.reveal', function(e){
        if(e.which === 13 || e.which === 32){
          e.stopPropagation();
          e.preventDefault();
          _this.open();
        }
      });
    }

    if(this.options.closeOnClick && this.options.overlay){
      this.$overlay.off('.zf.reveal').on('click.zf.reveal', this.close.bind(this));
    }
  };
  /**
   * Sets the position of the modal before opening
   * @param {Function} cb - a callback function to execute when positioning is complete.
   * @private
   */
  Reveal.prototype._setPosition = function(cb){
    var eleDims = Foundation.Box.GetDimensions(this.$element);
    var elePos = this.options.fullScreen ? 'reveal full' : (eleDims.height >= (0.5 * eleDims.windowDims.height)) ? 'reveal' : 'center';

    if(elePos === 'reveal full'){
      console.log('full');
      //set to full height/width
      this.$element
          .offset(Foundation.Box.GetOffsets(this.$element, null, elePos, this.options.vOffset))
          .css({
            'height': eleDims.windowDims.height,
            'width': eleDims.windowDims.width
          });
    }else if(!Foundation.MediaQuery.atLeast('medium') || !Foundation.Box.ImNotTouchingYou(this.$element, null, true, false)){
      //if smaller than medium, resize to 100% width minus any custom L/R margin
      this.$element
          .css({
            'width': eleDims.windowDims.width - (this.options.hOffset * 2)
          })
          .offset(Foundation.Box.GetOffsets(this.$element, null, 'center', this.options.vOffset, this.options.hOffset));
      //flag a boolean so we can reset the size after the element is closed.
      this.changedSize = true;
    }else{
      this.$element
          .css({
            'max-height': eleDims.windowDims.height - (this.options.vOffset * (this.options.btmOffsetPct / 100 + 1)),
            'width': ''
          })
          .offset(Foundation.Box.GetOffsets(this.$element, null, elePos, this.options.vOffset));
          //the max height based on a percentage of vertical offset plus vertical offset
    }

    cb();
  };

  /**
   * Opens the modal controlled by `this.$anchor`, and closes all others by default.
   * @function
   * @fires Reveal#closeAll
   * @fires Reveal#open
   */
  Reveal.prototype.open = function(){
    var _this = this;
    this.isActive = true;
    //make element invisible, but remove display: none so we can get size and positioning
    this.$element
        .css({'visibility': 'hidden'})
        .show()
        .scrollTop(0);

    this._setPosition(function(){
      _this.$element.hide()
                   .css({'visibility': ''});
      if(!_this.options.multipleOpened){
        /**
         * Fires immediately before the modal opens.
         * Closes any other modals that are currently open
         * @event Reveal#closeAll
         */
        _this.$element.trigger('closeme.zf.reveal', _this.id);
      }
      if(_this.options.animationIn){
        if(_this.options.overlay){
          Foundation.Motion.animateIn(_this.$overlay, 'fade-in', function(){
            Foundation.Motion.animateIn(_this.$element, _this.options.animationIn, function(){
            });
          });
        }else{
          Foundation.Motion.animateIn(_this.$element, _this.options.animationIn, function(){
          });
        }
      }else{
        if(_this.options.overlay){
          _this.$overlay.show(0, function(){
            _this.$element.show(_this.options.showDelay, function(){
            });
          });
        }else{
          _this.$element.show(_this.options.showDelay, function(){
          });
        }
      }
    });

    // handle accessibility
    this.$element.attr({'aria-hidden': false}).attr('tabindex', -1).focus()
    /**
     * Fires when the modal has successfully opened.
     * @event Reveal#open
     */
                 .trigger('open.zf.reveal');

    $('body').addClass('is-reveal-open')
             .attr({'aria-hidden': (this.options.overlay || this.options.fullScreen) ? true : false});
    setTimeout(function(){
      _this._extraHandlers();
      // Foundation.reflow();
    }, 0);
  };

  /**
   * Adds extra event handlers for the body and window if necessary.
   * @private
   */
  Reveal.prototype._extraHandlers = function(){
    var _this = this;
    var visibleFocusableElements = this.$element.find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]').filter(function() {
      if (!$(this).is(':visible') || $(this).attr('tabindex') < 0){ return false; }//only have visible elements and those that have a tabindex greater or equal 0
      return true;
    });

    if(!this.options.overlay && this.options.closeOnClick && !this.options.fullScreen){
      $('body').on('click.zf.reveal', function(e){
        // if()
          _this.close();
      });
    }
    if(this.options.closeOnEsc){
      $(window).on('keydown.zf.reveal', function(e){
        if (visibleFocusableElements.length === 0) { // no focusable elements inside the modal at all, prevent tabbing in general
          e.preventDefault();
        }
        Foundation.Keyboard.handleKey(e, _this, {
          close: function() {
            if (this.options.closeOnEsc) {
              this.close();
            }
          }
        });
      });
    }

    // lock focus within modal while tabbing
    this.$element.on('keydown.zf.reveal', function(e) {
      var $target = $(this);
      // handle keyboard event with keyboard util
      Foundation.Keyboard.handleKey(e, _this, {
        tab_forward: function() {
          if (this.$element.find(':focus').is(visibleFocusableElements.eq(-1))) { // left modal downwards, setting focus to first element
            visibleFocusableElements.eq(0).focus();
            e.preventDefault();
          }
        },
        tab_backward: function() {
          if (this.$element.find(':focus').is(visibleFocusableElements.eq(0)) || this.$element.is(':focus')) { // left modal upwards, setting focus to last element
            visibleFocusableElements.eq(-1).focus();
            e.preventDefault();
          }
        },
        open: function() {
          if ($target.is(visibleFocusableElements)) { // dont't trigger if acual element has focus (i.e. inputs, links, ...)
            this.open();
          }
        },
        close: function() {
          if (this.options.closeOnEsc) {
            this.close();
          }
        }
      });
      if (visibleFocusableElements.length === 0) { // no focusable elements inside the modal at all, prevent tabbing in general
        e.preventDefault();
      }
    });

  };

  /**
   * Closes the modal.
   * @function
   * @fires Reveal#closed
   */
  Reveal.prototype.close = function(){
    if(!this.isActive || !this.$element.is(':visible')){
      return false;
    }
    var _this = this;

    if(this.options.animationOut){
      Foundation.Motion.animateOut(this.$element, this.options.animationOut, function(){
        if(_this.options.overlay){
          Foundation.Motion.animateOut(_this.$overlay, 'fade-out', function(){
          });
        }
      });
    }else{
      this.$element.hide(_this.options.hideDelay, function(){
        if(_this.options.overlay){
          _this.$overlay.hide(0, function(){
          });
        }
      });
    }
    //conditionals to remove extra event listeners added on open
    if(this.options.closeOnEsc){
      $(window).off('keydown.zf.reveal');
    }
    if(!this.options.overlay && this.options.closeOnClick){
      $('body').off('click.zf.reveal');
    }
    this.$element.off('keydown.zf.reveal');

    //if the modal changed size, reset it
    if(this.changedSize){
      this.$element.css({
        'height': '',
        'width': ''
      });
    }

    $('body').removeClass('is-reveal-open').attr({'aria-hidden': false, 'tabindex': ''});

    /**
    * Resets the modal content
    * This prevents a running video to keep going in the background
    */
    if(this.options.resetOnClose) {
      this.$element.html(this.$element.html());
    }

    this.isActive = false;
    this.$element.attr({'aria-hidden': true})
    /**
     * Fires when the modal is done closing.
     * @event Reveal#closed
     */
                 .trigger('closed.zf.reveal');
  };
  /**
   * Toggles the open/closed state of a modal.
   * @function
   */
  Reveal.prototype.toggle = function(){
    if(this.isActive){
      this.close();
    }else{
      this.open();
    }
  };

  /**
   * Destroys an instance of a modal.
   * @function
   */
  Reveal.prototype.destroy = function() {
    if(this.options.overlay){
      this.$overlay.hide().off().remove();
    }
    this.$element.hide();
    this.$anchor.off();

    Foundation.unregisterPlugin(this);
  };

  Foundation.plugin(Reveal);

  // Exports for AMD/Browserify
  if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
    module.exports = Reveal;
  if (typeof define === 'function')
    define(['foundation'], function() {
      return Reveal;
    });

}(Foundation, jQuery);
<|endoftext|>


<commit_before>import React from 'react';
import SearchBar from 'react-search-bar';

export default React.createClass({
  getInitialState: function () {
    return {
      users: [
        'leo',
        'bill',
        'barbara',
        'christine',
        'allie',
        'lucille',
        'glenn',
        'jeanette',
        'madge',
        'winifred']
    };
  },
  onChange: function (input, resolve) {
    resolve(this.state.users.filter((suggestion) => {
      return suggestion.toLowerCase().startsWith(input.toLowerCase());
    }));
  },
  onSubmit: function (input) {
    this.props.history.push('/' + input);
  },

  render: function () {
    return (
      <SearchBar
        placeholder="Search for a user"
        onChange={this.onChange}
        onSubmit={this.onSubmit} />
    );
  }
});

<commit_msg>Add API to users page<commit_after>import React from 'react';
import R from 'ramda';
import SearchBar from 'react-search-bar';

export default React.createClass({
  getInitialState: function () {
    return {
      users: [],
      names: []
    };
  },
  componentDidMount: function () {
    let component = this;
    if (process.env.NODE_ENV === 'development') {
      fetch('http://104.236.25.175:3000/users')
      .then(function (response) {
        if (response.status >= 400) {
          throw new Error('Bad response');
        }
        return response.json();
      })
      .then(function (users) {
        var names = R.map(R.prop('name'), users);
        if (component.isMounted()) {
          component.setState({
            users: users,
            names: names
          });
        }
      });
    }
  },
  onChange: function (input, resolve) {
    resolve(this.state.names.filter((suggestion) => {
      return suggestion.toLowerCase().startsWith(input.toLowerCase());
    }));
  },
  onSubmit: function (input) {
    var user = R.find(R.propEq('name', input))(this.state.users);
    this.props.history.push('/' + user.id);
  },

  render: function () {
    return (
      <SearchBar
        placeholder="Search for a user"
        onChange={this.onChange}
        onSubmit={this.onSubmit} />
    );
  }
});

<|endoftext|>


<commit_before>/* See license.txt for terms of usage */
/*global define:1*/

define([
    "firebug/firebug",
    "firebug/lib/trace",
    "firebug/lib/locale",
    "firebug/lib/wrapper",
    "firebug/lib/xpcom",
    "firebug/lib/events",
    "firebug/lib/domplate",
    "firebug/console/console",
    "firebug/chrome/tableRep",
],
function(Firebug, FBTrace, Locale, Wrapper, Xpcom, Events, Domplate, Console, TableRep) {

"use strict";

// ********************************************************************************************* //
// Resources

// https://bugzilla.mozilla.org/show_bug.cgi?id=912874

// ********************************************************************************************* //
// Constants

var {domplate, SPAN, TAG} = Domplate;

// ********************************************************************************************* //
// Command Implementation

function onExecuteCommand(context, args)
{
    var target = args[0];
    if (typeof target !== "object" || target === null)
        return undefined;

    try
    {
        var result = {};

        // Get event listeners.
        var listeners = getEventListenersForTarget(context, target);
        if (listeners)
            result = listeners;

        // Append also mutation observers into the result (if there are any).
        var observers = getMutationObserversForTarget(context, target);
        if (observers && observers.length > 0)
            result["MutationObserver"] = observers;

        var global = context.getCurrentGlobal();
        var objects = Wrapper.cloneIntoContentScope(global, result);

        consoleLog(context, target, listeners, observers);

        return objects;
    }
    catch (exc)
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("getEventListeners FAILS to create content view" + exc, exc);
    }

    return undefined;
}

// ********************************************************************************************* //
// Event Listeners

function getEventListenersForTarget(context, target)
{
    var listeners;

    try
    {
        listeners = Events.getEventListenersForTarget(target);
    }
    catch (exc)
    {
        if (FBTrace.DBG_COMMANDLINE)
            FBTrace.sysout("getEventListenersForTarget threw an exception", exc);

        return undefined;
    }

    // Sort listeners by type in alphabetical order, so they show up as such
    // in the returned object.
    listeners.sort(function(a, b)
    {
        if (a.type === b.type)
            return 0;
        return (a.type < b.type ? -1 : 1);
    });

    try
    {
        var global = context.getCurrentGlobal();
        var result = {};
        for (let li of listeners)
        {
            if (!result[li.type])
                result[li.type] = [];

            result[li.type].push(Wrapper.cloneIntoContentScope(global, {
                listener: li.func,
                useCapture: li.capturing
            }));
        }

        return result;
    }
    catch (exc)
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("getEventListeners FAILS to create content view" + exc, exc);
    }

    return undefined;
}

// ********************************************************************************************* //
// Mutation Observers

function getMutationObserversForTarget(context, target)
{
    var result = [];

    // xxxHonza: avoid exception in preceding Fx23 versions (we can remove at some point).
    if (typeof(target.getBoundMutationObservers) !== "function")
        return undefined;

    var global = context.getCurrentGlobal();
    var observers = target.getBoundMutationObservers();
    for (var i=0; i<observers.length; i++)
    {
        var observer = observers[i];
        var infos = observer.getObservingInfo();
        for (var j=0; j<infos.length; j++)
        {
            var info = infos[j];
            result.push(Wrapper.cloneIntoContentScope(global, {
                attributeOldValue: info.attributeOldValue,
                attributes: info.attributes,
                characterData: info.characterData,
                characterDataOldValue: info.characterDataOldValue,
                childList: info.childList,
                subtree: info.subtree,
                observedNode: info.observedNode,
                mutationCallback: observer.mutationCallback,
            }));
        }
    }

    return result;
}

// ********************************************************************************************* //
// Console Logging

function consoleLog(context, target, listeners, observers)
{
    var input = {
        target: target,
    };

    // xxxHonza: the function displayed in the Console panel doesn't
    // navigate to the Script panel.

    // xxxHonza: fix me, this is the second time we get the listeners.
    listeners = Events.getEventListenersForTarget(target);
    if (listeners && listeners.length > 0)
    {
        // Group for event listeners list
        input.title = Locale.$STR("eventListeners.group_title");
        var row = Console.openCollapsedGroup(input, context, "eventListenersDetails",
            GroupCaption, true, null, true);

        // xxxHonza: tableRep should have a 'render' method with parent-node passed in.
        TableRep.log(listeners, ["type", "capturing", "allowsUntrusted", "func"], context);
        Console.closeGroup(context, true);
    }

    if (observers && observers.length > 0)
    {
        // Group for mutation observers list
        input.title = Locale.$STR("mutationObservers.group_title");
        row = Console.openCollapsedGroup(input, context, "eventListenersDetails",
            GroupCaption, true, null, true);

        // xxxHonza: column labels localization?
        TableRep.log(observers, ["attributeOldValue", "attributes", "characterData",
            "characterData", "characterDataOldValue", "childList", "subtree", "observedNode",
            "mutationCallback"], context);
        Console.closeGroup(context, true);
    }
}

// ********************************************************************************************* //
// Domplate Templates

var GroupCaption = domplate(
{
    tag:
        SPAN({"class": "eventListenersTitle"},
            SPAN({"class": "eventListenersCaption"},
                "$object.title"
            ),
            SPAN(" "),
            SPAN("»"),
            SPAN(" "),
            SPAN({"class": "eventListenersTarget"},
                TAG("$object|getTargetTag", {object: "$object.target"})
            )
        ),

    getTargetTag: function(object)
    {
        var rep = Firebug.getRep(object.target);
        return rep.shortTag ? rep.shortTag : rep.tag;
    }
});

// ********************************************************************************************* //
// Registration

Firebug.registerCommand("getEventListeners", {
    helpUrl: "https://getfirebug.com/wiki/index.php/getEventListeners",
    handler: onExecuteCommand.bind(this),
    description: Locale.$STR("console.cmd.help.getEventListeners")
});

return {
    getEventListeners: onExecuteCommand
};

// ********************************************************************************************* //
});
<commit_msg>Change MutationObserver to MutationObservers (issue 6740)<commit_after>/* See license.txt for terms of usage */
/*global define:1*/

define([
    "firebug/firebug",
    "firebug/lib/trace",
    "firebug/lib/locale",
    "firebug/lib/wrapper",
    "firebug/lib/xpcom",
    "firebug/lib/events",
    "firebug/lib/domplate",
    "firebug/console/console",
    "firebug/chrome/tableRep",
],
function(Firebug, FBTrace, Locale, Wrapper, Xpcom, Events, Domplate, Console, TableRep) {

"use strict";

// ********************************************************************************************* //
// Resources

// https://bugzilla.mozilla.org/show_bug.cgi?id=912874

// ********************************************************************************************* //
// Constants

var {domplate, SPAN, TAG} = Domplate;

// ********************************************************************************************* //
// Command Implementation

function onExecuteCommand(context, args)
{
    var target = args[0];
    if (typeof target !== "object" || target === null)
        return undefined;

    try
    {
        var result = {};

        // Get event listeners.
        var listeners = getEventListenersForTarget(context, target);
        if (listeners)
            result = listeners;

        // Append also mutation observers into the result (if there are any).
        var observers = getMutationObserversForTarget(context, target);
        if (observers && observers.length > 0)
            result["MutationObservers"] = observers;

        var global = context.getCurrentGlobal();
        var objects = Wrapper.cloneIntoContentScope(global, result);

        consoleLog(context, target, listeners, observers);

        return objects;
    }
    catch (exc)
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("getEventListeners FAILS to create content view" + exc, exc);
    }

    return undefined;
}

// ********************************************************************************************* //
// Event Listeners

function getEventListenersForTarget(context, target)
{
    var listeners;

    try
    {
        listeners = Events.getEventListenersForTarget(target);
    }
    catch (exc)
    {
        if (FBTrace.DBG_COMMANDLINE)
            FBTrace.sysout("getEventListenersForTarget threw an exception", exc);

        return undefined;
    }

    // Sort listeners by type in alphabetical order, so they show up as such
    // in the returned object.
    listeners.sort(function(a, b)
    {
        if (a.type === b.type)
            return 0;
        return (a.type < b.type ? -1 : 1);
    });

    try
    {
        var global = context.getCurrentGlobal();
        var result = {};
        for (let li of listeners)
        {
            if (!result[li.type])
                result[li.type] = [];

            result[li.type].push(Wrapper.cloneIntoContentScope(global, {
                listener: li.func,
                useCapture: li.capturing
            }));
        }

        return result;
    }
    catch (exc)
    {
        if (FBTrace.DBG_ERRORS)
            FBTrace.sysout("getEventListeners FAILS to create content view" + exc, exc);
    }

    return undefined;
}

// ********************************************************************************************* //
// Mutation Observers

function getMutationObserversForTarget(context, target)
{
    var result = [];

    // xxxHonza: avoid exception in preceding Fx23 versions (we can remove at some point).
    if (typeof(target.getBoundMutationObservers) !== "function")
        return undefined;

    var global = context.getCurrentGlobal();
    var observers = target.getBoundMutationObservers();
    for (var i=0; i<observers.length; i++)
    {
        var observer = observers[i];
        var infos = observer.getObservingInfo();
        for (var j=0; j<infos.length; j++)
        {
            var info = infos[j];
            result.push(Wrapper.cloneIntoContentScope(global, {
                attributeOldValue: info.attributeOldValue,
                attributes: info.attributes,
                characterData: info.characterData,
                characterDataOldValue: info.characterDataOldValue,
                childList: info.childList,
                subtree: info.subtree,
                observedNode: info.observedNode,
                mutationCallback: observer.mutationCallback,
            }));
        }
    }

    return result;
}

// ********************************************************************************************* //
// Console Logging

function consoleLog(context, target, listeners, observers)
{
    var input = {
        target: target,
    };

    // xxxHonza: the function displayed in the Console panel doesn't
    // navigate to the Script panel.

    // xxxHonza: fix me, this is the second time we get the listeners.
    listeners = Events.getEventListenersForTarget(target);
    if (listeners && listeners.length > 0)
    {
        // Group for event listeners list
        input.title = Locale.$STR("eventListeners.group_title");
        var row = Console.openCollapsedGroup(input, context, "eventListenersDetails",
            GroupCaption, true, null, true);

        // xxxHonza: tableRep should have a 'render' method with parent-node passed in.
        TableRep.log(listeners, ["type", "capturing", "allowsUntrusted", "func"], context);
        Console.closeGroup(context, true);
    }

    if (observers && observers.length > 0)
    {
        // Group for mutation observers list
        input.title = Locale.$STR("mutationObservers.group_title");
        row = Console.openCollapsedGroup(input, context, "eventListenersDetails",
            GroupCaption, true, null, true);

        // xxxHonza: column labels localization?
        TableRep.log(observers, ["attributeOldValue", "attributes", "characterData",
            "characterData", "characterDataOldValue", "childList", "subtree", "observedNode",
            "mutationCallback"], context);
        Console.closeGroup(context, true);
    }
}

// ********************************************************************************************* //
// Domplate Templates

var GroupCaption = domplate(
{
    tag:
        SPAN({"class": "eventListenersTitle"},
            SPAN({"class": "eventListenersCaption"},
                "$object.title"
            ),
            SPAN(" "),
            SPAN("»"),
            SPAN(" "),
            SPAN({"class": "eventListenersTarget"},
                TAG("$object|getTargetTag", {object: "$object.target"})
            )
        ),

    getTargetTag: function(object)
    {
        var rep = Firebug.getRep(object.target);
        return rep.shortTag ? rep.shortTag : rep.tag;
    }
});

// ********************************************************************************************* //
// Registration

Firebug.registerCommand("getEventListeners", {
    helpUrl: "https://getfirebug.com/wiki/index.php/getEventListeners",
    handler: onExecuteCommand.bind(this),
    description: Locale.$STR("console.cmd.help.getEventListeners")
});

return {
    getEventListeners: onExecuteCommand
};

// ********************************************************************************************* //
});
<|endoftext|>


<commit_before>var Q = require('q')

var getApiData = function (url) {
  var deferred = Q.defer()
  var req = new XMLHttpRequest() // eslint-disable-line

  req.open('GET', url, true)

  req.onload = function () {
    if (this.status === 200) {
      var json = JSON.parse(req.responseText)
      deferred.resolve(json)
    } else {
      // tba better error handling
      alert('error')  // eslint-disable-line
    }
  }

  req.onerror = function () {
    deferred.reject(new Error('Server responded with a status of ' + req.status))
  }

  req.send()

  return deferred.promise
}

module.exports = {
  data: getApiData
}
<commit_msg>update standardjs hinting<commit_after>/* global XMLHttpRequest */

var Q = require('q')

var getApiData = function (url) {
  var deferred = Q.defer()
  var req = new XMLHttpRequest()

  req.open('GET', url, true)

  req.onload = function () {
    if (this.status === 200) {
      var json = JSON.parse(req.responseText)
      deferred.resolve(json)
    } else {
      // tba better error handling
      alert('error')  // eslint-disable-line
    }
  }

  req.onerror = function () {
    deferred.reject(new Error('Server responded with a status of ' + req.status))
  }

  req.send()

  return deferred.promise
}

module.exports = {
  data: getApiData
}
<|endoftext|>


<commit_before>;
(function(window, $, _, Backbone){
    'use strict';

    LY.namespace('Collections');

    LY.Collections.CoursesPreview = Backbone.Collection.extend({
        model: LY.Models.CoursePreview,
        url: 'learnyourself/data/courses.json'
    });

    LY.Collections.Courses = Backbone.Collection.extend({
        model: LY.Models.Course,
        url: 'learnyourself/data/courses.json'
    });

}(window, jQuery, _, Backbone));<commit_msg>revert url<commit_after>;
(function(window, $, _, Backbone){
    'use strict';

    LY.namespace('Collections');

    LY.Collections.CoursesPreview = Backbone.Collection.extend({
        model: LY.Models.CoursePreview,
        url: '/data/courses.json'
    });

    LY.Collections.Courses = Backbone.Collection.extend({
        model: LY.Models.Course,
        url: '/data/courses.json'
    });

}(window, jQuery, _, Backbone));<|endoftext|>


<commit_before>'use strict';

/**
 * A View aggregates the initialized events of multiple sources and safely
 * forwards their update events to a parent Properties object.
 */
class View {
  constructor(parent, sources) {
    this.parent = parent;
    this.sources = sources instanceof Array ? sources : [];

    /**
     * Create a statically bound method to register/deregister update events
     */
    this.onUpdate = function onUpdate() {
      /**
       * This is safe because the onUpdate handler is only registered to
       * sources after they have all initialized, and is removed before the View
       * is replaced.
       */
      this.parent.build();
    }.bind(this);
  }

  register(source) {
    this.sources.push(source);
  }

  /**
   * Initialize registered sources. Waits for all sources' `initialize` promises
   * to resolve, then deregisters the current active View's listeners from its
   * sources, then registers its own listeners for sources' update events, then
   * sets itself as the active view and builds the Properties instance.
   *
   * @return {Promise<View>}
   */
  activate() {
    // Already active
    if (this.parent.active === this) {
      return Promise.resolve(this);
    }

    // Wait for all sources to initialize
    return Promise.all(this.sources.map((source) => source.initialize()))
      .then(() => {
        // Deregister current active view's update listeners
        this.parent.active.destroy();

        // Register for sources' update events
        this.sources.forEach((source) => {
          source.addListener('update', this.onUpdate);
        });

        // Set self as active view and rebuild properties
        this.parent.active = this;
        return this.parent.build().then(() => this);
      });
  }

  /**
   * Deregister handler from sources' update events
   */
  destroy() {
    this.sources.forEach((source) => {
      source.removeListener('update', this.onUpdate);
    });
  }
}
module.exports = View;
<commit_msg>Fix state-guarding in View#activate()<commit_after>'use strict';

/**
 * A View aggregates the initialized events of multiple sources and safely
 * forwards their update events to a parent Properties object.
 */
class View {
  constructor(parent, sources) {
    this.parent = parent;
    this.sources = sources instanceof Array ? sources : [];

    /**
     * Create a statically bound method to register/deregister update events
     */
    this.onUpdate = function onUpdate() {
      /**
       * This is safe because the onUpdate handler is only registered to
       * sources after they have all initialized, and is removed before the View
       * is replaced.
       */
      this.parent.build();
    }.bind(this);
  }

  register(source) {
    this.sources.push(source);
  }

  /**
   * Initialize registered sources. Waits for all sources' `initialize` promises
   * to resolve, then deregisters the current active View's listeners from its
   * sources, then registers its own listeners for sources' update events, then
   * sets itself as the active view and builds the Properties instance.
   *
   * @return {Promise<View>}
   */
  activate() {
    const current = this.parent.active;

    // Already active
    if (this.parent.active === this) {
      return Promise.resolve(this);
    }
    this.parent.active = this;

    // Wait for all sources to initialize
    return Promise.all(this.sources.map((source) => source.initialize()))
      .then(() => {
        // Deregister current active view's update listeners
        current.destroy();

        // Register for sources' update events
        this.sources.forEach((source) => {
          source.addListener('update', this.onUpdate);
        });

        // Rebuild properties
        return this.parent.build().then(() => this);
      });
  }

  /**
   * Deregister handler from sources' update events
   */
  destroy() {
    this.sources.forEach((source) => {
      source.removeListener('update', this.onUpdate);
    });
  }
}
module.exports = View;
<|endoftext|>


<commit_before>/*exported Harmonic*/
var Harmonic = (function() {
  'use strict';

  var config = {
    // Cell count on a side.
    cellCount: Math.pow( 2, 6 )
  };

  var CellType = {
    UNTYPED: 0,
    BOUNDARY: 1,
    INTERIOR: 2,
    EXTERIOR: 3
  };

  function clamp( value, min, max ) {
    return Math.min( Math.max( value, min ), max );
  }

  /**
   * Marks all cells which lie on the Bresenham line rasterization as
   * BOUNDARY cells.
   *
   * Vertices are expected to be normalized to grid units. That is, a grid
   * cell will have world dimensions equal to column width and row height, but
   * local dimensions of 1 by 1.
   */
  function bresenham( grid, cols, x0, y0, x1, y1 ) {
    var dx = Math.abs( x1 - x0 ),
        dy = Math.abs( y1 - y0 );

    // Steps.
    var sx = x0 < x1 ? 1 : -1,
        sy = y0 < y1 ? 1 : -1;

    var error = dx - dy;

    var error2;
    while ( true ) {
      grid[ y0 * cols + x0 ].type = CellType.BOUNDARY;

      if ( x0 === x1 && y0 === y1 ) {
        return;
      }

      error2 = 2 * error;
      if ( error2 > -dy ) {
        error -= dy;
        x0 += sx;
      }

      if ( error2 < dx ) {
        error += dx;
        y0 += sy;
      }
    }
  }

  function dimensions( vertices ) {
    var xmin = Number.POSITIVE_INFINITY,
        ymin = Number.POSITIVE_INFINITY,
        xmax = Number.NEGATIVE_INFINITY,
        ymax = Number.NEGATIVE_INFINITY;

    var x, y;
    for ( var i = 0, il = 0.5 * vertices.length; i < il; i++ ) {
      x = vertices[ 2 * i ];
      y = vertices[ 2 * i + 1 ];

      if ( x < xmin ) { xmin = x; }
      if ( x > xmax ) { xmax = x; }
      if ( y < ymin ) { ymin = y; }
      if ( y > ymax ) { ymax = y; }
    }

    return {
      x: xmin,
      y: ymin,
      width: xmax - xmin,
      height: ymax - ymin
    };
  }

  function convert2d( x, y, vertices ) {
    var vertexCount = 0.5 * vertices.length;
    var aabb = dimensions( vertices );

    var cellCount = config.cellCount;

    var scaleX = cellCount / aabb.width,
        scaleY = cellCount / aabb.height;

    var cells = [];

    var i, il;
    for ( i = 0, il = cellCount * cellCount; i < il; i++ ) {
      cells.push({
        type: CellType.UNTYPED
      });
    }

    // Determine boundaries.
    var xmin = aabb.x,
        ymin = aabb.y;

    var x0, y0, x1, y1;
    for ( i = 0; i < vertexCount; i++ ) {
      x0 = vertices[ 2 * i ];
      y0 = vertices[ 2 * i + 1 ];
      x1 = vertices[ 2 * ( ( i + 1 ) % vertexCount ) ];
      y1 = vertices[ 2 * ( ( i + 1 ) % vertexCount ) + 1 ];

      // Normalize line to grid.
      x0 = Math.floor( clamp( ( x0 - xmin ) * scaleX, 0, cellCount - 1 ) );
      y0 = Math.floor( clamp( ( y0 - ymin ) * scaleY, 0, cellCount - 1 ) );
      x1 = Math.floor( clamp( ( x1 - xmin ) * scaleX, 0, cellCount - 1 ) );
      y1 = Math.floor( clamp( ( y1 - ymin ) * scaleY, 0, cellCount - 1 ) );

      bresenham( cells, cellCount, x0, y0, x1, y1 );
    }

    return {
      cells: cells,
      width: 1 / scaleX,
      height: 1 / scaleY
    };
  }

  return {
    config: config,
    convert2d: convert2d
  };
}) ();
<commit_msg>harmonic: Add hacky flood fill algorithm.<commit_after>/*exported Harmonic*/
var Harmonic = (function() {
  'use strict';

  var config = {
    // Cell count on a side.
    cellCount: Math.pow( 2, 6 )
  };

  var CellType = {
    UNTYPED: 0,
    BOUNDARY: 1,
    INTERIOR: 2,
    EXTERIOR: 3
  };

  function clamp( value, min, max ) {
    return Math.min( Math.max( value, min ), max );
  }

  /**
   * Marks all cells which lie on the Bresenham line rasterization as
   * BOUNDARY cells.
   *
   * Vertices are expected to be normalized to grid units. That is, a grid
   * cell will have world dimensions equal to column width and row height, but
   * local dimensions of 1 by 1.
   *
   * Assumes a square grid.
   */
  function bresenham( grid, count, x0, y0, x1, y1 ) {
    var dx = Math.abs( x1 - x0 ),
        dy = Math.abs( y1 - y0 );

    // Steps.
    var sx = x0 < x1 ? 1 : -1,
        sy = y0 < y1 ? 1 : -1;

    var error = dx - dy;

    var error2;
    while ( true ) {
      grid[ y0 * count + x0 ].type = CellType.BOUNDARY;

      if ( x0 === x1 && y0 === y1 ) {
        return;
      }

      error2 = 2 * error;
      if ( error2 > -dy ) {
        error -= dy;
        x0 += sx;
      }

      if ( error2 < dx ) {
        error += dx;
        y0 += sy;
      }
    }
  }

  /**
   * Flood fill to mark all EXTERIOR cells.
   *
   * Assumes a square grid.
   */
  function floodFill( grid, count, x, y, dx, dy ) {
    var xi = x,
        yi = y;

    var horizontal;
    while ( true ) {
      var index = yi * count + xi;
      if ( grid[ index ].type === CellType.BOUNDARY ) {
        return;
      }

      grid[ index ].type = CellType.EXTERIOR;

      // Flood fill horizontally.
      horizontal = true;
      while ( horizontal ) {
        xi += dx;
        index = yi * count + xi;

        if ( grid[ index ].type === CellType.BOUNDARY ) {
          horizontal = false;
          xi = x;
        } else {
          grid[ index ].type = CellType.EXTERIOR;
        }

        if ( xi === 0 || xi === count - 1 ) {
          horizontal = false;
          xi = x;
        }
      }

      yi += dy;

      if ( yi === 0 || yi === count - 1 ) {
        return;
      }
    }
  }

  function dimensions( vertices ) {
    var xmin = Number.POSITIVE_INFINITY,
        ymin = Number.POSITIVE_INFINITY,
        xmax = Number.NEGATIVE_INFINITY,
        ymax = Number.NEGATIVE_INFINITY;

    var x, y;
    for ( var i = 0, il = 0.5 * vertices.length; i < il; i++ ) {
      x = vertices[ 2 * i ];
      y = vertices[ 2 * i + 1 ];

      if ( x < xmin ) { xmin = x; }
      if ( x > xmax ) { xmax = x; }
      if ( y < ymin ) { ymin = y; }
      if ( y > ymax ) { ymax = y; }
    }

    return {
      x: xmin,
      y: ymin,
      width: xmax - xmin,
      height: ymax - ymin
    };
  }

  function convert2d( x, y, vertices ) {
    var vertexCount = 0.5 * vertices.length;
    var aabb = dimensions( vertices );

    var cellCount = config.cellCount;

    var scaleX = cellCount / aabb.width,
        scaleY = cellCount / aabb.height;

    var cells = [];

    var i, il;
    for ( i = 0, il = cellCount * cellCount; i < il; i++ ) {
      cells.push({
        type: CellType.UNTYPED
      });
    }

    // Determine boundaries.
    var xmin = aabb.x,
        ymin = aabb.y;

    var x0, y0, x1, y1;
    for ( i = 0; i < vertexCount; i++ ) {
      x0 = vertices[ 2 * i ];
      y0 = vertices[ 2 * i + 1 ];
      x1 = vertices[ 2 * ( ( i + 1 ) % vertexCount ) ];
      y1 = vertices[ 2 * ( ( i + 1 ) % vertexCount ) + 1 ];

      // Normalize line to grid.
      x0 = Math.floor( clamp( ( x0 - xmin ) * scaleX, 0, cellCount - 1 ) );
      y0 = Math.floor( clamp( ( y0 - ymin ) * scaleY, 0, cellCount - 1 ) );
      x1 = Math.floor( clamp( ( x1 - xmin ) * scaleX, 0, cellCount - 1 ) );
      y1 = Math.floor( clamp( ( y1 - ymin ) * scaleY, 0, cellCount - 1 ) );

      bresenham( cells, cellCount, x0, y0, x1, y1 );
    }

    // Flood fill to determine exterior cells.
    floodFill( cells, cellCount, 0, 0, 1, 1 );
    floodFill( cells, cellCount, 0, cellCount - 1, 1, -1 );
    floodFill( cells, cellCount, cellCount - 1, 0, -1, 1 );
    floodFill( cells, cellCount, cellCount - 1, cellCount - 1, -1, -1 );

    return {
      cells: cells,
      width: 1 / scaleX,
      height: 1 / scaleY
    };
  }

  return {
    config: config,
    convert2d: convert2d
  };
}) ();
<|endoftext|>


<commit_before>var _ = require('underscore');
var models = require('../models');
var url = require('url');
var moment = require('moment');
var calendar = require('node-calendar');

var util = require('../utilities');

var Task = models.Task;

var makerPage = function(req, res){

    Task.TaskModel.findByOwner(req.session.account._id, function(err, docs) {
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        res.render('app', {csrfToken: req.csrfToken()});
    });
};

var displayPage = function(req, res){

    Task.TaskModel.findByOwner(req.session.account._id, function(err, docs) {
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        for(var i = 0; i < docs.length; i++){
            docs[i] = docs[i].toAPI();
        }

        //Format parameter date back to "LL"
        var parameterDate = req.params.dateParam.substring(0,2) + "/" + req.params.dateParam.substring(2,4) + "/" + req.params.dateParam.substring(4,8);
        parameterDate = moment(parameterDate).format("LL");

        // Calendar
        var date = parameterDate;
//        console.log(parameterDate);

        var year = req.params.dateParam.substring(4,8);
        var month = req.params.dateParam.substring(0,2);

//        var date = new Date();
//        var year = date.getFullYear();
//        var month = date.getMonth() + 1;
        var formatedDate = moment(date).format("LL");

        var index = 0;
        var thisWeek;
        var newMonthAdd = month;
        var newMonthSub = month;
        var newYearAdd = year;
        var newYearSub = year;

        var cal = new calendar.Calendar(calendar.MONDAY).monthdatescalendar(year,month);
        if (month - 1 < 1){
            newMonthSub = 12;
            newYearSub = year - 1;
        }
        else{
            newMonthSub--;
            newYearSub = year;
        }
        var calPrev = new calendar.Calendar(calendar.MONDAY).monthdatescalendar(newYearSub,newMonthSub);

        if (month + 1 > 12){
            newMonthAdd = 1;
            newYearAdd = year + 1;
        }
        else{
            newMonthAdd++;
            newYearAdd = year;
        }

        var calNext = new calendar.Calendar(calendar.MONDAY).monthdatescalendar(newYearAdd,newMonthAdd);

        cal = cal.concat(calPrev);
        cal = cal.concat(calNext);

        // get current week
        for (var x = 0; x < cal.length; x++){
            for (var j = 0; j < 7; j++){
                cal[x][j] = moment(cal[x][j]).format("LL");
                if(parameterDate == cal[x][j]){
                    index = x;
                }
            }
        }

        thisWeek = cal[index];

        // 0: current, 1: prev, 2: next
        var dateParam = [];
        dateParam.push( moment(thisWeek[0]).format("L").replace("/", '').replace("/", ''));
        var prev = moment(thisWeek[0]).subtract(1, 'week').calendar();
        dateParam.push(moment(prev).format("L").replace("/", '').replace("/", ''));
        var next = moment(thisWeek[0]).add(1, 'week').calendar();
        dateParam.push(moment(next).format("L").replace("/", '').replace("/", ''));

        res.render('display', {csrfToken: req.csrfToken(), tasks: docs, calendar: thisWeek, currentWeek: dateParam});
    });
};

var displayCompletedPage = function(req, res){

    var completed = [];

    Task.TaskModel.findByOwner(req.session.account._id, function(err, docs) {
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        for(var i = 0; i < docs.length; i++){
            docs[i] = docs[i].toAPI();
            if (docs[i].completed === true){
                completed.push(docs[i]);
            }
        }

        res.render('completedTasks', {csrfToken: req.csrfToken(), completedTasks: completed});
    });
};

var makeTask = function(req, res){

    if(!req.body.name || !req.body.importance || !req.body.date) {
        return res.status(400).json({error: "Oops! All fields are required"});
    }

    //    var today = moment(new Date()).format("L");
    //    var inputDate = req.body.date
    //    if(moment(inputDate).format("L") < today){
    //        return res.status(400).json({error: "Make sure the task you're creating is for the future!"});
    //    }

    if(req.body.importance < 1 || req.body.importance > 3){
        return res.status(400).json({error: "Oops! Importance must be between 1 and 3"});
    }

    var taskData = {
        name: req.body.name,
        importance: req.body.importance,
        date: req.body.date,
        owner: req.session.account._id
    };

    var newTask = new Task.TaskModel(taskData);

    newTask.save(function(err) {
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        req.session.task = newTask.toAPI();

        res.json({redirect: '/display'});
    });

};

var removeTask = function(req, res){

    Task.TaskModel.findOne({_id: req.params.id}, function(err, doc){
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        doc.remove();
        doc.save();

        res.redirect(req.get('referer'));
    });

};

var clearTasks = function(req, res){

    Task.TaskModel.findByOwner(req.session.account._id, function(err, docs) {
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        for(var i = 0; i < docs.length; i++){
            if (docs[i].completed === true){
                docs[i].remove();
                docs[i].save();
            }
        }

        res.redirect(req.get('referer'));
    });

};

var checkTask = function(req, res){

    Task.TaskModel.findOne({_id: req.params.id}, function(err, doc){
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        doc.completed = true;
        doc.save();

        res.redirect(req.get('referer'));
    });

};

var editPage = function(req, res){
    Task.TaskModel.findOne({_id: req.params.id}, function(err, doc){
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        doc = doc.toAPI();

        res.render('editTask', {csrfToken: req.csrfToken(), t: doc});
    });
};

var editTask = function(req, res){
    Task.TaskModel.findOne({_id: req.params.id}, function(err, doc){

        doc.name = req.body.name;
        doc.importance = req.body.importance;
        doc.date = req.body.date;
        doc.save();

        res.json({redirect: '/display'});
    });
};

module.exports.makerPage = makerPage;
module.exports.make = makeTask;
module.exports.editPage = editPage;
module.exports.edit = editTask;
module.exports.removeTask = removeTask;
module.exports.clearTasks = clearTasks;
module.exports.checkTask = checkTask;
module.exports.displayPage = displayPage;
module.exports.displayCompletedPage = displayCompletedPage;<commit_msg>display without parameter redirects to current week<commit_after>var _ = require('underscore');
var models = require('../models');
var url = require('url');
var moment = require('moment');
var calendar = require('node-calendar');

var util = require('../utilities');

var Task = models.Task;

var makerPage = function(req, res){

    Task.TaskModel.findByOwner(req.session.account._id, function(err, docs) {
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        res.render('app', {csrfToken: req.csrfToken()});
    });
};

var displayPage = function(req, res){

    Task.TaskModel.findByOwner(req.session.account._id, function(err, docs) {

        var date, year, month, thisWeek;
        var index = 0;

        // If no parameters, use today's date
        if (!req.params.dateParam){
            date = new Date();
            var formatedDate = moment(date).format("L");
            req.params.dateParam = formatedDate.replace("/", '').replace("/", '');
        }

        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        for(var i = 0; i < docs.length; i++){
            docs[i] = docs[i].toAPI();
        }

        //Format parameter date back to "LL"
        var parameterDate = req.params.dateParam.substring(0,2) + "/" + req.params.dateParam.substring(2,4) + "/" + req.params.dateParam.substring(4,8);
        parameterDate = moment(parameterDate).format("LL");

        // Calendar
        year = req.params.dateParam.substring(4,8);
        month = req.params.dateParam.substring(0,2);

        var newMonthAdd = month;
        var newMonthSub = month;
        var newYearAdd = year;
        var newYearSub = year;

        // Get calendar for current, previous and next month
        var cal = new calendar.Calendar(calendar.MONDAY).monthdatescalendar(year,month);
        if (month - 1 < 1){
            newMonthSub = 12;
            newYearSub = year - 1;
        }
        else{
            newMonthSub--;
            newYearSub = year;
        }
        var calPrev = new calendar.Calendar(calendar.MONDAY).monthdatescalendar(newYearSub,newMonthSub);

        if (month + 1 > 12){
            newMonthAdd = 1;
            newYearAdd = year + 1;
        }
        else{
            newMonthAdd++;
            newYearAdd = year;
        }

        var calNext = new calendar.Calendar(calendar.MONDAY).monthdatescalendar(newYearAdd,newMonthAdd);

        cal = cal.concat(calPrev);
        cal = cal.concat(calNext);

        // Get current week
        for (var x = 0; x < cal.length; x++){
            for (var j = 0; j < 7; j++){
                cal[x][j] = moment(cal[x][j]).format("LL");
                if(parameterDate == cal[x][j]){
                    index = x;
                }
            }
        }

        thisWeek = cal[index];

        // dateParam passes the parameter for current, previous and next week
        // 0: current, 1: prev, 2: next
        var dateParam = [];
        dateParam.push( moment(thisWeek[0]).format("L").replace("/", '').replace("/", ''));
        var prev = moment(thisWeek[0]).subtract(1, 'week').calendar();
        dateParam.push(moment(prev).format("L").replace("/", '').replace("/", ''));
        var next = moment(thisWeek[0]).add(1, 'week').calendar();
        dateParam.push(moment(next).format("L").replace("/", '').replace("/", ''));

        res.render('display', {csrfToken: req.csrfToken(), tasks: docs, calendar: thisWeek, currentWeek: dateParam});
    });
};

var displayCompletedPage = function(req, res){

    var completed = [];

    Task.TaskModel.findByOwner(req.session.account._id, function(err, docs) {
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        for(var i = 0; i < docs.length; i++){
            docs[i] = docs[i].toAPI();
            if (docs[i].completed === true){
                completed.push(docs[i]);
            }
        }

        res.render('completedTasks', {csrfToken: req.csrfToken(), completedTasks: completed});
    });
};

var makeTask = function(req, res){

    if(!req.body.name || !req.body.importance || !req.body.date) {
        return res.status(400).json({error: "Oops! All fields are required"});
    }

    //    var today = moment(new Date()).format("L");
    //    var inputDate = req.body.date
    //    if(moment(inputDate).format("L") < today){
    //        return res.status(400).json({error: "Make sure the task you're creating is for the future!"});
    //    }

    if(req.body.importance < 1 || req.body.importance > 3){
        return res.status(400).json({error: "Oops! Importance must be between 1 and 3"});
    }

    var taskData = {
        name: req.body.name,
        importance: req.body.importance,
        date: req.body.date,
        owner: req.session.account._id
    };

    var newTask = new Task.TaskModel(taskData);

    newTask.save(function(err) {
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        req.session.task = newTask.toAPI();

        res.json({redirect: '/display'});
    });

};

var removeTask = function(req, res){

    Task.TaskModel.findOne({_id: req.params.id}, function(err, doc){
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        doc.remove();
        doc.save();

        res.redirect(req.get('referer'));
    });

};

var clearTasks = function(req, res){

    Task.TaskModel.findByOwner(req.session.account._id, function(err, docs) {
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        for(var i = 0; i < docs.length; i++){
            if (docs[i].completed === true){
                docs[i].remove();
                docs[i].save();
            }
        }

        res.redirect(req.get('referer'));
    });

};

var checkTask = function(req, res){

    Task.TaskModel.findOne({_id: req.params.id}, function(err, doc){
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        doc.completed = true;
        doc.save();

        res.redirect(req.get('referer'));
    });

};

var editPage = function(req, res){
    Task.TaskModel.findOne({_id: req.params.id}, function(err, doc){
        if (err) {
            console.log(err);
            return res.status(400).json({error: "An error occurred"});
        }

        doc = doc.toAPI();

        res.render('editTask', {csrfToken: req.csrfToken(), t: doc});
    });
};

var editTask = function(req, res){
    Task.TaskModel.findOne({_id: req.params.id}, function(err, doc){

        doc.name = req.body.name;
        doc.importance = req.body.importance;
        doc.date = req.body.date;
        doc.save();

        res.json({redirect: '/display'});
    });
};

module.exports.makerPage = makerPage;
module.exports.make = makeTask;
module.exports.editPage = editPage;
module.exports.edit = editTask;
module.exports.removeTask = removeTask;
module.exports.clearTasks = clearTasks;
module.exports.checkTask = checkTask;
module.exports.displayPage = displayPage;
module.exports.displayCompletedPage = displayCompletedPage;<|endoftext|>


<commit_before>/**
 * APP
 *
 * The app controller manages the global scope.
 */

var util = require('util'),
    events = require('events'),
    rewriter = require('../util/jsonrewriter'),
    Amount = ripple.Amount;

var module = angular.module('app', []);

module.controller('AppCtrl', ['$rootScope', '$compile', 'rpId', 'rpNetwork',
                              function ($scope, $compile, $id, $net)
{
  reset();

  var account;

  // Global reference for debugging only (!)
  if ("object" === typeof rippleclient) {
    rippleclient.id = $id;
    rippleclient.net = $net;
  }

  function reset()
  {
    $scope.account = {};
    $scope.lines = {};
    $scope.offers = {};
    $scope.events = [];
    $scope.history = [];
    $scope.balances = {};
    $scope.loadState = [];
  }

  var myHandleAccountEvent;
  var myHandleAccountEntry;

  function handleAccountLoad(e, data)
  {
    var remote = $net.remote;

    account = data.account;

    reset();

    remote.set_secret(data.account, data.secret);

    var accountObj = remote.account(data.account);

    // We need a reference to these functions after they're bound, so we can
    // unregister them if the account is unloaded.
    myHandleAccountEvent = handleAccountEvent;
    myHandleAccountEntry = handleAccountEntry;

    accountObj.on('transaction', myHandleAccountEvent);
    accountObj.on('entry', function(data){
      $scope.$apply(function () {
        myHandleAccountEntry(data);
      });
    });

    accountObj.entry(function (err, entry) {
      if (err) {
        $scope.loadState['account'] = true;
      }
    });

    // Ripple credit lines
    remote.request_account_lines(data.account)
      .on('success', handleRippleLines)
      .on('error', handleRippleLinesError).request();

    // Transactions
    remote.request_account_tx({
      'account': data.account,
      'ledger_index_min': -1,
      'descending': true,
      'limit': Options.transactions_per_page,
      'count': true
    })
      .on('success', handleAccountTx)
      .on('error', handleAccountTxError).request();

    // Outstanding offers
    remote.request_account_offers(data.account)
      .on('success', handleOffers)
      .on('error', handleOffersError).request();
  }

  function handleAccountUnload(e, data)
  {
    var remote = $net.remote;
    var accountObj = remote.account(data.account);
    accountObj.removeListener('transaction', myHandleAccountEvent);
    accountObj.removeListener('entry', myHandleAccountEntry);
  }

  function handleRippleLines(data)
  {
    $scope.$apply(function () {
      $scope.lines = {};

      for (var n=0, l=data.lines.length; n<l; n++) {
        var line = data.lines[n];

        // XXX: This reinterpretation of the server response should be in the
        //      library upstream.
        line = $.extend({}, line, {
          limit: ripple.Amount.from_json({value: line.limit, currency: line.currency, issuer: line.account}),
          limit_peer: ripple.Amount.from_json({value: line.limit_peer, currency: line.currency, issuer: account}),
          balance: ripple.Amount.from_json({value: line.balance, currency: line.currency, issuer: account})
        });

        $scope.lines[line.account+line.currency] = line;
        updateRippleBalance(line.currency, line.account, line.balance);
      }
      console.log('lines updated:', $scope.lines);

      $scope.loadState['lines'] = true;
    });
  }

  function handleRippleLinesError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['lines'] = true;
    });
  }

  function handleOffers(data)
  {
    $scope.$apply(function () {
      data.offers.forEach(function (offerData) {
        var offer = {
          seq: +offerData.seq,
          gets: ripple.Amount.from_json(offerData.taker_gets),
          pays: ripple.Amount.from_json(offerData.taker_pays),
          flags: offerData.flags
        };

        updateOffer(offer);
      });
      console.log('offers updated:', $scope.offers);

      $scope.loadState['offers'] = true;
    });
  }

  function handleOffersError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['offers'] = true;
    });
  }

  function handleAccountEntry(data)
  {
    var remote = $net.remote;
    $scope.account = data;

    // As per json wire format convention, real ledger entries are CamelCase,
    // e.g. OwnerCount, additional convenience fields are lower case, e.g.
    // reserve, max_spend.
    var reserve_base = Amount.from_json(""+remote._reserve_base),
        reserve_inc  = Amount.from_json(""+remote._reserve_inc),
        owner_count  = $scope.account.OwnerCount || 0;
    $scope.account.reserve_base = reserve_base;
    $scope.account.reserve = reserve_base.add(reserve_inc.product_human(owner_count));
    $scope.account.reserve_to_add_trust = reserve_base.add(reserve_inc.product_human(owner_count+1));
    $scope.account.reserve_low_balance = $scope.account.reserve.product_human(2);

    // Maximum amount user can spend
    var bal = Amount.from_json(data.Balance);
    $scope.account.max_spend = bal.subtract($scope.account.reserve);

    $scope.loadState['account'] = true;
  }

  function handleAccountTx(data)
  {
    $scope.$apply(function () {
      $scope.history_count = data.count;
      $scope.tx_marker = data.marker;

      if (data.transactions) {
        data.transactions.reverse().forEach(function (e) {
          processTxn(e.tx, e.meta, true);
        });
      }

      $scope.loadState['transactions'] = true;
    });
  }
  function handleAccountTxError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['transactions'] = true;
    });
  }

  function handleAccountEvent(e)
  {
    $scope.$apply(function () {
      processTxn(e.transaction, e.meta);
    });
  }

  /**
   * Process a transaction and add it to the history table.
   */
  function processTxn(tx, meta, is_historic)
  {
    var processedTxn = rewriter.processTxn(tx, meta, account);

    if (processedTxn) {
      var transaction = processedTxn.transaction;

      // Update account
      if (processedTxn.accountRoot) {
        handleAccountEntry(processedTxn.accountRoot);
      }

      // Show status notification
      if (processedTxn.tx_result === "tesSUCCESS" &&
          transaction &&
          !is_historic) {
        $scope.$broadcast('$appTxNotification', transaction);
      }

      // Add to recent notifications
      if (processedTxn.tx_result === "tesSUCCESS") {
        $scope.events.unshift(processedTxn);
      }

      // Add to history
      $scope.history.unshift(processedTxn);

      // Update Ripple lines
      if (processedTxn.effects && !is_historic) {
        updateLines(processedTxn.effects);
      }

      // Update my offers
      if (processedTxn.effects && !is_historic) {
        // Iterate on each effect to find offers
        processedTxn.effects.forEach(function (effect) {
          // Only these types are offers
          if (_.contains([
            'offer_created',
            'offer_funded',
            'offer_partially_funded',
            'offer_cancelled'], effect.type))
          {
            var offer = {
              seq: +effect.seq,
              gets: effect.gets,
              pays: effect.pays,
              deleted: effect.deleted,
              flags: effect.flags
            };

            updateOffer(offer);
          }
        });
      }
    }
  }

  function updateOffer(offer)
  {
    if (offer.flags && offer.flags === ripple.Remote.flags.offer.Sell) {
      offer.type = 'sell';
      offer.first = offer.gets;
      offer.second = offer.pays;
    } else {
      offer.type = 'buy';
      offer.first = offer.pays;
      offer.second = offer.gets;
    }

    if (!offer.deleted) {
      $scope.offers[""+offer.seq] = offer;
    } else {
      delete $scope.offers[""+offer.seq];
    }
  }

  function updateLines(effects)
  {
    if (!$.isArray(effects)) return;

    $.each(effects, function () {
      if (_.contains([
        'trust_create_local',
        'trust_create_remote',
        'trust_change_local',
        'trust_change_remote',
        'trust_change_balance',
        'trust_change_no_ripple'], this.type))
      {
        var effect = this,
            line = {},
            index = effect.counterparty + effect.currency;

        line.currency = effect.currency;
        line.account = effect.counterparty;
        line.flags = effect.flags;
        line.no_ripple = effect.noRipple;

        if (effect.balance) {
          line.balance = effect.balance;
          updateRippleBalance(effect.currency,
                                    effect.counterparty,
                                    effect.balance);
        }

        if (effect.deleted) {
          delete $scope.lines[index];
          return;
        }

        if (effect.limit) {
          line.limit = effect.limit;
        }

        if (effect.limit_peer) {
          line.limit_peer = effect.limit_peer;
        }

        $scope.lines[index] = $.extend($scope.lines[index], line);
      }
    });
  }

  function updateRippleBalance(currency, new_account, new_balance)
  {
    // Ensure the balances entry exists first
    if (!$scope.balances[currency]) {
      $scope.balances[currency] = {components: {}, total: null};
    }

    var balance = $scope.balances[currency];

    if (new_account) {
      balance.components[new_account] = new_balance;
    }

    $(balance.components).sort(function(a,b){
      return a.compareTo(b);
    });

    balance.total = null;
    for (var counterparty in balance.components) {
      var amount = balance.components[counterparty];
      balance.total = balance.total ? balance.total.add(amount) : amount;
    }
  }

  // Personalized default currency set
  if (!store.get('ripple_currencies_all')) {
    store.set('ripple_currencies_all',require('../data/currencies'));
  }

  $scope.currencies_all = store.get('ripple_currencies_all')
    ? store.get('ripple_currencies_all')
    : require('../data/currencies');

  // Personalized default pair set
  if (!store.get('ripple_pairs_all')) {
    store.set('ripple_pairs_all',require('../data/pairs'));
  }

  $scope.pairs_all = store.get('ripple_pairs_all')
    ? store.get('ripple_pairs_all')
    : require('../data/pairs');

  function compare(a, b) {
    if (a.order < b.order) return 1;
    if (a.order > b.order) return -1;
    return 0;
  }

  // sort currencies and pairs by order
  $scope.currencies_all.sort(compare);
  $scope.pairs_all.sort(compare);

  $scope.$watch('currencies_all', function(){
    store.set('ripple_currencies_all',$scope.currencies_all);
  }, true);

  $scope.$watch('pairs_all', function(){
    store.set('ripple_pairs_all',$scope.pairs_all);
  }, true);

  $scope.currencies = $scope.currencies_all.slice(1);
  $scope.pairs = $scope.pairs_all.slice(1);

  $scope.app_loaded = true;

  // Moved this to the run block
  // Nav links same page click fix
  // $('nav a').click(function(){
  //   if (location.hash == this.hash) {
  //     location.href="#/";
  //     location.href=this.href;
  //   }
  // });

  $scope.$on('$idAccountLoad', function (e, data) {
    // Server is connected
    if ($scope.connected) {
      handleAccountLoad(e, data);
    }

    // Server is not connected yet. Handle account load after server response.
    $scope.$on('$netConnected', function(){
      if ($.isEmptyObject($scope.account)) {
        handleAccountLoad(e, data);
      }
    });
  });

  $scope.$on('$idAccountUnload', handleAccountUnload);

  // XXX: The app also needs to handle updating its data when the connection is
  //      lost and later re-established. (... or will the Ripple lib do that for us?)
  var removeFirstConnectionListener =
        $scope.$on('$netConnected', handleFirstConnection);
  function handleFirstConnection() {
    removeFirstConnectionListener();
  }

  $net.listenId($id);
  $net.init();
  $id.init();

  // Testing hooks

  this.reset                  =  reset;
  this.handleAccountLoad      =  handleAccountLoad;
  this.handleAccountUnload    =  handleAccountUnload;
  this.handleRippleLines      =  handleRippleLines;
  this.handleRippleLinesError =  handleRippleLinesError;
  this.handleOffers           =  handleOffers;
  this.handleOffersError      =  handleOffersError;
  this.handleAccountEntry     =  handleAccountEntry;
  this.handleAccountTx        =  handleAccountTx;
  this.handleAccountTxError   =  handleAccountTxError;
  this.handleAccountEvent     =  handleAccountEvent;
  this.processTxn             =  processTxn;
  this.updateOffer            =  updateOffer;
  this.updateLines            =  updateLines;
  this.updateRippleBalance    =  updateRippleBalance;
  this.compare                =  compare;
  this.handleFirstConnection  =  handleFirstConnection;
}]);
<commit_msg>Fix reserve calculation for new ripple-lib version.<commit_after>/**
 * APP
 *
 * The app controller manages the global scope.
 */

var util = require('util'),
    events = require('events'),
    rewriter = require('../util/jsonrewriter'),
    Amount = ripple.Amount;

var module = angular.module('app', []);

module.controller('AppCtrl', ['$rootScope', '$compile', 'rpId', 'rpNetwork',
                              function ($scope, $compile, $id, $net)
{
  reset();

  var account;

  // Global reference for debugging only (!)
  if ("object" === typeof rippleclient) {
    rippleclient.id = $id;
    rippleclient.net = $net;
  }

  function reset()
  {
    $scope.account = {};
    $scope.lines = {};
    $scope.offers = {};
    $scope.events = [];
    $scope.history = [];
    $scope.balances = {};
    $scope.loadState = [];
  }

  var myHandleAccountEvent;
  var myHandleAccountEntry;

  function handleAccountLoad(e, data)
  {
    var remote = $net.remote;

    account = data.account;

    reset();

    remote.set_secret(data.account, data.secret);

    var accountObj = remote.account(data.account);

    // We need a reference to these functions after they're bound, so we can
    // unregister them if the account is unloaded.
    myHandleAccountEvent = handleAccountEvent;
    myHandleAccountEntry = handleAccountEntry;

    accountObj.on('transaction', myHandleAccountEvent);
    accountObj.on('entry', function(data){
      $scope.$apply(function () {
        myHandleAccountEntry(data);
      });
    });

    accountObj.entry(function (err, entry) {
      if (err) {
        $scope.loadState['account'] = true;
      }
    });

    // Ripple credit lines
    remote.request_account_lines(data.account)
      .on('success', handleRippleLines)
      .on('error', handleRippleLinesError).request();

    // Transactions
    remote.request_account_tx({
      'account': data.account,
      'ledger_index_min': -1,
      'descending': true,
      'limit': Options.transactions_per_page,
      'count': true
    })
      .on('success', handleAccountTx)
      .on('error', handleAccountTxError).request();

    // Outstanding offers
    remote.request_account_offers(data.account)
      .on('success', handleOffers)
      .on('error', handleOffersError).request();
  }

  function handleAccountUnload(e, data)
  {
    var remote = $net.remote;
    var accountObj = remote.account(data.account);
    accountObj.removeListener('transaction', myHandleAccountEvent);
    accountObj.removeListener('entry', myHandleAccountEntry);
  }

  function handleRippleLines(data)
  {
    $scope.$apply(function () {
      $scope.lines = {};

      for (var n=0, l=data.lines.length; n<l; n++) {
        var line = data.lines[n];

        // XXX: This reinterpretation of the server response should be in the
        //      library upstream.
        line = $.extend({}, line, {
          limit: ripple.Amount.from_json({value: line.limit, currency: line.currency, issuer: line.account}),
          limit_peer: ripple.Amount.from_json({value: line.limit_peer, currency: line.currency, issuer: account}),
          balance: ripple.Amount.from_json({value: line.balance, currency: line.currency, issuer: account})
        });

        $scope.lines[line.account+line.currency] = line;
        updateRippleBalance(line.currency, line.account, line.balance);
      }
      console.log('lines updated:', $scope.lines);

      $scope.loadState['lines'] = true;
    });
  }

  function handleRippleLinesError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['lines'] = true;
    });
  }

  function handleOffers(data)
  {
    $scope.$apply(function () {
      data.offers.forEach(function (offerData) {
        var offer = {
          seq: +offerData.seq,
          gets: ripple.Amount.from_json(offerData.taker_gets),
          pays: ripple.Amount.from_json(offerData.taker_pays),
          flags: offerData.flags
        };

        updateOffer(offer);
      });
      console.log('offers updated:', $scope.offers);

      $scope.loadState['offers'] = true;
    });
  }

  function handleOffersError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['offers'] = true;
    });
  }

  function handleAccountEntry(data)
  {
    var remote = $net.remote;
    $scope.account = data;

    // XXX Shouldn't be using private methods
    var server = remote._getServer();

    // As per json wire format convention, real ledger entries are CamelCase,
    // e.g. OwnerCount, additional convenience fields are lower case, e.g.
    // reserve, max_spend.
    var ownerCount  = $scope.account.OwnerCount || 0;
    $scope.account.reserve_base = server.reserve(0);
    $scope.account.reserve = server.reserve(ownerCount);
    $scope.account.reserve_to_add_trust = server.reserve(ownerCount+1);
    $scope.account.reserve_low_balance = $scope.account.reserve.product_human(2);

    // Maximum amount user can spend
    var bal = Amount.from_json(data.Balance);
    $scope.account.max_spend = bal.subtract($scope.account.reserve);

    $scope.loadState['account'] = true;
  }

  function handleAccountTx(data)
  {
    $scope.$apply(function () {
      $scope.history_count = data.count;
      $scope.tx_marker = data.marker;

      if (data.transactions) {
        data.transactions.reverse().forEach(function (e) {
          processTxn(e.tx, e.meta, true);
        });
      }

      $scope.loadState['transactions'] = true;
    });
  }
  function handleAccountTxError(data)
  {
    $scope.$apply(function () {
      $scope.loadState['transactions'] = true;
    });
  }

  function handleAccountEvent(e)
  {
    $scope.$apply(function () {
      processTxn(e.transaction, e.meta);
    });
  }

  /**
   * Process a transaction and add it to the history table.
   */
  function processTxn(tx, meta, is_historic)
  {
    var processedTxn = rewriter.processTxn(tx, meta, account);

    if (processedTxn) {
      var transaction = processedTxn.transaction;

      // Update account
      if (processedTxn.accountRoot) {
        handleAccountEntry(processedTxn.accountRoot);
      }

      // Show status notification
      if (processedTxn.tx_result === "tesSUCCESS" &&
          transaction &&
          !is_historic) {
        $scope.$broadcast('$appTxNotification', transaction);
      }

      // Add to recent notifications
      if (processedTxn.tx_result === "tesSUCCESS") {
        $scope.events.unshift(processedTxn);
      }

      // Add to history
      $scope.history.unshift(processedTxn);

      // Update Ripple lines
      if (processedTxn.effects && !is_historic) {
        updateLines(processedTxn.effects);
      }

      // Update my offers
      if (processedTxn.effects && !is_historic) {
        // Iterate on each effect to find offers
        processedTxn.effects.forEach(function (effect) {
          // Only these types are offers
          if (_.contains([
            'offer_created',
            'offer_funded',
            'offer_partially_funded',
            'offer_cancelled'], effect.type))
          {
            var offer = {
              seq: +effect.seq,
              gets: effect.gets,
              pays: effect.pays,
              deleted: effect.deleted,
              flags: effect.flags
            };

            updateOffer(offer);
          }
        });
      }
    }
  }

  function updateOffer(offer)
  {
    if (offer.flags && offer.flags === ripple.Remote.flags.offer.Sell) {
      offer.type = 'sell';
      offer.first = offer.gets;
      offer.second = offer.pays;
    } else {
      offer.type = 'buy';
      offer.first = offer.pays;
      offer.second = offer.gets;
    }

    if (!offer.deleted) {
      $scope.offers[""+offer.seq] = offer;
    } else {
      delete $scope.offers[""+offer.seq];
    }
  }

  function updateLines(effects)
  {
    if (!$.isArray(effects)) return;

    $.each(effects, function () {
      if (_.contains([
        'trust_create_local',
        'trust_create_remote',
        'trust_change_local',
        'trust_change_remote',
        'trust_change_balance',
        'trust_change_no_ripple'], this.type))
      {
        var effect = this,
            line = {},
            index = effect.counterparty + effect.currency;

        line.currency = effect.currency;
        line.account = effect.counterparty;
        line.flags = effect.flags;
        line.no_ripple = effect.noRipple;

        if (effect.balance) {
          line.balance = effect.balance;
          updateRippleBalance(effect.currency,
                                    effect.counterparty,
                                    effect.balance);
        }

        if (effect.deleted) {
          delete $scope.lines[index];
          return;
        }

        if (effect.limit) {
          line.limit = effect.limit;
        }

        if (effect.limit_peer) {
          line.limit_peer = effect.limit_peer;
        }

        $scope.lines[index] = $.extend($scope.lines[index], line);
      }
    });
  }

  function updateRippleBalance(currency, new_account, new_balance)
  {
    // Ensure the balances entry exists first
    if (!$scope.balances[currency]) {
      $scope.balances[currency] = {components: {}, total: null};
    }

    var balance = $scope.balances[currency];

    if (new_account) {
      balance.components[new_account] = new_balance;
    }

    $(balance.components).sort(function(a,b){
      return a.compareTo(b);
    });

    balance.total = null;
    for (var counterparty in balance.components) {
      var amount = balance.components[counterparty];
      balance.total = balance.total ? balance.total.add(amount) : amount;
    }
  }

  // Personalized default currency set
  if (!store.get('ripple_currencies_all')) {
    store.set('ripple_currencies_all',require('../data/currencies'));
  }

  $scope.currencies_all = store.get('ripple_currencies_all')
    ? store.get('ripple_currencies_all')
    : require('../data/currencies');

  // Personalized default pair set
  if (!store.get('ripple_pairs_all')) {
    store.set('ripple_pairs_all',require('../data/pairs'));
  }

  $scope.pairs_all = store.get('ripple_pairs_all')
    ? store.get('ripple_pairs_all')
    : require('../data/pairs');

  function compare(a, b) {
    if (a.order < b.order) return 1;
    if (a.order > b.order) return -1;
    return 0;
  }

  // sort currencies and pairs by order
  $scope.currencies_all.sort(compare);
  $scope.pairs_all.sort(compare);

  $scope.$watch('currencies_all', function(){
    store.set('ripple_currencies_all',$scope.currencies_all);
  }, true);

  $scope.$watch('pairs_all', function(){
    store.set('ripple_pairs_all',$scope.pairs_all);
  }, true);

  $scope.currencies = $scope.currencies_all.slice(1);
  $scope.pairs = $scope.pairs_all.slice(1);

  $scope.app_loaded = true;

  // Moved this to the run block
  // Nav links same page click fix
  // $('nav a').click(function(){
  //   if (location.hash == this.hash) {
  //     location.href="#/";
  //     location.href=this.href;
  //   }
  // });

  $scope.$on('$idAccountLoad', function (e, data) {
    // Server is connected
    if ($scope.connected) {
      handleAccountLoad(e, data);
    }

    // Server is not connected yet. Handle account load after server response.
    $scope.$on('$netConnected', function(){
      if ($.isEmptyObject($scope.account)) {
        handleAccountLoad(e, data);
      }
    });
  });

  $scope.$on('$idAccountUnload', handleAccountUnload);

  // XXX: The app also needs to handle updating its data when the connection is
  //      lost and later re-established. (... or will the Ripple lib do that for us?)
  var removeFirstConnectionListener =
        $scope.$on('$netConnected', handleFirstConnection);
  function handleFirstConnection() {
    removeFirstConnectionListener();
  }

  $net.listenId($id);
  $net.init();
  $id.init();

  // Testing hooks

  this.reset                  =  reset;
  this.handleAccountLoad      =  handleAccountLoad;
  this.handleAccountUnload    =  handleAccountUnload;
  this.handleRippleLines      =  handleRippleLines;
  this.handleRippleLinesError =  handleRippleLinesError;
  this.handleOffers           =  handleOffers;
  this.handleOffersError      =  handleOffersError;
  this.handleAccountEntry     =  handleAccountEntry;
  this.handleAccountTx        =  handleAccountTx;
  this.handleAccountTxError   =  handleAccountTxError;
  this.handleAccountEvent     =  handleAccountEvent;
  this.processTxn             =  processTxn;
  this.updateOffer            =  updateOffer;
  this.updateLines            =  updateLines;
  this.updateRippleBalance    =  updateRippleBalance;
  this.compare                =  compare;
  this.handleFirstConnection  =  handleFirstConnection;
}]);
<|endoftext|>