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

/**
 * @fileoverview A base ratings widget that allows the user to select a rating,
 * like "star video" in Google Video. This fires a "change" event when the user
 * selects a rating.
 *
 * Keyboard:
 * ESC = Clear (if supported)
 * Home = 1 star
 * End = Full rating
 * Left arrow = Decrease rating
 * Right arrow = Increase rating
 * 0 = Clear (if supported)
 * 1 - 9 = nth star
 *
 * @see ../demos/ratings.html
 */

goog.provide('goog.ui.Ratings');
goog.provide('goog.ui.Ratings.EventType');

goog.require('goog.dom.a11y');
goog.require('goog.dom.classes');
goog.require('goog.events.EventType');
goog.require('goog.ui.Component');

/**
 * A UI Control used for rating things, i.e. videos on Google Video.
 * @param {Array.<string>=} opt_ratings Ratings. Default: [1,2,3,4,5].
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
 * @constructor
 * @extends {goog.ui.Component}
 */
goog.ui.Ratings = function(opt_ratings, opt_domHelper) {
  goog.ui.Component.call(this, opt_domHelper);

  /**
   * Ordered ratings that can be picked, Default: [1,2,3,4,5]
   * @type {Array.<string>}
   * @private
   */
  this.ratings_ = opt_ratings || ['1', '2', '3', '4', '5'];

  /**
   * Array containing references to the star elements
   * @type {Array.<Element>}
   * @private
   */
  this.stars_ = [];
};
goog.inherits(goog.ui.Ratings, goog.ui.Component);

/**
 * Default CSS class to be applied to the root element of components rendered
 * by this renderer.
 * @type {string}
 */
goog.ui.Ratings.CSS_CLASS = goog.getCssName('goog-ratings');

/**
 * The last index to be highlighted
 * @type {number}
 * @private
 */
goog.ui.Ratings.prototype.highlightedIndex_ = -1;

/**
 * The currently selected index
 * @type {number}
 * @private
 */
goog.ui.Ratings.prototype.selectedIndex_ = -1;

/**
 * An attached form field to set the value to
 * @type {HTMLInputElement|HTMLSelectElement|null}
 * @private
 */
goog.ui.Ratings.prototype.attachedFormField_ = null;

/**
 * Enums for Ratings event type.
 * @enum {string}
 */
goog.ui.Ratings.EventType = {
  CHANGE: 'change',
  HIGHLIGHT_CHANGE: 'highlightchange',
  HIGHLIGHT: 'highlight',
  UNHIGHLIGHT: 'unhighlight'
};

/**
 * Decorate a HTML structure already in the document.  Expects the structure:
 *

 * - div
 *   - select
 *       - option 1 #text = 1 star
 *       - option 2 #text = 2 stars
 *       - option 3 #text = 3 stars
 *       - option N (where N is max number of ratings)
 *

 *
 * The div can contain other elements for graceful degredation, but they will be
 * hidden when the decoration occurs.
 *
 * @param {Element} el Div element to decorate.
 */
goog.ui.Ratings.prototype.decorateInternal = function(el) {
  var select = el.getElementsByTagName('select')[0];
  if (!select) {
    throw Error('Can not decorate ' + el + ', with Ratings. Must ' +
                'contain select box');
  }
  this.ratings_.length = 0;
  for (var i = 0, n = select.options.length; i < n; i++) {
    var option = select.options[i];
    this.ratings_.push(option.text);
  }
  this.setSelectedIndex(select.selectedIndex);
  select.style.display = 'none';
  this.attachedFormField_ = select;
  this.createDom();
  el.insertBefore(this.getElement(), select);
};

/**
 * Render the rating widget inside the provided element. This will override the
 * current content of the element.
 */
goog.ui.Ratings.prototype.enterDocument = function() {
  var el = this.getElement();
  el.tabIndex = 0;
  goog.dom.classes.add(el, goog.getCssName('goog-ratings'));
  goog.dom.a11y.setRole(el, 'slider');
  goog.dom.a11y.setState(el, 'valuemin', 0);
  var max = this.ratings_.length - 1;
  goog.dom.a11y.setState(el, 'valuemax', max);
  var handler = this.getHandler();
  handler.listen(el, 'keydown', this.onKeyDown_);

  // Create the elements for the stars
  for (var i = 0; i < this.ratings_.length; i++) {
    var star = this.getDomHelper().createDom('span', {
        'title': this.ratings_[i],
        'class': this.getClassName_(i, false),
        'index': i});
    this.stars_.push(star);
    el.appendChild(star);
  }

  handler.listen(el, goog.events.EventType.CLICK, this.onClick_);
  handler.listen(el, goog.events.EventType.MOUSEOUT, this.onMouseOut_);
  handler.listen(el, goog.events.EventType.MOUSEOVER, this.onMouseOver_);

  this.highlightIndex_(this.selectedIndex_);
};

/**
 * Should be called when the widget is removed from the document but may be
 * reused.  This removes all the listeners the widget has attached and destroys
 * the DOM nodes it uses.
 */
goog.ui.Ratings.prototype.exitDocument = function() {
  goog.ui.Ratings.superClass_.exitDocument.call(this);
  for (var i = 0; i < this.stars_.length; i++) {
    this.getDomHelper().removeNode(this.stars_[i]);
  }
  this.stars_.length = 0;
};

/** @inheritDoc */
goog.ui.Ratings.prototype.disposeInternal = function() {
  goog.ui.Ratings.superClass_.disposeInternal.call(this);
  this.ratings_.length = 0;
  this.rendered_ = false;
};

/**
 * Returns the base CSS class used by subcomponents of this component.
 * @return {string} Component-specific CSS class.
 */
goog.ui.Ratings.prototype.getCssClass = function() {
  return goog.ui.Ratings.CSS_CLASS;
};

/**
 * Sets the selected index. If the provided index is greater than the number of
 * ratings then the max is set.  0 is the first item, -1 is no selection.
 * @param {number} index The index of the rating to select.
 */
goog.ui.Ratings.prototype.setSelectedIndex = function(index) {
  index = Math.max(-1, Math.min(index, this.ratings_.length - 1));
  if (index != this.selectedIndex_) {
    this.selectedIndex_ = index;
    this.highlightIndex_(this.selectedIndex_);
    if (this.attachedFormField_) {
      if (this.attachedFormField_.tagName == 'SELECT') {
        this.attachedFormField_.selectedIndex = index;
      } else {
        this.attachedFormField_.value =
            /** @type {string} */ (this.getValue());
      }
      goog.dom.a11y.setState(this.getElement(),
        'valuenow',
        this.ratings_[index]);
    }
    this.dispatchEvent(goog.ui.Ratings.EventType.CHANGE);
  }
};

/**
 * @return {number} The index of the currently selected rating.
 */
goog.ui.Ratings.prototype.getSelectedIndex = function() {
  return this.selectedIndex_;
};

/**
 * Returns the rating value of the currently selected rating
 * @return {?string} The value of the currently selected rating (or null).
 */
goog.ui.Ratings.prototype.getValue = function() {
  return this.selectedIndex_ == -1 ? null : this.ratings_[this.selectedIndex_];
};

/**
 * Returns the index of the currently highlighted rating, -1 if the mouse isn't
 * currently over the widget
 * @return {number} The index of the currently highlighted rating.
 */
goog.ui.Ratings.prototype.getHighlightedIndex = function() {
  return this.highlightedIndex_;
};

/**
 * Returns the value of the currently highlighted rating, null if the mouse
 * isn't currently over the widget
 * @return {?string} The value of the currently highlighted rating, or null.
 */
goog.ui.Ratings.prototype.getHighlightedValue = function() {
  return this.highlightedIndex_ == -1 ? null :
      this.ratings_[this.highlightedIndex_];
};

/**
 * Sets the array of ratings that the comonent
 * @param {Array.<string>} ratings Array of value to use as ratings.
 */
goog.ui.Ratings.prototype.setRatings = function(ratings) {
  this.ratings_ = ratings;
  // TODO(user): If rendered update stars
};

/**
 * Gets the array of ratings that the component
 * @return {Array.<string>} Array of ratings.
 */
goog.ui.Ratings.prototype.getRatings = function() {
  return this.ratings_;
};

/**
 * Attaches an input or select element to the ratings widget. The value or
 * index of the field will be updated along with the ratings widget.
 * @param {HTMLSelectElement|HTMLInputElement} field The field to attach to.
 */
goog.ui.Ratings.prototype.setAttachedFormField = function(field) {
  this.attachedFormField_ = field;
};

/**
 * Returns the attached input or select element to the ratings widget.
 * @return {HTMLSelectElement|HTMLInputElement|null} The attached form field.
 */
goog.ui.Ratings.prototype.getAttachedFormField = function() {
  return this.attachedFormField_;
};

/**
 * Handle the mouse moving over a star.
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.ui.Ratings.prototype.onMouseOver_ = function(e) {
  if (goog.isDef(e.target.index)) {
    var n = e.target.index;
    if (this.highlightedIndex_ != n) {
      this.highlightIndex_(n);
      this.highlightedIndex_ = n;
      this.dispatchEvent(goog.ui.Ratings.EventType.HIGHLIGHT_CHANGE);
      this.dispatchEvent(goog.ui.Ratings.EventType.HIGHLIGHT);
    }
  }
};

/**
 * Handle the mouse moving over a star.
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.ui.Ratings.prototype.onMouseOut_ = function(e) {
  // Only remove the highlight if the mouse is not moving to another star
  if (e.relatedTarget && !goog.isDef(e.relatedTarget.index)) {
    this.highlightIndex_(this.selectedIndex_);
    this.highlightedIndex_ = -1;
    this.dispatchEvent(goog.ui.Ratings.EventType.HIGHLIGHT_CHANGE);
    this.dispatchEvent(goog.ui.Ratings.EventType.UNHIGHLIGHT);
  }
};

/**
 * Handle the mouse moving over a star.
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.ui.Ratings.prototype.onClick_ = function(e) {
  if (goog.isDef(e.target.index)) {
    this.setSelectedIndex(e.target.index);
  }
};

/**
 * Handle the key down event. 0 = unselected in this case, 1 = the first rating
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.ui.Ratings.prototype.onKeyDown_ = function(e) {
  switch (e.keyCode) {
    case 27: // esc
      this.setSelectedIndex(-1);
      break;
    case 36: // home
      this.setSelectedIndex(0);
      break;
    case 35: // end
      this.setSelectedIndex(this.ratings_.length);
      break;
    case 37: // left arrow
      this.setSelectedIndex(this.getSelectedIndex() - 1);
      break;
    case 39: // right arrow
      this.setSelectedIndex(this.getSelectedIndex() + 1);
      break;
    default:
      // Detected a numeric key stroke, such as 0 - 9.  0 clears, 1 is first
      // star, 9 is 9th star or last if there are less than 9 stars.
      var num = parseInt(String.fromCharCode(e.keyCode), 10);
      if (!isNaN(num)) {
        this.setSelectedIndex(num - 1);
      }
  }
};

/**
 * Highlights the ratings up to the selected index
 * @param {number} n Index to highlight.
 * @private
 */
goog.ui.Ratings.prototype.highlightIndex_ = function(n) {
  for (var i = 0, star; star = this.stars_[i]; i++) {
    goog.dom.classes.set(star, this.getClassName_(i, i <= n));
  }
};

/**
 * Get the class name for a given rating.  All stars have the class:
 * goog-ratings-star.
 * Other possible classnames dependent on position and state are:
 * goog-ratings-firststar-on
 * goog-ratings-firststar-off
 * goog-ratings-midstar-on
 * goog-ratings-midstar-off
 * goog-ratings-laststar-on
 * goog-ratings-laststar-off
 * @param {number} i Index to get class name for.
 * @param {boolean} on Whether it should be on.
 * @return {string} The class name.
 * @private
 */
goog.ui.Ratings.prototype.getClassName_ = function(i, on) {
  var className;
  var baseClass = this.getCssClass();

  if (i === 0) {
    className = goog.getCssName(baseClass, 'firststar');
  } else if (i == this.ratings_.length - 1) {
    className = goog.getCssName(baseClass, 'laststar');
  } else {
    className = goog.getCssName(baseClass, 'midstar');
  }

  if (on) {
    className = goog.getCssName(className, 'on');
  } else {
    className = goog.getCssName(className, 'off');
  }

  return goog.getCssName(baseClass, 'star') + ' ' + className;
};

<commit_msg><commit_after>// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A base ratings widget that allows the user to select a rating,
 * like "star video" in Google Video. This fires a "change" event when the user
 * selects a rating.
 *
 * Keyboard:
 * ESC = Clear (if supported)
 * Home = 1 star
 * End = Full rating
 * Left arrow = Decrease rating
 * Right arrow = Increase rating
 * 0 = Clear (if supported)
 * 1 - 9 = nth star
 *
 * @see ../demos/ratings.html
 */

goog.provide('goog.ui.Ratings');
goog.provide('goog.ui.Ratings.EventType');

goog.require('goog.dom.a11y');
goog.require('goog.dom.classes');
goog.require('goog.events.EventType');
goog.require('goog.ui.Component');

/**
 * A UI Control used for rating things, i.e. videos on Google Video.
 * @param {Array.<string>=} opt_ratings Ratings. Default: [1,2,3,4,5].
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
 * @constructor
 * @extends {goog.ui.Component}
 */
goog.ui.Ratings = function(opt_ratings, opt_domHelper) {
  goog.ui.Component.call(this, opt_domHelper);

  /**
   * Ordered ratings that can be picked, Default: [1,2,3,4,5]
   * @type {Array.<string>}
   * @private
   */
  this.ratings_ = opt_ratings || ['1', '2', '3', '4', '5'];

  /**
   * Array containing references to the star elements
   * @type {Array.<Element>}
   * @private
   */
  this.stars_ = [];
};
goog.inherits(goog.ui.Ratings, goog.ui.Component);

/**
 * Default CSS class to be applied to the root element of components rendered
 * by this renderer.
 * @type {string}
 */
goog.ui.Ratings.CSS_CLASS = goog.getCssName('goog-ratings');

/**
 * The last index to be highlighted
 * @type {number}
 * @private
 */
goog.ui.Ratings.prototype.highlightedIndex_ = -1;

/**
 * The currently selected index
 * @type {number}
 * @private
 */
goog.ui.Ratings.prototype.selectedIndex_ = -1;

/**
 * An attached form field to set the value to
 * @type {HTMLInputElement|HTMLSelectElement|null}
 * @private
 */
goog.ui.Ratings.prototype.attachedFormField_ = null;

/**
 * Enums for Ratings event type.
 * @enum {string}
 */
goog.ui.Ratings.EventType = {
  CHANGE: 'change',
  HIGHLIGHT_CHANGE: 'highlightchange',
  HIGHLIGHT: 'highlight',
  UNHIGHLIGHT: 'unhighlight'
};

/**
 * Decorate a HTML structure already in the document.  Expects the structure:
 *

 * - div
 *   - select
 *       - option 1 #text = 1 star
 *       - option 2 #text = 2 stars
 *       - option 3 #text = 3 stars
 *       - option N (where N is max number of ratings)
 *

 *
 * The div can contain other elements for graceful degredation, but they will be
 * hidden when the decoration occurs.
 *
 * @param {Element} el Div element to decorate.
 */
goog.ui.Ratings.prototype.decorateInternal = function(el) {
  var select = el.getElementsByTagName('select')[0];
  if (!select) {
    throw Error('Can not decorate ' + el + ', with Ratings. Must ' +
                'contain select box');
  }
  this.ratings_.length = 0;
  for (var i = 0, n = select.options.length; i < n; i++) {
    var option = select.options[i];
    this.ratings_.push(option.text);
  }
  this.setSelectedIndex(select.selectedIndex);
  select.style.display = 'none';
  this.attachedFormField_ = select;
  this.createDom();
  el.insertBefore(this.getElement(), select);
};

/**
 * Render the rating widget inside the provided element. This will override the
 * current content of the element.
 */
goog.ui.Ratings.prototype.enterDocument = function() {
  var el = this.getElement();
  el.tabIndex = 0;
  goog.dom.classes.add(el, this.getCssClass());
  goog.dom.a11y.setRole(el, 'slider');
  goog.dom.a11y.setState(el, 'valuemin', 0);
  var max = this.ratings_.length - 1;
  goog.dom.a11y.setState(el, 'valuemax', max);
  var handler = this.getHandler();
  handler.listen(el, 'keydown', this.onKeyDown_);

  // Create the elements for the stars
  for (var i = 0; i < this.ratings_.length; i++) {
    var star = this.getDomHelper().createDom('span', {
        'title': this.ratings_[i],
        'class': this.getClassName_(i, false),
        'index': i});
    this.stars_.push(star);
    el.appendChild(star);
  }

  handler.listen(el, goog.events.EventType.CLICK, this.onClick_);
  handler.listen(el, goog.events.EventType.MOUSEOUT, this.onMouseOut_);
  handler.listen(el, goog.events.EventType.MOUSEOVER, this.onMouseOver_);

  this.highlightIndex_(this.selectedIndex_);
};

/**
 * Should be called when the widget is removed from the document but may be
 * reused.  This removes all the listeners the widget has attached and destroys
 * the DOM nodes it uses.
 */
goog.ui.Ratings.prototype.exitDocument = function() {
  goog.ui.Ratings.superClass_.exitDocument.call(this);
  for (var i = 0; i < this.stars_.length; i++) {
    this.getDomHelper().removeNode(this.stars_[i]);
  }
  this.stars_.length = 0;
};

/** @inheritDoc */
goog.ui.Ratings.prototype.disposeInternal = function() {
  goog.ui.Ratings.superClass_.disposeInternal.call(this);
  this.ratings_.length = 0;
  this.rendered_ = false;
};

/**
 * Returns the base CSS class used by subcomponents of this component.
 * @return {string} Component-specific CSS class.
 */
goog.ui.Ratings.prototype.getCssClass = function() {
  return goog.ui.Ratings.CSS_CLASS;
};

/**
 * Sets the selected index. If the provided index is greater than the number of
 * ratings then the max is set.  0 is the first item, -1 is no selection.
 * @param {number} index The index of the rating to select.
 */
goog.ui.Ratings.prototype.setSelectedIndex = function(index) {
  index = Math.max(-1, Math.min(index, this.ratings_.length - 1));
  if (index != this.selectedIndex_) {
    this.selectedIndex_ = index;
    this.highlightIndex_(this.selectedIndex_);
    if (this.attachedFormField_) {
      if (this.attachedFormField_.tagName == 'SELECT') {
        this.attachedFormField_.selectedIndex = index;
      } else {
        this.attachedFormField_.value =
            /** @type {string} */ (this.getValue());
      }
      goog.dom.a11y.setState(this.getElement(),
        'valuenow',
        this.ratings_[index]);
    }
    this.dispatchEvent(goog.ui.Ratings.EventType.CHANGE);
  }
};

/**
 * @return {number} The index of the currently selected rating.
 */
goog.ui.Ratings.prototype.getSelectedIndex = function() {
  return this.selectedIndex_;
};

/**
 * Returns the rating value of the currently selected rating
 * @return {?string} The value of the currently selected rating (or null).
 */
goog.ui.Ratings.prototype.getValue = function() {
  return this.selectedIndex_ == -1 ? null : this.ratings_[this.selectedIndex_];
};

/**
 * Returns the index of the currently highlighted rating, -1 if the mouse isn't
 * currently over the widget
 * @return {number} The index of the currently highlighted rating.
 */
goog.ui.Ratings.prototype.getHighlightedIndex = function() {
  return this.highlightedIndex_;
};

/**
 * Returns the value of the currently highlighted rating, null if the mouse
 * isn't currently over the widget
 * @return {?string} The value of the currently highlighted rating, or null.
 */
goog.ui.Ratings.prototype.getHighlightedValue = function() {
  return this.highlightedIndex_ == -1 ? null :
      this.ratings_[this.highlightedIndex_];
};

/**
 * Sets the array of ratings that the comonent
 * @param {Array.<string>} ratings Array of value to use as ratings.
 */
goog.ui.Ratings.prototype.setRatings = function(ratings) {
  this.ratings_ = ratings;
  // TODO(user): If rendered update stars
};

/**
 * Gets the array of ratings that the component
 * @return {Array.<string>} Array of ratings.
 */
goog.ui.Ratings.prototype.getRatings = function() {
  return this.ratings_;
};

/**
 * Attaches an input or select element to the ratings widget. The value or
 * index of the field will be updated along with the ratings widget.
 * @param {HTMLSelectElement|HTMLInputElement} field The field to attach to.
 */
goog.ui.Ratings.prototype.setAttachedFormField = function(field) {
  this.attachedFormField_ = field;
};

/**
 * Returns the attached input or select element to the ratings widget.
 * @return {HTMLSelectElement|HTMLInputElement|null} The attached form field.
 */
goog.ui.Ratings.prototype.getAttachedFormField = function() {
  return this.attachedFormField_;
};

/**
 * Handle the mouse moving over a star.
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.ui.Ratings.prototype.onMouseOver_ = function(e) {
  if (goog.isDef(e.target.index)) {
    var n = e.target.index;
    if (this.highlightedIndex_ != n) {
      this.highlightIndex_(n);
      this.highlightedIndex_ = n;
      this.dispatchEvent(goog.ui.Ratings.EventType.HIGHLIGHT_CHANGE);
      this.dispatchEvent(goog.ui.Ratings.EventType.HIGHLIGHT);
    }
  }
};

/**
 * Handle the mouse moving over a star.
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.ui.Ratings.prototype.onMouseOut_ = function(e) {
  // Only remove the highlight if the mouse is not moving to another star
  if (e.relatedTarget && !goog.isDef(e.relatedTarget.index)) {
    this.highlightIndex_(this.selectedIndex_);
    this.highlightedIndex_ = -1;
    this.dispatchEvent(goog.ui.Ratings.EventType.HIGHLIGHT_CHANGE);
    this.dispatchEvent(goog.ui.Ratings.EventType.UNHIGHLIGHT);
  }
};

/**
 * Handle the mouse moving over a star.
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.ui.Ratings.prototype.onClick_ = function(e) {
  if (goog.isDef(e.target.index)) {
    this.setSelectedIndex(e.target.index);
  }
};

/**
 * Handle the key down event. 0 = unselected in this case, 1 = the first rating
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.ui.Ratings.prototype.onKeyDown_ = function(e) {
  switch (e.keyCode) {
    case 27: // esc
      this.setSelectedIndex(-1);
      break;
    case 36: // home
      this.setSelectedIndex(0);
      break;
    case 35: // end
      this.setSelectedIndex(this.ratings_.length);
      break;
    case 37: // left arrow
      this.setSelectedIndex(this.getSelectedIndex() - 1);
      break;
    case 39: // right arrow
      this.setSelectedIndex(this.getSelectedIndex() + 1);
      break;
    default:
      // Detected a numeric key stroke, such as 0 - 9.  0 clears, 1 is first
      // star, 9 is 9th star or last if there are less than 9 stars.
      var num = parseInt(String.fromCharCode(e.keyCode), 10);
      if (!isNaN(num)) {
        this.setSelectedIndex(num - 1);
      }
  }
};

/**
 * Highlights the ratings up to the selected index
 * @param {number} n Index to highlight.
 * @private
 */
goog.ui.Ratings.prototype.highlightIndex_ = function(n) {
  for (var i = 0, star; star = this.stars_[i]; i++) {
    goog.dom.classes.set(star, this.getClassName_(i, i <= n));
  }
};

/**
 * Get the class name for a given rating.  All stars have the class:
 * goog-ratings-star.
 * Other possible classnames dependent on position and state are:
 * goog-ratings-firststar-on
 * goog-ratings-firststar-off
 * goog-ratings-midstar-on
 * goog-ratings-midstar-off
 * goog-ratings-laststar-on
 * goog-ratings-laststar-off
 * @param {number} i Index to get class name for.
 * @param {boolean} on Whether it should be on.
 * @return {string} The class name.
 * @private
 */
goog.ui.Ratings.prototype.getClassName_ = function(i, on) {
  var className;
  var baseClass = this.getCssClass();

  if (i === 0) {
    className = goog.getCssName(baseClass, 'firststar');
  } else if (i == this.ratings_.length - 1) {
    className = goog.getCssName(baseClass, 'laststar');
  } else {
    className = goog.getCssName(baseClass, 'midstar');
  }

  if (on) {
    className = goog.getCssName(className, 'on');
  } else {
    className = goog.getCssName(className, 'off');
  }

  return goog.getCssName(baseClass, 'star') + ' ' + className;
};

<|endoftext|>


<commit_before>// dnsbl plugin

var cfg;
var reject=true;

exports.register = function() {
    cfg = this.config.get('dnsbl.ini');
    this.inherits('dns_list_base');

    if (cfg.main.enable_stats) {
        this.logdebug('stats reporting enabled');
        this.enable_stats = true;
    }

    if (cfg.main.stats_redis_host) {
        this.redis_host = cfg.main.stats_redis_host;
        this.logdebug('set stats redis host to: ' + this.redis_host);
    }

    if (cfg.main.reject !== 'undefined') reject = cfg.main.reject;

    this.zones = [];
    // Compatibility with old-plugin
    this.zones = this.zones.concat(this.config.get('dnsbl.zones', 'list'));
    if (cfg.main.zones) {
        this.zones = this.zones.concat(cfg.main.zones.replace(/\s+/g,'').split(/[;,]/));
    }

    if (cfg.main.periodic_checks) {
        this.check_zones(cfg.main.periodic_checks);
    }
}

exports.hook_connect = function(next, connection) {
    if (!this.zones || !this.zones.length) {
        connection.logerror(this, "no zones");
        return next();
    }
    var plugin = this;
    this.first(connection.remote_ip, this.zones, function (err, zone, a) {
        if (!a) return next();

        var msg = 'host [' + connection.remote_ip + '] is blacklisted by ' + zone;
        if (reject) return next(DENY, msg);

        connection.loginfo(plugin, msg);
        return next();
    });
}
<commit_msg>dnsbl: add error handler<commit_after>// dnsbl plugin

var cfg;
var reject=true;

exports.register = function() {
    cfg = this.config.get('dnsbl.ini');
    this.inherits('dns_list_base');

    if (cfg.main.enable_stats) {
        this.logdebug('stats reporting enabled');
        this.enable_stats = true;
    }

    if (cfg.main.stats_redis_host) {
        this.redis_host = cfg.main.stats_redis_host;
        this.logdebug('set stats redis host to: ' + this.redis_host);
    }

    if (cfg.main.reject !== 'undefined') reject = cfg.main.reject;

    this.zones = [];
    // Compatibility with old-plugin
    this.zones = this.zones.concat(this.config.get('dnsbl.zones', 'list'));
    if (cfg.main.zones) {
        this.zones = this.zones.concat(cfg.main.zones.replace(/\s+/g,'').split(/[;,]/));
    }

    if (cfg.main.periodic_checks) {
        this.check_zones(cfg.main.periodic_checks);
    }
}

exports.hook_connect = function(next, connection) {
    if (!this.zones || !this.zones.length) {
        connection.logerror(this, "no zones");
        return next();
    }
    var plugin = this;
    this.first(connection.remote_ip, this.zones, function (err, zone, a) {
        if (err) {
            connection.logerror(plugin, err);
            return next();
        }
        if (!a) return next();

        var msg = 'host [' + connection.remote_ip + '] is blacklisted by ' + zone;
        if (reject) return next(DENY, msg);

        connection.loginfo(plugin, msg);
        return next();
    });
}
<|endoftext|>


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

  /* {portal::library} */

  /**
   * Portal in a real-time library agnostic framework for establishing real-time
   * connections with servers.
   *
   * @param {String} url The url of your server.
   */
  function Portal(url) {
    if (!(this instanceof Portal)) return new Portal(url);

    this.events = {};
    this.url = this.parse(url);

    this.initialise().connect();
  }

  /**
   * Initialise the portal and setup all parsers and internal listeners.
   *
   * @api private
   */
  Portal.prototype.initialise = function initalise() {
    var portal = this;

    this.on('portal::data', function message(data) {
      portal.decoder(data, function decoding(err, packet) {
        //
        // Do a "save" emit('error') when we fail to parse a message. We don't
        // want to throw here as listening to errors should be optional.
        //
        if (err) return portal.listeners('error').length && socket.emit('error', err);
        portal.emit('data', packet);
      });
    });

    return this;
  };

  /**
   * Establish a connection with the server.
   *
   * @api private
   */
  Portal.prototype.connect = function connect() {
    this.emit('portal::connect', this.uri());
  };

  /**
   * Parse the connection string.
   *
   * @param {String} url Connection url
   * @returns {Object} Parsed connection.
   * @api public
   */
  Portal.prototype.parse = function parse(url) {
    var a = document.createElement('a');
    a.href = url;

    return a;
  };

  /**
   * Generates a connection url.
   *
   * @returns {String} The url.
   * @api private
   */
  Portal.prototype.uri = function uri() {
    var server = [];

    server.push(this.url.protocol === 'https:' ? 'wss:' : 'ws:', '');
    server.push(this.url.host, this.pathname.slice(1));

    //
    // Optionally add a search query
    //
    if (this.url.search) server.push(this.url.search);
    return server.join('/');
  };

  /**
   * Emit an event to all registered event listeners.
   *
   * @param {String} event The name of the event.
   * @returns {Boolean} Indication if we've emitted an event.
   * @api public
   */
  Portal.prototype.emit = function emit(event) {
    if (!(event in this.events)) return false;

    var args = Array.prototype.slice.call(arguments, 1)
      , length = this.events[event].length
      , i = 0;

    for (; i < length; i++) {
      this.events[event][i].apply(this, args);
    }

    return true;
  };

  /**
   * Register a new EventListener for the given event.
   *
   * @param {String} event Name of the event.
   * @param {Functon} fn Callback function.
   * @api public
   */
  Portal.prototype.on = function on(event, fn) {
    if (!(event in this.events)) this.events[event] = [];
    this.events[event].push(fn);

    return this;
  };

  /**
   * Simple emit wrapper that returns a function that emits an event once it's
   * called. This makes it easier for transports to emit specific events. The
   * scope of this function is limited as it will only emit one single argument.
   *
   * @param {String} event Name of the event that we should emit.
   * @param {Function} parser Argument parser.
   * @api public
   */
  Portal.prototype.emits = function emits(event, parser) {
    var portal = this;

    return function emit(arg) {
      var data = parser ? parser.apply(portal, arguments) : arg;

      //
      // Timeout is required to prevent crashes on WebSockets connections on
      // mobile devices.
      //
      setTimeout(function timeout() {
        portal.emit('portal::'+ event, data);
      }, 0);
    };
  };

  //
  // These libraries are automatically are automatically inserted at the
  // serverside using the Portal#library method.
  //
  Portal.prototype.client = null; // @import {portal::transport};
  Portal.prototype.pathname = null; // @import {portal::pathname};
  Portal.prototype.encoder = null; // @import {portal::encoder};
  Portal.prototype.decoder = null; // @import {portal::decoder};
  Portal.prototype.version = null; // @import {portal::version};
})(this);
<commit_msg>[minor] Exponential backoff<commit_after>(function portalclient() {
  'use strict';

  /* {portal::library} */

  /**
   * Portal in a real-time library agnostic framework for establishing real-time
   * connections with servers.
   *
   * @param {String} url The url of your server.
   */
  function Portal(url) {
    if (!(this instanceof Portal)) return new Portal(url);

    this.events = {};             // Stores the events.
    this.backoff = {};            // Stores the backoff configuration.
    this.url = this.parse(url);

    this.initialise().connect();
  }

  /**
   * Initialise the portal and setup all parsers and internal listeners.
   *
   * @api private
   */
  Portal.prototype.initialise = function initalise() {
    var portal = this;

    this.on('portal::data', function message(data) {
      portal.decoder(data, function decoding(err, packet) {
        //
        // Do a "save" emit('error') when we fail to parse a message. We don't
        // want to throw here as listening to errors should be optional.
        //
        if (err) return portal.listeners('error').length && socket.emit('error', err);
        portal.emit('data', packet);
      });
    });

    this.on('portal::end', function end() {
      this.reconnect(function (fail, backoff) {
        portal.backoff = backoff; // Save the opts again of this backoff.
        if (fail) return self.emit('end', fail);

        // Try to re-open the connection again.
        portal.emit('portal::reconnect');
      }, portal.backoff);
    });

    return this;
  };

  /**
   * Establish a connection with the server.
   *
   * @api private
   */
  Portal.prototype.connect = function connect() {
    this.emit('portal::connect', this.uri());
  };

  /**
   * Close the connection.
   *
   * @api public
   */
  Portal.prototype.end = function end() {

  };

  /**
   * Exponential backoff algorithm for retry aperations. It uses an randomized
   * retry so we don't DDOS our server when it goes down under presure.
   *
   * @param {Function} callback Callback to be called after the timeout.
   * @param {Object} opts Options for configuring the timeout.
   * @api private
   */
  Portal.prototype.backoff = function backoff(callback, opts) {
    opts = opts || {};

    opts.maxDelay = opts.maxDelay || Infinity;  // Maximum delay
    opts.minDelay = opts.minDelay || 500;       // Minimum delay
    opts.retries = opts.retries || 25;          // Amount of allowed retries
    opts.attempt = (+opts.attempt || 0) + 1;    // Current attempt
    opts.factor = opts.factor || 2;             // Backoff factor

    // Bailout if we are about to make to much attempts. Please note that we use
    // `>` because we already incremented the value above.
    if (opts.attempt > opts.retries || opts.backoff) {
      return callback(new Error('Unable to retry'), opts);
    }

    // Prevent duplicate backoff attempts.
    opts.backoff = true;

    // Calculate the timeout, but make it randomly so we don't retry connections
    // at the same interval and defeat the purpose. This exponential backoff is
    // based on the work of:
    //
    // http://dthain.blogspot.nl/2009/02/exponential-backoff-in-distributed.html
    opts.timeout = opts.attempt !== 1
      ? Math.min(Math.round(
          (Math.random() * 1) * opts.minDelay * Math.pow(opts.factor, opts.attempt)
        ), opts.maxDelay)
      : opts.minDelay;

    setTimeout(function delay() {
      opts.backoff = false;
      callback(undefined, opts);
    }, opts.timeout);

    return this;
  };

  /**
   * Parse the connection string.
   *
   * @param {String} url Connection url
   * @returns {Object} Parsed connection.
   * @api public
   */
  Portal.prototype.parse = function parse(url) {
    var a = document.createElement('a');
    a.href = url;

    return a;
  };

  /**
   * Generates a connection url.
   *
   * @returns {String} The url.
   * @api private
   */
  Portal.prototype.uri = function uri() {
    var server = [];

    server.push(this.url.protocol === 'https:' ? 'wss:' : 'ws:', '');
    server.push(this.url.host, this.pathname.slice(1));

    //
    // Optionally add a search query
    //
    if (this.url.search) server.push(this.url.search);
    return server.join('/');
  };

  /**
   * Emit an event to all registered event listeners.
   *
   * @param {String} event The name of the event.
   * @returns {Boolean} Indication if we've emitted an event.
   * @api public
   */
  Portal.prototype.emit = function emit(event) {
    if (!(event in this.events)) return false;

    var args = Array.prototype.slice.call(arguments, 1)
      , length = this.events[event].length
      , i = 0;

    for (; i < length; i++) {
      this.events[event][i].apply(this, args);
    }

    return true;
  };

  /**
   * Register a new EventListener for the given event.
   *
   * @param {String} event Name of the event.
   * @param {Functon} fn Callback function.
   * @api public
   */
  Portal.prototype.on = function on(event, fn) {
    if (!(event in this.events)) this.events[event] = [];
    this.events[event].push(fn);

    return this;
  };

  /**
   * Simple emit wrapper that returns a function that emits an event once it's
   * called. This makes it easier for transports to emit specific events. The
   * scope of this function is limited as it will only emit one single argument.
   *
   * @param {String} event Name of the event that we should emit.
   * @param {Function} parser Argument parser.
   * @api public
   */
  Portal.prototype.emits = function emits(event, parser) {
    var portal = this;

    return function emit(arg) {
      var data = parser ? parser.apply(portal, arguments) : arg;

      //
      // Timeout is required to prevent crashes on WebSockets connections on
      // mobile devices.
      //
      setTimeout(function timeout() {
        portal.emit('portal::'+ event, data);
      }, 0);
    };
  };

  //
  // These libraries are automatically are automatically inserted at the
  // serverside using the Portal#library method.
  //
  Portal.prototype.client = null; // @import {portal::transport};
  Portal.prototype.pathname = null; // @import {portal::pathname};
  Portal.prototype.encoder = null; // @import {portal::encoder};
  Portal.prototype.decoder = null; // @import {portal::decoder};
  Portal.prototype.version = null; // @import {portal::version};
})(this);
<|endoftext|>


<commit_before>/**
 * Patterns polyfill-color - Polyfill for input type=date
 *
 */
define([
    "pat-registry",
    "modernizr",
    "pikaday.jquery"
], function(registry, modernizr, pikaday) {
    var _ = {
        name: "polyfill-date",
        trigger: ".pat-polyfill-date",
        init: function($el) {
            if (!Modernizr.inputtypes.date) {
                $el.pikaday();
            }
            return $el;
        },

        destroy: function() {
            // XXX
        }
    };

    registry.register(_);
    return _;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>change trigger<commit_after>/**
 * Patterns polyfill-color - Polyfill for input type=date
 *
 */
define([
    "pat-registry",
    "modernizr",
    "pikaday.jquery"
], function(registry, modernizr, pikaday) {
    var _ = {
        name: "polyfill-date",
        trigger: ".pat-date-picker",
        init: function($el) {
            if (!Modernizr.inputtypes.date) {
                $el.pikaday();
            }
            return $el;
        },

        destroy: function() {
            // XXX
        }
    };

    registry.register(_);
    return _;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<|endoftext|>


<commit_before>var glslify = require('glslify-sync') // works in client & server
import mat4 from 'gl-mat4'

export default function drawTri (regl, params) {
  const {width, height} = params
  return regl({
    vert: glslify(__dirname + '/shaders/tri.vert'),
    frag: glslify(__dirname + '/shaders/tri.frag'),

    attributes: {
      position: [
        width / 2, height, 0,
        0, 0, 0,
        width, 0, 0]
    },
    count: 3,
    uniforms: {
      model: (context, props) => props.model || mat4.identity([]),
      color: regl.prop('color'),
      angle: ({tick}) => 0.01 * tick
    },
    cull: {
      enable: false,
      face: 'back'
    }
  })
}
<commit_msg>chore(cleanup): minor cleanups of drawTri<commit_after>var glslify = require('glslify-sync') // works in client & server
import mat4 from 'gl-mat4'

export default function drawTri (regl, params) {
  const {width, height} = params
  return regl({
    vert: glslify(__dirname + '/shaders/tri.vert'),
    frag: glslify(__dirname + '/shaders/tri.frag'),

    attributes: {
      position: [
        width / 2, height, 0,
        0, 0, 0,
        width, 0, 0]
    },
    count: 3,
    uniforms: {
      model: (context, props) => props.model || mat4.identity([]),
      color: regl.prop('color')
    },
    cull: {
      enable: false,
      face: 'back'
    }
  })
}
<|endoftext|>


<commit_before>jQuery(function($) {

//    this.baseUrl = "http://swagr.api.wordnik.com/v4/list.json";
//    this.apiKey = "my-api-key";

    var ApiSelectionController = Spine.Controller.create({
        proxied: ["showApi"],

        baseUrlList: new Array(),

        init: function() {
            if (this.supportsLocalStorage()) {
                var baseUrl = localStorage.getItem("com.wordnik.swagger.ui.baseUrl");
                var apiKey = localStorage.getItem("com.wordnik.swagger.ui.apiKey");

                if(baseUrl && baseUrl.length > 0)
                    $("#input_baseUrl").val(baseUrl);

                if(apiKey && apiKey.length > 0)
                    $("#input_apiKey").val(apiKey);

//                if (baseUrls && jQuery.trim(baseUrls).length > 0) {
//                    this.baseUrlList = baseUrls.split(",");
//                }

            } else {
                log("local storage not supported, user will need to specifiy the api url")
            }

            $("#button_explore").click(this.showApi);
        },

        slapOn: function() {
            messageController.showMessage("Please enter a base url for the api that you wish to explore.");
            $("#resources_container").hide();
            this.showApi();
        },

        supportsLocalStorage: function() {
            try {
                return 'localStorage' in window && window['localStorage'] !== null;
            } catch (e) {
                return false;
            }
        },

        showApi: function() {
            var baseUrl = jQuery.trim($("#input_baseUrl").val());
            var apiKey = jQuery.trim($("#input_apiKey").val());
            if (baseUrl.length == 0) {
                $("#input_baseUrl").wiggle();
            } else {

                if(this.supportsLocalStorage()) {
                    localStorage.setItem("com.wordnik.swagger.ui.apiKey", apiKey);
                    localStorage.setItem("com.wordnik.swagger.ui.baseUrl", baseUrl);

                }
                var resourceListController = ResourceListController.init({baseUrl: baseUrl, apiKey: apiKey})
            }
        }
    });

    var MessageController = Spine.Controller.create({
        showMessage: function(msg) {
            if (msg) {
                $("#content_message").html(msg);
                $("#content_message").show();
            } else {
                $("#content_message").html("");
                $("#content_message").hide();
            }

        },

        clearMessage: function() {
            this.showMessage();
        }
    });
    var messageController = MessageController.init();

    // The following heirarchy is followed by these view controllers
    // ResourceListController
    //     >>> ResourceController
    //         >>> ApiController
    //             >>> OperationController

    var ResourceListController = Spine.Controller.create({
        proxied: ["addAll", "addOne"],

        ApiResource: null,

        init: function() {
            if (this.baseUrl == null) {
                throw new Error("A baseUrl must be passed to ResourceListController");
            }

            $("#content_message").hide();
            $("#resources_container").hide();
            $("#resources").html("");

            // create and initialize SwaggerService
            var swaggerService = new SwaggerService(this.baseUrl, this.apiKey, function(msg) {
                if (msg)
                    messageController.showMessage(msg);
                else
                    messageController.showMessage("Rendering page...");
            });
            $("#api_host_url").html(swaggerService.apiHost());
            swaggerService.init();

            // Create convenience references to Spine models
            this.ApiResource = swaggerService.ApiResource();

            this.ApiResource.bind("refresh", this.addAll);
        },

        addAll: function() {
            this.ApiResource.each(this.addOne);
            messageController.clearMessage();
            $("#resources_container").slideDown(function() {
                setTimeout(function() {
                    Docs.shebang();
                }, 400)
            });
        },

        addOne: function(apiResource) {
            ResourceController.init({item: apiResource, container: "#resources"});
        }
    });

    var ResourceController = Spine.Controller.create({
        proxied: ["renderApi", "renderOperation"],

        templateName: "#resourceTemplate",
        apiResource: null,
        apiList: null,
        modelList: null,

        init: function() {
            this.render();

            this.apiResource = this.item;
            this.apiList = this.apiResource.apiList;
            this.modelList = this.apiResource.modelList;

//            log("------------- apiResource : " + this.apiResource.name);
//            this.apiList.logAll();
//            this.modelList.logAll();

            this.apiList.each(this.renderApi);

        },

        render: function() {
            $(this.templateName).tmpl(this.item).appendTo(this.container);
        },

        renderApi: function(api) {
            var resourceApisContainer = "#" + this.apiResource.name + "_endpoint_list";
            ApiController.init({item: api, container: resourceApisContainer});

        }

    });

    var ApiController = Spine.Controller.create({
        proxied: ["renderOperation"],

        api: null,
        templateName: "#apiTemplate",

        init: function() {
            this.render();

            this.api = this.item;

            this.api.operations.each(this.renderOperation);
        },

        render: function() {
            $(this.templateName).tmpl(this.item).appendTo(this.container);
        },

        renderOperation: function(operation) {
            var operationsContainer = "#" + this.api.name + "_endpoint_operations";
            OperationController.init({item: operation, container: operationsContainer});
        }
    });

    var OperationController = Spine.Controller.create({
        proxied: ["submitOperation", "showResponse", "showErrorStatus", "showCompleteStatus"],

        operation: null,
        templateName: "#operationTemplate",
        elementScope: "#operationTemplate",

        init: function() {
            this.render();

            this.operation = this.item;
            this.isGetOperation = (this.operation.httpMethodLowercase == "get");
            this.elementScope = "#" + this.operation.apiName + "_" + this.operation.nickname + "_" + this.operation.httpMethod;

            this.renderParams();

        },

        render: function() {
            $(this.templateName).tmpl(this.item).appendTo(this.container);
        },

        renderParams: function() {
            if (this.operation.parameters && this.operation.parameters.count() > 0) {
                var operationParamsContainer = this.elementScope + "_params";
//                log("operationParamsContainer = " + operationParamsContainer);
                for (var p = 0; p < this.operation.parameters.count(); p++) {
                    var param = this.operation.parameters.all()[p];

                    var templateName = "#paramTemplate";
                    if (param.required)
                        templateName += "Required";

                    if (!this.isGetOperation)
                        templateName += "ReadOnly";

                    $(templateName).tmpl(param).appendTo(operationParamsContainer);
//                    log("adding " + $(templateName).tmpl(param) + " TO " + operationParamsContainer);
                }
            }

            var submitButtonId = this.elementScope + "_content_sandbox_response_button";
            if (this.isGetOperation) {
                $(submitButtonId).click(this.submitOperation);
            } else {
                $(submitButtonId).hide();

                var valueHeader = this.elementScope + "_value_header";
                $(valueHeader).html("Default Value");
            }

        },

        submitOperation: function() {
            var form = $(this.elementScope + "_form");
            var error_free = true;
            var missing_input = null;

            // Cycle through the forms required inputs
            form.find("input.required").each(function() {

                // Remove any existing error styles from the input
                $(this).removeClass('error');

                // Tack the error style on if the input is empty..
                if ($(this).val() == '') {
                    if (missing_input == null)
                        missing_input = $(this);
                    $(this).addClass('error');
                    $(this).wiggle();
                    error_free = false;
                }

            });

            log("error_free = " + error_free);

            if (error_free) {
                var invocationUrl = this.operation.invocationUrl(form.serializeArray());
                $(".request_url", this.elementScope + "_content_sandbox_response").html("
" + invocationUrl + "
");
                $.getJSON(invocationUrl, this.showResponse).complete(this.showCompleteStatus).error(this.showErrorStatus);
            }

        },

        showResponse: function(response) {
//            log(response);
            var prettyJson = JSON.stringify(response, null, '\t');
//            log(prettyJson);

            $(".response_body", this.elementScope + "_content_sandbox_response").html("
" + prettyJson + "
");

            $(this.elementScope + "_content_sandbox_response").slideDown();
        },

        showErrorStatus: function(data) {
            log("error " + data.status);
            this.showStatus(data);
        },

        showCompleteStatus: function(data) {
            log("complete " + data.status);
            this.showStatus(data);
        },

        showStatus: function(data) {
            log(data);
            log(data.getAllResponseHeaders());
            $(".response_code", this.elementScope + "_content_sandbox_response").html("
" + data.status + "
");
            $(".response_headers", this.elementScope + "_content_sandbox_response").html("
" + data.getAllResponseHeaders() + "
");
        }

    });

    var apiSelectionController = ApiSelectionController.init();
    if (this.baseUrl) {
        var resourceListController = ResourceListController.init({baseUrl: this.baseUrl, apiKey: this.apiKey});
    } else {
        apiSelectionController.slapOn();
    }

});

<commit_msg>Fixed JS issue<commit_after>jQuery(function($) {

//    this.baseUrl = "http://swagr.api.wordnik.com/v4/list.json";
//    this.apiKey = "my-api-key";

    var ApiSelectionController = Spine.Controller.create({
        proxied: ["showApi"],

        baseUrlList: new Array(),

        init: function() {
            if (this.supportsLocalStorage()) {
                var baseUrl = localStorage.getItem("com.wordnik.swagger.ui.baseUrl");
                var apiKey = localStorage.getItem("com.wordnik.swagger.ui.apiKey");

                if(baseUrl && baseUrl.length > 0)
                    $("#input_baseUrl").val(baseUrl);

                if(apiKey && apiKey.length > 0)
                    $("#input_apiKey").val(apiKey);

//                if (baseUrls && jQuery.trim(baseUrls).length > 0) {
//                    this.baseUrlList = baseUrls.split(",");
//                }

            } else {
                log("local storage not supported, user will need to specifiy the api url")
            }

            $("#button_explore").click(this.showApi);
        },

        slapOn: function() {
            messageController.showMessage("Please enter a base url for the api that you wish to explore.");
            $("#resources_container").hide();
            this.showApi();
        },

        supportsLocalStorage: function() {
            try {
                return 'localStorage' in window && window['localStorage'] !== null;
            } catch (e) {
                return false;
            }
        },

        showApi: function() {
            var baseUrl = jQuery.trim($("#input_baseUrl").val());
            var apiKey = jQuery.trim($("#input_apiKey").val());
            if (baseUrl.length == 0) {
                $("#input_baseUrl").wiggle();
            } else {

                if(this.supportsLocalStorage()) {
                    localStorage.setItem("com.wordnik.swagger.ui.apiKey", apiKey);
                    localStorage.setItem("com.wordnik.swagger.ui.baseUrl", baseUrl);

                }
                var resourceListController = ResourceListController.init({baseUrl: baseUrl, apiKey: apiKey})
            }
        }
    });

    var MessageController = Spine.Controller.create({
        showMessage: function(msg) {
            if (msg) {
                $("#content_message").html(msg);
                $("#content_message").show();
            } else {
                $("#content_message").html("");
                $("#content_message").hide();
            }

        },

        clearMessage: function() {
            this.showMessage();
        }
    });
    var messageController = MessageController.init();

    // The following heirarchy is followed by these view controllers
    // ResourceListController
    //     >>> ResourceController
    //         >>> ApiController
    //             >>> OperationController

    var ResourceListController = Spine.Controller.create({
        proxied: ["addAll", "addOne"],

        ApiResource: null,

        init: function() {
            if (this.baseUrl == null) {
                throw new Error("A baseUrl must be passed to ResourceListController");
            }

            $("#content_message").hide();
            $("#resources_container").hide();
            $("#resources").html("");

            // create and initialize SwaggerService
            var swaggerService = new SwaggerService(this.baseUrl, this.apiKey, function(msg) {
                if (msg)
                    messageController.showMessage(msg);
                else
                    messageController.showMessage("Rendering page...");
            });
            $("#api_host_url").html(swaggerService.apiHost());
            swaggerService.init();

            // Create convenience references to Spine models
            this.ApiResource = swaggerService.ApiResource();

            this.ApiResource.bind("refresh", this.addAll);
        },

        addAll: function() {
            this.ApiResource.each(this.addOne);
            messageController.clearMessage();
            $("#resources_container").slideDown(function() {
                setTimeout(function() {
                    Docs.shebang();
                }, 400)
            });
        },

        addOne: function(apiResource) {
            ResourceController.init({item: apiResource, container: "#resources"});
        }
    });

    var ResourceController = Spine.Controller.create({
        proxied: ["renderApi", "renderOperation"],

        templateName: "#resourceTemplate",
        apiResource: null,
        apiList: null,
        modelList: null,

        init: function() {
            this.render();

            this.apiResource = this.item;
            this.apiList = this.apiResource.apiList;
            this.modelList = this.apiResource.modelList;

//            log("------------- apiResource : " + this.apiResource.name);
//            this.apiList.logAll();
//            this.modelList.logAll();

            this.apiList.each(this.renderApi);

        },

        render: function() {
            $(this.templateName).tmpl(this.item).appendTo(this.container);
        },

        renderApi: function(api) {
            var resourceApisContainer = "#" + this.apiResource.name + "_endpoint_list";
            ApiController.init({item: api, container: resourceApisContainer});

        }

    });

    var ApiController = Spine.Controller.create({
        proxied: ["renderOperation"],

        api: null,
        templateName: "#apiTemplate",

        init: function() {
            this.render();

            this.api = this.item;

            this.api.operations.each(this.renderOperation);
        },

        render: function() {
            $(this.templateName).tmpl(this.item).appendTo(this.container);
        },

        renderOperation: function(operation) {
            var operationsContainer = "#" + this.api.name + "_endpoint_operations";
            OperationController.init({item: operation, container: operationsContainer});
        }
    });

    var OperationController = Spine.Controller.create({
        proxied: ["submitOperation", "showResponse", "showErrorStatus", "showCompleteStatus"],

        operation: null,
        templateName: "#operationTemplate",
        elementScope: "#operationTemplate",

        init: function() {
            this.render();

            this.operation = this.item;
            this.isGetOperation = (this.operation.httpMethodLowercase == "get");
            this.elementScope = "#" + this.operation.apiName + "_" + this.operation.nickname + "_" + this.operation.httpMethod;

            this.renderParams();

        },

        render: function() {
            $(this.templateName).tmpl(this.item).appendTo(this.container);
        },

        renderParams: function() {
            if (this.operation.parameters && this.operation.parameters.count() > 0) {
                var operationParamsContainer = this.elementScope + "_params";
//                log("operationParamsContainer = " + operationParamsContainer);
                for (var p = 0; p < this.operation.parameters.count(); p++) {
                    var param = this.operation.parameters.all()[p];

                    var templateName = "#paramTemplate";
                    if (param.required)
                        templateName += "Required";

                    if (!this.isGetOperation)
                        templateName += "ReadOnly";

                    $(templateName).tmpl(param).appendTo(operationParamsContainer);
//                    log("adding " + $(templateName).tmpl(param) + " TO " + operationParamsContainer);
                }
            }

            var submitButtonId = this.elementScope + "_content_sandbox_response_button";
            if (this.isGetOperation) {
                $(submitButtonId).click(this.submitOperation);
            } else {
                $(submitButtonId).hide();

                var valueHeader = this.elementScope + "_value_header";
                $(valueHeader).html("Default Value");
            }

        },

        submitOperation: function() {
            var form = $(this.elementScope + "_form");
            var error_free = true;
            var missing_input = null;

            // Cycle through the forms required inputs
            form.find("input.required").each(function() {

                // Remove any existing error styles from the input
                $(this).removeClass('error');

                // Tack the error style on if the input is empty..
                if ($(this).val() == '') {
                    if (missing_input == null)
                        missing_input = $(this);
                    $(this).addClass('error');
                    $(this).wiggle();
                    error_free = false;
                }

            });

            log("error_free = " + error_free);

            if (error_free) {
                var invocationUrl = this.operation.invocationUrl(form.serializeArray());
                $(".request_url", this.elementScope + "_content_sandbox_response").html("
" + invocationUrl + "
");
                $.getJSON(invocationUrl, this.showResponse).complete(this.showCompleteStatus).error(this.showErrorStatus);
            }

        },

        showResponse: function(response) {
//            log(response);
            var prettyJson = JSON.stringify(response, null, '\t');
//            log(prettyJson);

            $(".response_body", this.elementScope + "_content_sandbox_response").html("
" + prettyJson + "
");

            $(this.elementScope + "_content_sandbox_response").slideDown();
        },

        showErrorStatus: function(data) {
            log("error " + data.status);
            this.showStatus(data);
        },

        showCompleteStatus: function(data) {
            log("complete " + data.status);
            this.showStatus(data);
        },

        showStatus: function(data) {
            log(data);
            log(data.getAllResponseHeaders());
            $(".response_code", this.elementScope + "_content_sandbox_response").html("
" + data.status + "
");
            $(".response_headers", this.elementScope + "_content_sandbox_response").html("
" + data.getAllResponseHeaders() + "
");
        }

    });

    window.apiSelectionController = ApiSelectionController.init();
    if (this.baseUrl) {
        window.resourceListController = ResourceListController.init({baseUrl: this.baseUrl, apiKey: this.apiKey});
    } else {
        apiSelectionController.slapOn();
    }

});

<|endoftext|>


<commit_before>// Copyright 2013 Voxer IP LLC. All rights reserved.

var GO, // Global Object
    http = require("http"),
    inherits = require("util").inherits,
    EventEmitter = require("events").EventEmitter;

var MAX_COUNT = Math.pow(2, 52); // if we need more than 51 bits, wrap around at 4,503,599,627,370,495.

// PoolEndpoint - a backend that requests can be sent to
// http: either require("http") or require("https")
// ip: host ip
// port: host port
// options: {
//   ping: ping path (no ping checks)
//   ping_timeout: in ms (5000)
//   max_sockets: max concurrent open sockets (5)
//   timeout: default request timeout in ms (60000)
//   resolution: how often timeouts are checked in ms (1000)
//   keep_alive: use an alternate Agent that does keep-alive properly (boolean) default false
//   agent_ptions: {} an object for passing options directly to the Http Agent
// }
function PoolEndpoint(protocol, ip, port, options) {
    options = options || {};

    this.http = protocol;
    this.healthy = true;

    this.ip = ip;
    this.address = ip;
    this.port = port;
    this.name = this.ip + ":" + this.port;

    this.keep_alive = options.keep_alive || options.keepAlive;
    this.agent_options = options.agent_options || options.agentOptions;

    if (this.keep_alive) {
        if (protocol === http) {
            this.agent = new GO.KeepAliveAgent.HTTP(this.agent_options);
        } else {
            this.agent = new GO.KeepAliveAgent.HTTPS(this.agent_options);
        }
    } else {
        this.agent = new protocol.Agent(this.agent_options);
    }

    this.agent.maxSockets = options.max_sockets || options.maxSockets || 5;

    this.requests = {};
    this.request_count = 0;
    this.requests_last_check = 0;
    this.request_rate = 0;
    this.pending = 0;
    this.successes = 0;
    this.failures = 0;
    this.filtered = 0;

    this.timeout = (options.timeout === 0) ? 0 : options.timeout || (60 * 1000);
    this.resolution = (options.resolution === 0) ? 0 : options.resolution || 1000;
    if (this.resolution > 0 && this.timeout > 0) {
        var self = this;
        this.timeout_interval = setInterval(function () {
            self.check_timeouts();
        }, this.resolution);
    }

    // note that the pinger doesn't start by default, but in the future we might want to add an option for checking an endpoint before ever using it
    this.ping_path = options.ping;
    this.ping_timeout = options.ping_timeout || 5000;
    this.pinger = new GO.PoolPinger(this);
}
inherits(PoolEndpoint, EventEmitter);

PoolEndpoint.prototype.close = function () {
    clearInterval(this.timeout_interval);
    // No more ping-ing.
    this.ping_path = null;
    var request_ids = Object.keys(this.requests);
    for (var i = 0; i < request_ids.length; i++) {
        var req_id = request_ids[i];
        this.requests[req_id].on_aborted();
        this.delete_request(req_id);
    }

    if (this.agent && typeof(this.agent.destroy) === 'function') {
        this.agent.destroy();
    }
};

// options: {
//   timeout: request timeout in ms (this.timeout)
//   encoding: response body encoding (utf8)
//   data: string or buffer
// }
PoolEndpoint.prototype.request = function (options, callback) {
    var has_retry = !!options.retry_filter,
        req = new GO.PoolEndpointRequest(this, options, callback);

    this.update_pending();
    this.requests[req.id] = req;
    req.start();

    // If you want to retry, you can't stream.
    if (has_retry) {
        return;
    }
    return req;
};

PoolEndpoint.prototype.ready = function () {
    if (! this.healthy) {
        return false; // unhealthy endpoints are never ready
    }
    if (this.keep_alive) {
        // if we are doing keep_alive and we have more sockets than active requests, we are ready
        if (this.agent.sockets[this.name] && this.agent.sockets[this.name].length > this.pending) {
            return true;
        } else {
            return false;
        }
    }
    // we are ready if we currently have nothing to do
    return this.pending === 0;
};

PoolEndpoint.prototype.stats = function () {
    var socket_keys = Object.keys(this.agent.sockets);
    var request_counts = [];
    for (var i = 0; i < socket_keys.length; i++) {
        var name = socket_keys[i];
        var s = this.agent.sockets[name] || [];
        for (var j = 0; j < s.length; j++) {
            request_counts.push(s[j]._request_count || 1);
        }
    }
    return {
        name: this.name,
        request_count: this.request_count,
        request_rate: this.request_rate,
        pending: this.pending,
        successes: this.successes,
        failures: this.failures,
        filtered: this.filtered,
        healthy: this.healthy,
        socket_count: this.agent.sockets[this.name] ? this.agent.sockets[this.name].length : 0,
        socket_request_counts: request_counts
    };
};

PoolEndpoint.prototype.check_timeouts = function () {
    var now = Date.now(); // only run Date.now() once per check interval
    var request_keys = Object.keys(this.requests);
    for (var i = 0; i < request_keys.length; i++) {
        var request = this.requests[request_keys[i]];
        var expire_time = now - request.options.timeout;

        if (request.last_touched <= expire_time) {
            if (request.options.path !== this.ping_path) {
                this.emit("timeout", request);
            }
            request.timed_out = true;
            request.out_request.abort();
            this.delete_request(request_keys[i]);
        }
    }
    this.request_rate = this.request_count - this.requests_last_check;
    this.requests_last_check = this.request_count;
};

PoolEndpoint.prototype.reset_counters = function () {
    this.requests_last_check = this.request_rate - this.pending;
    this.request_count = this.pending;
    this.successes = 0;
    this.failures = 0;
    this.filtered = 0;
};

PoolEndpoint.prototype.update_pending = function () {
    this.pending = this.request_count - (this.successes + this.failures + this.filtered);
    if (this.request_count === MAX_COUNT) {
        this.reset_counters();
    }
};

PoolEndpoint.prototype.complete = function (err, request, response, body) {
    this.delete_request(request.id);
    this.update_pending();
    request.done(err, response, body);
};

PoolEndpoint.prototype.request_succeeded = function (request, response, body) {
    this.successes++;
    this.complete(null, request, response, body);
};

PoolEndpoint.prototype.request_failed = function (err, request) {
    this.failures++;
    if (!request.destroyed) {
        this.set_healthy(false);
    }
    this.complete(err, request);
};

PoolEndpoint.prototype.filter_rejected = function (err, request) {
    this.filtered++;
    this.complete(err, request);
};

PoolEndpoint.prototype.busyness = function () {
    return this.pending;
};

PoolEndpoint.prototype.set_healthy = function (new_state) {
    if (! this.ping_path) {
        return; // an endpoint with no pingPath can never be made unhealthy
    }
    if (! new_state) {
        this.pinger.start();
    }
    if (this.healthy !== new_state) {
        this.healthy = new_state;
        this.emit("health", this);
    }
};

PoolEndpoint.prototype.setHealthy = PoolEndpoint.prototype.set_healthy;

PoolEndpoint.prototype.delete_request = function (id) {
    delete this.requests[id];
};

module.exports = function init(new_global) {
    GO = new_global;

    return PoolEndpoint;
};
<commit_msg>optimize request iteration in pool_endpoint<commit_after>// Copyright 2013 Voxer IP LLC. All rights reserved.

var GO, // Global Object
    http = require("http"),
    inherits = require("util").inherits,
    EventEmitter = require("events").EventEmitter;

var MAX_COUNT = Math.pow(2, 52); // if we need more than 51 bits, wrap around at 4,503,599,627,370,495.

// PoolEndpoint - a backend that requests can be sent to
// http: either require("http") or require("https")
// ip: host ip
// port: host port
// options: {
//   ping: ping path (no ping checks)
//   ping_timeout: in ms (5000)
//   max_sockets: max concurrent open sockets (5)
//   timeout: default request timeout in ms (60000)
//   resolution: how often timeouts are checked in ms (1000)
//   keep_alive: use an alternate Agent that does keep-alive properly (boolean) default false
//   agent_ptions: {} an object for passing options directly to the Http Agent
// }
function PoolEndpoint(protocol, ip, port, options) {
    options = options || {};

    this.http = protocol;
    this.healthy = true;

    this.ip = ip;
    this.address = ip;
    this.port = port;
    this.name = this.ip + ":" + this.port;

    this.keep_alive = options.keep_alive || options.keepAlive;
    this.agent_options = options.agent_options || options.agentOptions;

    if (this.keep_alive) {
        if (protocol === http) {
            this.agent = new GO.KeepAliveAgent.HTTP(this.agent_options);
        } else {
            this.agent = new GO.KeepAliveAgent.HTTPS(this.agent_options);
        }
    } else {
        this.agent = new protocol.Agent(this.agent_options);
    }

    this.agent.maxSockets = options.max_sockets || options.maxSockets || 5;

    this.requests = Object.create(null);
    this.request_count = 0;
    this.requests_last_check = 0;
    this.request_rate = 0;
    this.pending = 0;
    this.successes = 0;
    this.failures = 0;
    this.filtered = 0;

    this.timeout = (options.timeout === 0) ? 0 : options.timeout || (60 * 1000);
    this.resolution = (options.resolution === 0) ? 0 : options.resolution || 1000;
    if (this.resolution > 0 && this.timeout > 0) {
        var self = this;
        this.timeout_interval = setInterval(function () {
            self.check_timeouts();
        }, this.resolution);
    }

    // note that the pinger doesn't start by default, but in the future we might want to add an option for checking an endpoint before ever using it
    this.ping_path = options.ping;
    this.ping_timeout = options.ping_timeout || 5000;
    this.pinger = new GO.PoolPinger(this);
}
inherits(PoolEndpoint, EventEmitter);

PoolEndpoint.prototype.close = function () {
    clearInterval(this.timeout_interval);
    // No more ping-ing.
    this.ping_path = null;
    var request_ids = Object.keys(this.requests);
    for (var i = 0; i < request_ids.length; i++) {
        var req_id = request_ids[i];
        this.requests[req_id].on_aborted();
        this.delete_request(req_id);
    }

    if (this.agent && typeof(this.agent.destroy) === 'function') {
        this.agent.destroy();
    }
};

// options: {
//   timeout: request timeout in ms (this.timeout)
//   encoding: response body encoding (utf8)
//   data: string or buffer
// }
PoolEndpoint.prototype.request = function (options, callback) {
    var has_retry = !!options.retry_filter,
        req = new GO.PoolEndpointRequest(this, options, callback);

    this.update_pending();
    this.requests[req.id] = req;
    req.start();

    // If you want to retry, you can't stream.
    if (has_retry) {
        return;
    }
    return req;
};

PoolEndpoint.prototype.ready = function () {
    if (! this.healthy) {
        return false; // unhealthy endpoints are never ready
    }
    if (this.keep_alive) {
        // if we are doing keep_alive and we have more sockets than active requests, we are ready
        if (this.agent.sockets[this.name] && this.agent.sockets[this.name].length > this.pending) {
            return true;
        } else {
            return false;
        }
    }
    // we are ready if we currently have nothing to do
    return this.pending === 0;
};

PoolEndpoint.prototype.stats = function () {
    var socket_keys = Object.keys(this.agent.sockets);
    var request_counts = [];
    for (var i = 0; i < socket_keys.length; i++) {
        var name = socket_keys[i];
        var s = this.agent.sockets[name] || [];
        for (var j = 0; j < s.length; j++) {
            request_counts.push(s[j]._request_count || 1);
        }
    }
    return {
        name: this.name,
        request_count: this.request_count,
        request_rate: this.request_rate,
        pending: this.pending,
        successes: this.successes,
        failures: this.failures,
        filtered: this.filtered,
        healthy: this.healthy,
        socket_count: this.agent.sockets[this.name] ? this.agent.sockets[this.name].length : 0,
        socket_request_counts: request_counts
    };
};

PoolEndpoint.prototype.check_timeouts = function () {
    var now = Date.now(); // only run Date.now() once per check interval
    var requests = this.requests;
    var delete_array = [];
    for (var req_id in requests) {
        var request = requests[req_id];
        var expire_time = now - request.options.timeout;

        if (request.last_touched <= expire_time) {
            if (request.options.path !== this.ping_path) {
                this.emit("timeout", request);
            }
            request.timed_out = true;
            request.out_request.abort();
            delete_array.push(req_id);
        }
    }
    for (var i = 0; i < delete_array.length; i++) {
        this.delete_request(delete_array[i]);
    }
    this.request_rate = this.request_count - this.requests_last_check;
    this.requests_last_check = this.request_count;
};

PoolEndpoint.prototype.reset_counters = function () {
    this.requests_last_check = this.request_rate - this.pending;
    this.request_count = this.pending;
    this.successes = 0;
    this.failures = 0;
    this.filtered = 0;
};

PoolEndpoint.prototype.update_pending = function () {
    this.pending = this.request_count - (this.successes + this.failures + this.filtered);
    if (this.request_count === MAX_COUNT) {
        this.reset_counters();
    }
};

PoolEndpoint.prototype.complete = function (err, request, response, body) {
    this.delete_request(request.id);
    this.update_pending();
    request.done(err, response, body);
};

PoolEndpoint.prototype.request_succeeded = function (request, response, body) {
    this.successes++;
    this.complete(null, request, response, body);
};

PoolEndpoint.prototype.request_failed = function (err, request) {
    this.failures++;
    if (!request.destroyed) {
        this.set_healthy(false);
    }
    this.complete(err, request);
};

PoolEndpoint.prototype.filter_rejected = function (err, request) {
    this.filtered++;
    this.complete(err, request);
};

PoolEndpoint.prototype.busyness = function () {
    return this.pending;
};

PoolEndpoint.prototype.set_healthy = function (new_state) {
    if (! this.ping_path) {
        return; // an endpoint with no pingPath can never be made unhealthy
    }
    if (! new_state) {
        this.pinger.start();
    }
    if (this.healthy !== new_state) {
        this.healthy = new_state;
        this.emit("health", this);
    }
};

PoolEndpoint.prototype.setHealthy = PoolEndpoint.prototype.set_healthy;

PoolEndpoint.prototype.delete_request = function (id) {
    delete this.requests[id];
};

module.exports = function init(new_global) {
    GO = new_global;

    return PoolEndpoint;
};
<|endoftext|>


<commit_before>/**
 * @file wow command
 * @author James Naylor (a.k.a euronay)
 * @license GPL-3.0
 */

const request = xrequire('request-promise-native');

const wowConstants = {
  faction: [
    'Alliance',
    'Horde'
  ],
  gender: [
    'None',
    'Male',
    'Female'
  ],
  race: [
    'None',
    'Human',
    'Orc',
    'Dwarf',
    'Night Elf',
    'Undead',
    'Tauren',
    'Gnome',
    'Troll',
    'Goblin',
    'Blood Elf',
    'Draenei',
    'Fel Orc',
    'Naga',
    'Broken',
    'Skeleton',
    'Vrykul',
    'Tuskarr',
    'Forest Troll',
    'Taunka',
    'Northrend Skeleton',
    'Ice Troll',
    'Worgen',
    'Gilnean',
    'Pandaren',
    'Pandaren',
    'Pandaren',
    'Nightborn',
    'Highmountain Tauren',
    'Void Elf',
    'Lightforged Draenei',
    'Zandalari Troll',
    'Kul Tiran',
    'Human',
    'Dark Iron Dwarf',
    'Vulpera',
    'Mag\'har Orc'
  ],
  class: [
    'None',
    'Warrior',
    'Paladin',
    'Hunter',
    'Rogue',
    'Priest',
    'Death Knight',
    'Shaman',
    'Mage',
    'Warlock',
    'Druid',
    'Demon Hunter'
  ]
};

exports.exec = async (Bastion, message, args) => {
  try {
    if (!args.character || !args.realm) {
      /**
       * The command was ran with invalid parameters.
       * @fires commandUsage
       */
      return Bastion.emit('commandUsage', message, this.help);
    }

    let locale = args.region === 'us' ? 'en-us' : 'en-gb';

    let requestUrl = `https://${args.region}.api.battle.net/wow/character/${args.realm}/${args.character}?fields=guild,items&locale=${locale}&apikey=${Bastion.credentials.battleNetApiKey}`;

    let options = {
      url: requestUrl,
      json: true
    };

    let response = await request(options);

    let factionIcon = `https://worldofwarcraft.akamaized.net/static/components/Logo/Logo-${response.faction === 0 ? 'alliance' : 'horde'}.png`;

    let avatar = `https://render-${args.region}.worldofwarcraft.com/character/${response.thumbnail}`;

    let stats = [
      {
        name: 'Character',
        value: `${response.level} ${wowConstants.race[response.race]} ${wowConstants.class[response.class]}`
      },
      {
        name: 'Achievement Points',
        value: response.achievementPoints
      },
      {
        name: 'Item Level',
        value: response.items.averageItemLevel
      }
    ];

    if (response.guild) {
      stats.push(
        {
          name: 'Guild',
          value: response.guild.name
        }
      );
    }

    message.channel.send({
      embed: {
        author: {
          name: response.name,
          url: `https://worldofwarcraft.com/${locale}/character/${args.realm}/${args.character}`,
          icon_url: factionIcon
        },
        fields: stats,
        thumbnail: {
          url: avatar
        }
      }
    }).catch(e => {
      Bastion.log.error(e);
    });

  }
  catch (e) {
    if (e.name === 'StatusCodeError') {
      if (e.statusCode === '404') {
        return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'notFound', 'player'), message.channel);
      }
      return Bastion.emit('error', e.statusCode, e.error.message, message.channel);
    }
    Bastion.log.error(e);
  }
};

exports.config = {
  aliases: [],
  enabled: true,
  argsDefinitions: [
    { name: 'character', type: String, defaultOption: true },
    { name: 'realm', type: String, alias: 'r' },
    { name: 'region', type: String, alias: 'e', defaultValue: 'us' }
  ]
};

exports.help = {
  name: 'wow',
  description: 'Get stats of any World of Warcraft character from the Armory.',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'wow <character> <realm> [-r <region>]',
  example: [
    'wow Fallken -r Burning-Blade -e eu '
  ]
};
<commit_msg>commands: update wow command<commit_after>/**
 * @file wow command
 * @author James Naylor (a.k.a euronay)
 * @license GPL-3.0
 */

const request = xrequire('request-promise-native');

const wowConstants = {
  faction: [
    'Alliance',
    'Horde'
  ],
  gender: [
    'None',
    'Male',
    'Female'
  ],
  race: [
    'None',
    'Human',
    'Orc',
    'Dwarf',
    'Night Elf',
    'Undead',
    'Tauren',
    'Gnome',
    'Troll',
    'Goblin',
    'Blood Elf',
    'Draenei',
    'Fel Orc',
    'Naga',
    'Broken',
    'Skeleton',
    'Vrykul',
    'Tuskarr',
    'Forest Troll',
    'Taunka',
    'Northrend Skeleton',
    'Ice Troll',
    'Worgen',
    'Gilnean',
    'Pandaren',
    'Pandaren',
    'Pandaren',
    'Nightborn',
    'Highmountain Tauren',
    'Void Elf',
    'Lightforged Draenei',
    'Zandalari Troll',
    'Kul Tiran',
    'Human',
    'Dark Iron Dwarf',
    'Vulpera',
    'Mag\'har Orc'
  ],
  class: [
    'None',
    'Warrior',
    'Paladin',
    'Hunter',
    'Rogue',
    'Priest',
    'Death Knight',
    'Shaman',
    'Mage',
    'Warlock',
    'Druid',
    'Demon Hunter'
  ]
};

exports.exec = async (Bastion, message, args) => {
  try {
    if (!args.character || !args.realm) {
      /**
       * The command was ran with invalid parameters.
       * @fires commandUsage
       */
      return Bastion.emit('commandUsage', message, this.help);
    }

    let locale = args.region === 'us' ? 'en-us' : 'en-gb';

    let requestUrl = `https://${args.region}.api.battle.net/wow/character/${args.realm}/${args.character}?fields=guild,items&locale=${locale}&apikey=${Bastion.credentials.battleNetApiKey}`;

    let options = {
      url: requestUrl,
      json: true
    };

    let response = await request(options);

    let factionIcon = `https://worldofwarcraft.akamaized.net/static/components/Logo/Logo-${response.faction === 0 ? 'alliance' : 'horde'}.png`;

    let avatar = `https://render-${args.region}.worldofwarcraft.com/character/${response.thumbnail}`;

    let stats = [
      {
        name: 'Character',
        value: `${response.level} ${wowConstants.race[response.race]} ${wowConstants.class[response.class]}`
      },
      {
        name: 'Achievement Points',
        value: response.achievementPoints
      },
      {
        name: 'Item Level',
        value: response.items.averageItemLevel
      }
    ];

    if (response.guild) {
      stats.push(
        {
          name: 'Guild',
          value: response.guild.name
        }
      );
    }

    message.channel.send({
      embed: {
        author: {
          name: response.name,
          url: `https://worldofwarcraft.com/${locale}/character/${args.realm}/${args.character}`,
          icon_url: factionIcon
        },
        fields: stats,
        thumbnail: {
          url: avatar
        }
      }
    }).catch(e => {
      Bastion.log.error(e);
    });

  }
  catch (e) {
    if (e.name === 'StatusCodeError') {
      if (e.statusCode === '404') {
        return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'notFound', 'player'), message.channel);
      }
      return Bastion.emit('error', e.statusCode, e.error.message, message.channel);
    }
    Bastion.log.error(e);
  }
};

exports.config = {
  aliases: [],
  enabled: true,
  argsDefinitions: [
    { name: 'character', type: String, defaultOption: true },
    { name: 'realm', type: String, alias: 'r' },
    { name: 'region', type: String, defaultValue: 'us' }
  ]
};

exports.help = {
  name: 'wow',
  description: 'Get stats of any World of Warcraft character from the Armory.',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'wow <CHARACTER> <--realm REALM> [-region <REGION>]',
  example: [
    'wow Fallken --realm Burning-Blade --region us'
  ]
};
<|endoftext|>


<commit_before>// RockUp
// Commands-StartStop -- Cycle service state within target environment

var Config = require('../lib/Config');

var _ = require('underscore');
var inspect = require('util').inspect;
var Spinner = require('clui').Spinner;

module.exports = StartStopCommand;

function StartStopCommand (program) {

  _.each(["start", "stop", "restart"], function(command) {
    program
      .command(command+" <environment>")
      .option("--host <name>", "The specific host to target")
      .action( function(env, cliOptions) {
        var config = Config._loadLocalConfigFile(env);
        var hosts;
        if (cliOptions && cliOptions.host)
          hosts = [config.hosts.get(cliOptions.host)];
        else
          hosts = config.hosts.list;

        var gerund = { 'start': 'Starting', 'stop': 'Stopping', 'restart': 'Restarting'}[command];
        var spinner = new Spinner(gerund+' services on '+hosts.length+' host(s)...');
        spinner.start();

        var remain = hosts.length;
        var hostMap = {};
        _.each(hosts, function(host) {
          host[command]( function(result) {
            --remain;
            spinner.message(gerund+' services on '+remain+' host(s)...');
            hostMap[host.name] = result;
            if (remain === 0) {
              spinner.stop();
              console.log("");
              _.each(hostMap, function(result, hostName) {
                console.log(hostName.bold, "\t", inspect(result, {colors:true}).replace("\n", " ") );
              });
              console.log("");
              process.exit(0);
            }
          });
        });

      });
  });

  return program;
}
<commit_msg>Describe lifecycle actions in CLI help<commit_after>// RockUp
// Commands-StartStop -- Cycle service state within target environment

var Config = require('../lib/Config');

var _ = require('underscore');
var inspect = require('util').inspect;
var Spinner = require('clui').Spinner;

module.exports = StartStopCommand;

function StartStopCommand (program) {

  _.each(["start", "stop", "restart"], function(command) {
    program
      .command(command+" <environment>")
      .description("On host, "+command+" services running this app")
      .option("--host <name>", "The specific host to target")
      .action( function(env, cliOptions) {
        var config = Config._loadLocalConfigFile(env);
        var hosts;
        if (cliOptions && cliOptions.host)
          hosts = [config.hosts.get(cliOptions.host)];
        else
          hosts = config.hosts.list;

        var gerund = { 'start': 'Starting', 'stop': 'Stopping', 'restart': 'Restarting'}[command];
        var spinner = new Spinner(gerund+' services on '+hosts.length+' host(s)...');
        spinner.start();

        var remain = hosts.length;
        var hostMap = {};
        _.each(hosts, function(host) {
          host[command]( function(result) {
            --remain;
            spinner.message(gerund+' services on '+remain+' host(s)...');
            hostMap[host.name] = result;
            if (remain === 0) {
              spinner.stop();
              console.log("");
              _.each(hostMap, function(result, hostName) {
                console.log(hostName.bold, "\t", inspect(result, {colors:true}).replace("\n", " ") );
              });
              console.log("");
              process.exit(0);
            }
          });
        });

      });
  });

  return program;
}
<|endoftext|>