<commit_before>/**
 * @module ol/control/FullScreen
 */
import Control from './Control.js';
import EventType from '../events/EventType.js';
import MapProperty from '../MapProperty.js';
import {CLASS_CONTROL, CLASS_UNSELECTABLE, CLASS_UNSUPPORTED} from '../css.js';
import {listen, unlistenByKey} from '../events.js';
import {replaceNode} from '../dom.js';

const events = [
  'fullscreenchange',
  'webkitfullscreenchange',
  'MSFullscreenChange',
];

/**
 * @enum {string}
 */
const FullScreenEventType = {
  /**
   * Triggered after the map entered fullscreen.
   * @event FullScreenEventType#enterfullscreen
   * @api
   */
  ENTERFULLSCREEN: 'enterfullscreen',

  /**
   * Triggered after the map leave fullscreen.
   * @event FullScreenEventType#leavefullscreen
   * @api
   */
  LEAVEFULLSCREEN: 'leavefullscreen',
};

/***
 * @template Return
 * @typedef {import("../Observable").OnSignature<import("../Observable").EventTypes|
 *     'enterfullscreen'|'leavefullscreen', import("../events/Event.js").default, Return> &
 *   import("../Observable").OnSignature<import("../ObjectEventType").Types, import("../Object").ObjectEvent, Return> &
 *   import("../Observable").CombinedOnSignature<import("../Observable").EventTypes|
 *     'enterfullscreen'|'leavefullscreen'|import("../ObjectEventType").Types, Return>} FullScreenOnSignature
 */

/**
 * @typedef {Object} Options
 * @property {string} [className='ol-full-screen'] CSS class name.
 * @property {string|Text|HTMLElement} [label='\u2922'] Text label to use for the button.
 * Instead of text, also an element (e.g. a `span` element) can be used.
 * @property {string|Text|HTMLElement} [labelActive='\u00d7'] Text label to use for the
 * button when full-screen is active.
 * Instead of text, also an element (e.g. a `span` element) can be used.
 * @property {string} [activeClassName=className + '-true'] CSS class name for the button
 * when full-screen is active.
 * @property {string} [inactiveClassName=className + '-false'] CSS class name for the button
 * when full-screen is inactive.
 * @property {string} [tipLabel='Toggle full-screen'] Text label to use for the button tip.
 * @property {boolean} [keys=false] Full keyboard access.
 * @property {HTMLElement|string} [target] Specify a target if you want the
 * control to be rendered outside of the map's viewport.
 * @property {HTMLElement|string} [source] The element to be displayed
 * fullscreen. When not provided, the element containing the map viewport will
 * be displayed fullscreen.
 */

/**
 * @classdesc
 * Provides a button that when clicked fills up the full screen with the map.
 * The full screen source element is by default the element containing the map viewport unless
 * overridden by providing the `source` option. In which case, the dom
 * element introduced using this parameter will be displayed in full screen.
 *
 * When in full screen mode, a close button is shown to exit full screen mode.
 * The [Fullscreen API](https://www.w3.org/TR/fullscreen/) is used to
 * toggle the map in full screen mode.
 *
 * @fires FullScreenEventType#enterfullscreen
 * @fires FullScreenEventType#leavefullscreen
 * @api
 */
class FullScreen extends Control {
  /**
   * @param {Options} [opt_options] Options.
   */
  constructor(opt_options) {
    const options = opt_options ? opt_options : {};

    super({
      element: document.createElement('div'),
      target: options.target,
    });

    /***
     * @type {FullScreenOnSignature<import("../events").EventsKey>}
     */
    this.on;

    /***
     * @type {FullScreenOnSignature<import("../events").EventsKey>}
     */
    this.once;

    /***
     * @type {FullScreenOnSignature<void>}
     */
    this.un;

    /**
     * @private
     * @type {boolean}
     */
    this.keys_ = options.keys !== undefined ? options.keys : false;

    /**
     * @private
     * @type {HTMLElement|string|undefined}
     */
    this.source_ = options.source;

    /**
     * @type {boolean}
     * @private
     */
    this.isInFullscreen_ = false;

    /**
     * @private
     */
    this.boundHandleMapTargetChange_ = this.handleMapTargetChange_.bind(this);

    /**
     * @private
     * @type {string}
     */
    this.cssClassName_ =
      options.className !== undefined ? options.className : 'ol-full-screen';

    /**
     * @private
     * @type {Array<import("../events.js").EventsKey>}
     */
    this.documentListeners_ = [];

    /**
     * @private
     * @type {Array<string>}
     */
    this.activeClassName_ =
      options.activeClassName !== undefined
        ? options.activeClassName.split(' ')
        : [this.cssClassName_ + '-true'];

    /**
     * @private
     * @type {Array<string>}
     */
    this.inactiveClassName_ =
      options.inactiveClassName !== undefined
        ? options.inactiveClassName.split(' ')
        : [this.cssClassName_ + '-false'];

    const label = options.label !== undefined ? options.label : '\u2922';

    /**
     * @private
     * @type {Text|HTMLElement}
     */
    this.labelNode_ =
      typeof label === 'string' ? document.createTextNode(label) : label;

    const labelActive =
      options.labelActive !== undefined ? options.labelActive : '\u00d7';

    /**
     * @private
     * @type {Text|HTMLElement}
     */
    this.labelActiveNode_ =
      typeof labelActive === 'string'
        ? document.createTextNode(labelActive)
        : labelActive;

    const tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';

    /**
     * @private
     * @type {HTMLElement}
     */
    this.button_ = document.createElement('button');
    this.button_.title = tipLabel;
    this.button_.setAttribute('type', 'button');
    this.button_.appendChild(this.labelNode_);
    this.button_.addEventListener(
      EventType.CLICK,
      this.handleClick_.bind(this),
      false
    );
    this.setClassName_(this.button_, this.isInFullscreen_);

    this.element.className = `${this.cssClassName_} ${CLASS_UNSELECTABLE} ${CLASS_CONTROL}`;
    this.element.appendChild(this.button_);
  }

  /**
   * @param {MouseEvent} event The event to handle
   * @private
   */
  handleClick_(event) {
    event.preventDefault();
    this.handleFullScreen_();
  }

  /**
   * @private
   */
  handleFullScreen_() {
    const map = this.getMap();
    if (!map) {
      return;
    }
    const doc = map.getOwnerDocument();
    if (!isFullScreenSupported(doc)) {
      return;
    }
    if (isFullScreen(doc)) {
      exitFullScreen(doc);
    } else {
      let element;
      if (this.source_) {
        element =
          typeof this.source_ === 'string'
            ? doc.getElementById(this.source_)
            : this.source_;
      } else {
        element = map.getTargetElement();
      }
      if (this.keys_) {
        requestFullScreenWithKeys(element);
      } else {
        requestFullScreen(element);
      }
    }
  }

  /**
   * @private
   */
  handleFullScreenChange_() {
    const map = this.getMap();
    if (!map) {
      return;
    }
    const wasInFullscreen = this.isInFullscreen_;
    this.isInFullscreen_ = isFullScreen(map.getOwnerDocument());
    if (wasInFullscreen !== this.isInFullscreen_) {
      this.setClassName_(this.button_, this.isInFullscreen_);
      if (this.isInFullscreen_) {
        replaceNode(this.labelActiveNode_, this.labelNode_);
        this.dispatchEvent(FullScreenEventType.ENTERFULLSCREEN);
      } else {
        replaceNode(this.labelNode_, this.labelActiveNode_);
        this.dispatchEvent(FullScreenEventType.LEAVEFULLSCREEN);
      }
      map.updateSize();
    }
  }

  /**
   * @param {HTMLElement} element Target element
   * @param {boolean} fullscreen True if fullscreen class name should be active
   * @private
   */
  setClassName_(element, fullscreen) {
    if (fullscreen) {
      element.classList.remove(...this.inactiveClassName_);
      element.classList.add(...this.activeClassName_);
    } else {
      element.classList.remove(...this.activeClassName_);
      element.classList.add(...this.inactiveClassName_);
    }
  }

  /**
   * Remove the control from its current map and attach it to the new map.
   * Pass `null` to just remove the control from the current map.
   * Subclasses may set up event handlers to get notified about changes to
   * the map here.
   * @param {import("../PluggableMap.js").default|null} map Map.
   * @api
   */
  setMap(map) {
    const oldMap = this.getMap();
    if (oldMap) {
      oldMap.removeChangeListener(
        MapProperty.TARGET,
        this.boundHandleMapTargetChange_
      );
    }

    super.setMap(map);

    this.handleMapTargetChange_();
    if (map) {
      map.addChangeListener(
        MapProperty.TARGET,
        this.boundHandleMapTargetChange_
      );
    }
  }

  /**
   * @private
   */
  handleMapTargetChange_() {
    const listeners = this.documentListeners_;
    for (let i = 0, ii = listeners.length; i < ii; ++i) {
      unlistenByKey(listeners[i]);
    }
    listeners.length = 0;

    const map = this.getMap();
    if (map) {
      const doc = map.getOwnerDocument();
      if (isFullScreenSupported(doc)) {
        this.element.classList.remove(CLASS_UNSUPPORTED);
      } else {
        this.element.classList.add(CLASS_UNSUPPORTED);
      }

      for (let i = 0, ii = events.length; i < ii; ++i) {
        listeners.push(
          listen(doc, events[i], this.handleFullScreenChange_, this)
        );
      }
      this.handleFullScreenChange_();
    }
  }
}

/**
 * @param {Document} doc The root document to check.
 * @return {boolean} Fullscreen is supported by the current platform.
 */
function isFullScreenSupported(doc) {
  const body = doc.body;
  return !!(
    body['webkitRequestFullscreen'] ||
    (body['msRequestFullscreen'] && doc['msFullscreenEnabled']) ||
    (body.requestFullscreen && doc.fullscreenEnabled)
  );
}

/**
 * @param {Document} doc The root document to check.
 * @return {boolean} Element is currently in fullscreen.
 */
function isFullScreen(doc) {
  return !!(
    doc['webkitIsFullScreen'] ||
    doc['msFullscreenElement'] ||
    doc.fullscreenElement
  );
}

/**
 * Request to fullscreen an element.
 * @param {HTMLElement} element Element to request fullscreen
 */
function requestFullScreen(element) {
  if (element.requestFullscreen) {
    element.requestFullscreen();
  } else if (element['msRequestFullscreen']) {
    element['msRequestFullscreen']();
  } else if (element['webkitRequestFullscreen']) {
    element['webkitRequestFullscreen']();
  }
}

/**
 * Request to fullscreen an element with keyboard input.
 * @param {HTMLElement} element Element to request fullscreen
 */
function requestFullScreenWithKeys(element) {
  if (element['webkitRequestFullscreen']) {
    element['webkitRequestFullscreen']();
  } else {
    requestFullScreen(element);
  }
}

/**
 * Exit fullscreen.
 * @param {Document} doc The document to exit fullscren from
 */
function exitFullScreen(doc) {
  if (doc.exitFullscreen) {
    doc.exitFullscreen();
  } else if (doc['msExitFullscreen']) {
    doc['msExitFullscreen']();
  } else if (doc['webkitExitFullscreen']) {
    doc['webkitExitFullscreen']();
  }
}

export default FullScreen;
<commit_msg>Remove fullscreen handling fallback for IE<commit_after>/**
 * @module ol/control/FullScreen
 */
import Control from './Control.js';
import EventType from '../events/EventType.js';
import MapProperty from '../MapProperty.js';
import {CLASS_CONTROL, CLASS_UNSELECTABLE, CLASS_UNSUPPORTED} from '../css.js';
import {listen, unlistenByKey} from '../events.js';
import {replaceNode} from '../dom.js';

const events = [
  'fullscreenchange',
  'webkitfullscreenchange',
  'MSFullscreenChange',
];

/**
 * @enum {string}
 */
const FullScreenEventType = {
  /**
   * Triggered after the map entered fullscreen.
   * @event FullScreenEventType#enterfullscreen
   * @api
   */
  ENTERFULLSCREEN: 'enterfullscreen',

  /**
   * Triggered after the map leave fullscreen.
   * @event FullScreenEventType#leavefullscreen
   * @api
   */
  LEAVEFULLSCREEN: 'leavefullscreen',
};

/***
 * @template Return
 * @typedef {import("../Observable").OnSignature<import("../Observable").EventTypes|
 *     'enterfullscreen'|'leavefullscreen', import("../events/Event.js").default, Return> &
 *   import("../Observable").OnSignature<import("../ObjectEventType").Types, import("../Object").ObjectEvent, Return> &
 *   import("../Observable").CombinedOnSignature<import("../Observable").EventTypes|
 *     'enterfullscreen'|'leavefullscreen'|import("../ObjectEventType").Types, Return>} FullScreenOnSignature
 */

/**
 * @typedef {Object} Options
 * @property {string} [className='ol-full-screen'] CSS class name.
 * @property {string|Text|HTMLElement} [label='\u2922'] Text label to use for the button.
 * Instead of text, also an element (e.g. a `span` element) can be used.
 * @property {string|Text|HTMLElement} [labelActive='\u00d7'] Text label to use for the
 * button when full-screen is active.
 * Instead of text, also an element (e.g. a `span` element) can be used.
 * @property {string} [activeClassName=className + '-true'] CSS class name for the button
 * when full-screen is active.
 * @property {string} [inactiveClassName=className + '-false'] CSS class name for the button
 * when full-screen is inactive.
 * @property {string} [tipLabel='Toggle full-screen'] Text label to use for the button tip.
 * @property {boolean} [keys=false] Full keyboard access.
 * @property {HTMLElement|string} [target] Specify a target if you want the
 * control to be rendered outside of the map's viewport.
 * @property {HTMLElement|string} [source] The element to be displayed
 * fullscreen. When not provided, the element containing the map viewport will
 * be displayed fullscreen.
 */

/**
 * @classdesc
 * Provides a button that when clicked fills up the full screen with the map.
 * The full screen source element is by default the element containing the map viewport unless
 * overridden by providing the `source` option. In which case, the dom
 * element introduced using this parameter will be displayed in full screen.
 *
 * When in full screen mode, a close button is shown to exit full screen mode.
 * The [Fullscreen API](https://www.w3.org/TR/fullscreen/) is used to
 * toggle the map in full screen mode.
 *
 * @fires FullScreenEventType#enterfullscreen
 * @fires FullScreenEventType#leavefullscreen
 * @api
 */
class FullScreen extends Control {
  /**
   * @param {Options} [opt_options] Options.
   */
  constructor(opt_options) {
    const options = opt_options ? opt_options : {};

    super({
      element: document.createElement('div'),
      target: options.target,
    });

    /***
     * @type {FullScreenOnSignature<import("../events").EventsKey>}
     */
    this.on;

    /***
     * @type {FullScreenOnSignature<import("../events").EventsKey>}
     */
    this.once;

    /***
     * @type {FullScreenOnSignature<void>}
     */
    this.un;

    /**
     * @private
     * @type {boolean}
     */
    this.keys_ = options.keys !== undefined ? options.keys : false;

    /**
     * @private
     * @type {HTMLElement|string|undefined}
     */
    this.source_ = options.source;

    /**
     * @type {boolean}
     * @private
     */
    this.isInFullscreen_ = false;

    /**
     * @private
     */
    this.boundHandleMapTargetChange_ = this.handleMapTargetChange_.bind(this);

    /**
     * @private
     * @type {string}
     */
    this.cssClassName_ =
      options.className !== undefined ? options.className : 'ol-full-screen';

    /**
     * @private
     * @type {Array<import("../events.js").EventsKey>}
     */
    this.documentListeners_ = [];

    /**
     * @private
     * @type {Array<string>}
     */
    this.activeClassName_ =
      options.activeClassName !== undefined
        ? options.activeClassName.split(' ')
        : [this.cssClassName_ + '-true'];

    /**
     * @private
     * @type {Array<string>}
     */
    this.inactiveClassName_ =
      options.inactiveClassName !== undefined
        ? options.inactiveClassName.split(' ')
        : [this.cssClassName_ + '-false'];

    const label = options.label !== undefined ? options.label : '\u2922';

    /**
     * @private
     * @type {Text|HTMLElement}
     */
    this.labelNode_ =
      typeof label === 'string' ? document.createTextNode(label) : label;

    const labelActive =
      options.labelActive !== undefined ? options.labelActive : '\u00d7';

    /**
     * @private
     * @type {Text|HTMLElement}
     */
    this.labelActiveNode_ =
      typeof labelActive === 'string'
        ? document.createTextNode(labelActive)
        : labelActive;

    const tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';

    /**
     * @private
     * @type {HTMLElement}
     */
    this.button_ = document.createElement('button');
    this.button_.title = tipLabel;
    this.button_.setAttribute('type', 'button');
    this.button_.appendChild(this.labelNode_);
    this.button_.addEventListener(
      EventType.CLICK,
      this.handleClick_.bind(this),
      false
    );
    this.setClassName_(this.button_, this.isInFullscreen_);

    this.element.className = `${this.cssClassName_} ${CLASS_UNSELECTABLE} ${CLASS_CONTROL}`;
    this.element.appendChild(this.button_);
  }

  /**
   * @param {MouseEvent} event The event to handle
   * @private
   */
  handleClick_(event) {
    event.preventDefault();
    this.handleFullScreen_();
  }

  /**
   * @private
   */
  handleFullScreen_() {
    const map = this.getMap();
    if (!map) {
      return;
    }
    const doc = map.getOwnerDocument();
    if (!isFullScreenSupported(doc)) {
      return;
    }
    if (isFullScreen(doc)) {
      exitFullScreen(doc);
    } else {
      let element;
      if (this.source_) {
        element =
          typeof this.source_ === 'string'
            ? doc.getElementById(this.source_)
            : this.source_;
      } else {
        element = map.getTargetElement();
      }
      if (this.keys_) {
        requestFullScreenWithKeys(element);
      } else {
        requestFullScreen(element);
      }
    }
  }

  /**
   * @private
   */
  handleFullScreenChange_() {
    const map = this.getMap();
    if (!map) {
      return;
    }
    const wasInFullscreen = this.isInFullscreen_;
    this.isInFullscreen_ = isFullScreen(map.getOwnerDocument());
    if (wasInFullscreen !== this.isInFullscreen_) {
      this.setClassName_(this.button_, this.isInFullscreen_);
      if (this.isInFullscreen_) {
        replaceNode(this.labelActiveNode_, this.labelNode_);
        this.dispatchEvent(FullScreenEventType.ENTERFULLSCREEN);
      } else {
        replaceNode(this.labelNode_, this.labelActiveNode_);
        this.dispatchEvent(FullScreenEventType.LEAVEFULLSCREEN);
      }
      map.updateSize();
    }
  }

  /**
   * @param {HTMLElement} element Target element
   * @param {boolean} fullscreen True if fullscreen class name should be active
   * @private
   */
  setClassName_(element, fullscreen) {
    if (fullscreen) {
      element.classList.remove(...this.inactiveClassName_);
      element.classList.add(...this.activeClassName_);
    } else {
      element.classList.remove(...this.activeClassName_);
      element.classList.add(...this.inactiveClassName_);
    }
  }

  /**
   * Remove the control from its current map and attach it to the new map.
   * Pass `null` to just remove the control from the current map.
   * Subclasses may set up event handlers to get notified about changes to
   * the map here.
   * @param {import("../PluggableMap.js").default|null} map Map.
   * @api
   */
  setMap(map) {
    const oldMap = this.getMap();
    if (oldMap) {
      oldMap.removeChangeListener(
        MapProperty.TARGET,
        this.boundHandleMapTargetChange_
      );
    }

    super.setMap(map);

    this.handleMapTargetChange_();
    if (map) {
      map.addChangeListener(
        MapProperty.TARGET,
        this.boundHandleMapTargetChange_
      );
    }
  }

  /**
   * @private
   */
  handleMapTargetChange_() {
    const listeners = this.documentListeners_;
    for (let i = 0, ii = listeners.length; i < ii; ++i) {
      unlistenByKey(listeners[i]);
    }
    listeners.length = 0;

    const map = this.getMap();
    if (map) {
      const doc = map.getOwnerDocument();
      if (isFullScreenSupported(doc)) {
        this.element.classList.remove(CLASS_UNSUPPORTED);
      } else {
        this.element.classList.add(CLASS_UNSUPPORTED);
      }

      for (let i = 0, ii = events.length; i < ii; ++i) {
        listeners.push(
          listen(doc, events[i], this.handleFullScreenChange_, this)
        );
      }
      this.handleFullScreenChange_();
    }
  }
}

/**
 * @param {Document} doc The root document to check.
 * @return {boolean} Fullscreen is supported by the current platform.
 */
function isFullScreenSupported(doc) {
  const body = doc.body;
  return !!(
    body['webkitRequestFullscreen'] ||
    (body.requestFullscreen && doc.fullscreenEnabled)
  );
}

/**
 * @param {Document} doc The root document to check.
 * @return {boolean} Element is currently in fullscreen.
 */
function isFullScreen(doc) {
  return !!(doc['webkitIsFullScreen'] || doc.fullscreenElement);
}

/**
 * Request to fullscreen an element.
 * @param {HTMLElement} element Element to request fullscreen
 */
function requestFullScreen(element) {
  if (element.requestFullscreen) {
    element.requestFullscreen();
  } else if (element['webkitRequestFullscreen']) {
    element['webkitRequestFullscreen']();
  }
}

/**
 * Request to fullscreen an element with keyboard input.
 * @param {HTMLElement} element Element to request fullscreen
 */
function requestFullScreenWithKeys(element) {
  if (element['webkitRequestFullscreen']) {
    element['webkitRequestFullscreen']();
  } else {
    requestFullScreen(element);
  }
}

/**
 * Exit fullscreen.
 * @param {Document} doc The document to exit fullscren from
 */
function exitFullScreen(doc) {
  if (doc.exitFullscreen) {
    doc.exitFullscreen();
  } else if (doc['webkitExitFullscreen']) {
    doc['webkitExitFullscreen']();
  }
}

export default FullScreen;
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 * Provides unified configuration for all features.
 *
 * This is a custom shindig library that has not yet been submitted for
 * standardization. It is designed to make developing of features for the
 * opensocial / gadgets platforms easier and is intended as a supplemental
 * tool to Shindig's standardized feature loading mechanism.
 *
 * Usage:
 * First, you must register a component that needs configuration:
 *

 *   var config = {
 *     name : gadgets.config.NonEmptyStringValidator,
 *     url : new gadgets.config.RegExValidator(/.+%mySpecialValue%.+/)
 *   };
 *   gadgets.config.register("my-feature", config, myCallback);
 *

 *
 * This will register a component named "my-feature" that expects input config
 * containing a "name" field with a value that is a non-empty string, and a
 * "url" field with a value that matches the given regular expression.
 *
 * When gadgets.config.init is invoked by the container, it will automatically
 * validate your registered configuration and will throw an exception if
 * the provided configuration does not match what was required.
 *
 * Your callback will be invoked by passing all configuration data passed to
 * gadgets.config.init, which allows you to optionally inspect configuration
 * from other features, if present.
 *
 * Note that the container may optionally bypass configuration validation for
 * performance reasons. This does not mean that you should duplicate validation
 * code, it simply means that validation will likely only be performed in debug
 * builds, and you should assume that production builds always have valid
 * configuration.
 */

var gadgets = gadgets || {};

gadgets.config = function() {
  var components = {};

  return {
    /**
     * Registers a configurable component and its configuration parameters.
     *
     * @param {String} component The name of the component to register. Should
     *     be the same as the fully qualified name of the <Require> feature or
     *     the fully qualified javascript object reference (e.g. gadgets.io).
     * @param {Object} opt_validators Mapping of option name to validation
     *     functions that take the form function(data) {return isValid(data);}
     * @param {Function} opt_callback A function to be invoked when a
     *     configuration is registered. If passed, this function will be invoked
     *     immediately after a call to init has been made. Do not assume that
     *     dependent libraries have been configured until after init is
     *     complete. If you rely on this, it is better to defer calling
     *     dependent libraries until you can be sure that configuration is
     *     complete. Takes the form function(config), where config will be
     *     all registered config data for all components. This allows your
     *     component to read configuration from other components.
     * @throws {Error} If the component has already been registered.
     */
    register: function(component, opt_validators, opt_callback) {
      if (components[component]) {
        throw new Error('Component "' + component + '" is already registered.');
      }
      components[component] = {
        validators: opt_validators || {},
        callback: opt_callback
      };
    },

    /**
     * Retrieves configuration data on demand.
     *
     * @param {String} opt_component The component to fetch. If not provided
     *     all configuration will be returned.
     * @return {Object} The requested configuration.
     * @throws {Error} If the given component has not been registered
     */
    get: function(opt_component) {
      if (opt_component) {
        if (!components[opt_component]) {
          throw new Error('Component "' + opt_component + '" not registered.');
        }
        return configuration[opt_component] || {};
      }
      return configuration;
    },

    /**
     * Initializes the configuration.
     *
     * @param {Object} config The full set of configuration data.
     * @param {Boolean} opt_noValidation True if you want to skip validation.
     * @throws {Error} If there is a configuration error.
     */
    init: function(config, opt_noValidation) {
      configuration = config;
      for (var name in components) if (components.hasOwnProperty(name)) {
        var component = components[name],
            conf = config[name],
            validators = component.validators;
        if (!opt_noValidation) {
          for (var v in validators) if (validators.hasOwnProperty(v)) {
            if (!validators[v](conf[v])) {
              throw new Error('Invalid config value "' + conf[v] +
                  '" for parameter "' + v + '" in component "' +
                  name + '"');
            }
          }
        }
        if (component.callback) {
          component.callback(config);
        }
      }
    },

    // Standard validators go here.

    /**
     * Ensures that data is one of a fixed set of items.
     * @param {Array.<String>} list The list of valid values.
     * Also supports argument sytax: EnumValidator("Dog", "Cat", "Fish");
     */
    EnumValidator: function(list) {
      var listItems = [];
      if (arguments.length > 1) {
        for (var i = 0, arg; arg = arguments[i]; ++i) {
          listItems.push(arg);
        }
      } else {
        listItems = list;
      }
      return function(data) {
        for (var i = 0, test; test = listItems[i]; ++i) {
          if (data === listItems[i]) {
            return true;
          }
        }
      }
      return false;
    },

    /**
     * Tests the value against a regular expression.
     */
    RegExValidator: function(re) {
      return function(data) {
        return re.test(data);
      }
    },

    /**
     * Validates that a value was provided.
     */
    ExistsValidator: function(data) {
      return typeof data !== "undefined";
    },

    /**
     * Validates that a value is a non-empty string.
     */
    NonEmptyStringValidator: function(data) {
      return typeof data === "string" && data.length > 0
    },

    /**
     * Validates that the value is a boolean.
     */
    BooleanValidator: function(data) {
      return !!data;
    },

    /**
     * Similar to the ECMAScript4 virtual typing system, ensures that
     * whatever object was passed in is "like" the existing object.
     * Doesn't actually do type validation though, but instead relies
     * on other validators.
     *
     * example:
     *
     *  var validator = new gadgets.config.LikeValidator(
     *    "booleanField" : gadgets.config.BooleanValidator,
     *    "regexField" : new gadgets.config.RegExValidator(/foo.+/);
     *  );
     *
     * This can be used recursively as well to validate sub-objects.
     *
     * @param {Object} test The object to test against.
     */
    LikeValidator : function(test) {
      return function(data) {
        for (var member in test) {
          var t = test[member];
          if (!t(data[member])) {
            return false;
          }
        }
        return true;
      };
    }
  };
}();<commit_msg>Fixed BooleanValidator to actually validate that the data was a boolean rather than validating that it was true.<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 * Provides unified configuration for all features.
 *
 * This is a custom shindig library that has not yet been submitted for
 * standardization. It is designed to make developing of features for the
 * opensocial / gadgets platforms easier and is intended as a supplemental
 * tool to Shindig's standardized feature loading mechanism.
 *
 * Usage:
 * First, you must register a component that needs configuration:
 *

 *   var config = {
 *     name : gadgets.config.NonEmptyStringValidator,
 *     url : new gadgets.config.RegExValidator(/.+%mySpecialValue%.+/)
 *   };
 *   gadgets.config.register("my-feature", config, myCallback);
 *

 *
 * This will register a component named "my-feature" that expects input config
 * containing a "name" field with a value that is a non-empty string, and a
 * "url" field with a value that matches the given regular expression.
 *
 * When gadgets.config.init is invoked by the container, it will automatically
 * validate your registered configuration and will throw an exception if
 * the provided configuration does not match what was required.
 *
 * Your callback will be invoked by passing all configuration data passed to
 * gadgets.config.init, which allows you to optionally inspect configuration
 * from other features, if present.
 *
 * Note that the container may optionally bypass configuration validation for
 * performance reasons. This does not mean that you should duplicate validation
 * code, it simply means that validation will likely only be performed in debug
 * builds, and you should assume that production builds always have valid
 * configuration.
 */

var gadgets = gadgets || {};

gadgets.config = function() {
  var components = {};

  return {
    /**
     * Registers a configurable component and its configuration parameters.
     *
     * @param {String} component The name of the component to register. Should
     *     be the same as the fully qualified name of the <Require> feature or
     *     the fully qualified javascript object reference (e.g. gadgets.io).
     * @param {Object} opt_validators Mapping of option name to validation
     *     functions that take the form function(data) {return isValid(data);}
     * @param {Function} opt_callback A function to be invoked when a
     *     configuration is registered. If passed, this function will be invoked
     *     immediately after a call to init has been made. Do not assume that
     *     dependent libraries have been configured until after init is
     *     complete. If you rely on this, it is better to defer calling
     *     dependent libraries until you can be sure that configuration is
     *     complete. Takes the form function(config), where config will be
     *     all registered config data for all components. This allows your
     *     component to read configuration from other components.
     * @throws {Error} If the component has already been registered.
     */
    register: function(component, opt_validators, opt_callback) {
      if (components[component]) {
        throw new Error('Component "' + component + '" is already registered.');
      }
      components[component] = {
        validators: opt_validators || {},
        callback: opt_callback
      };
    },

    /**
     * Retrieves configuration data on demand.
     *
     * @param {String} opt_component The component to fetch. If not provided
     *     all configuration will be returned.
     * @return {Object} The requested configuration.
     * @throws {Error} If the given component has not been registered
     */
    get: function(opt_component) {
      if (opt_component) {
        if (!components[opt_component]) {
          throw new Error('Component "' + opt_component + '" not registered.');
        }
        return configuration[opt_component] || {};
      }
      return configuration;
    },

    /**
     * Initializes the configuration.
     *
     * @param {Object} config The full set of configuration data.
     * @param {Boolean} opt_noValidation True if you want to skip validation.
     * @throws {Error} If there is a configuration error.
     */
    init: function(config, opt_noValidation) {
      configuration = config;
      for (var name in components) if (components.hasOwnProperty(name)) {
        var component = components[name],
            conf = config[name],
            validators = component.validators;
        if (!opt_noValidation) {
          for (var v in validators) if (validators.hasOwnProperty(v)) {
            if (!validators[v](conf[v])) {
              throw new Error('Invalid config value "' + conf[v] +
                  '" for parameter "' + v + '" in component "' +
                  name + '"');
            }
          }
        }
        if (component.callback) {
          component.callback(config);
        }
      }
    },

    // Standard validators go here.

    /**
     * Ensures that data is one of a fixed set of items.
     * @param {Array.<String>} list The list of valid values.
     * Also supports argument sytax: EnumValidator("Dog", "Cat", "Fish");
     */
    EnumValidator: function(list) {
      var listItems = [];
      if (arguments.length > 1) {
        for (var i = 0, arg; arg = arguments[i]; ++i) {
          listItems.push(arg);
        }
      } else {
        listItems = list;
      }
      return function(data) {
        for (var i = 0, test; test = listItems[i]; ++i) {
          if (data === listItems[i]) {
            return true;
          }
        }
      }
      return false;
    },

    /**
     * Tests the value against a regular expression.
     */
    RegExValidator: function(re) {
      return function(data) {
        return re.test(data);
      }
    },

    /**
     * Validates that a value was provided.
     */
    ExistsValidator: function(data) {
      return typeof data !== "undefined";
    },

    /**
     * Validates that a value is a non-empty string.
     */
    NonEmptyStringValidator: function(data) {
      return typeof data === "string" && data.length > 0
    },

    /**
     * Validates that the value is a boolean.
     */
    BooleanValidator: function(data) {
      return typeof data === "boolean";
    },

    /**
     * Similar to the ECMAScript4 virtual typing system, ensures that
     * whatever object was passed in is "like" the existing object.
     * Doesn't actually do type validation though, but instead relies
     * on other validators.
     *
     * example:
     *
     *  var validator = new gadgets.config.LikeValidator(
     *    "booleanField" : gadgets.config.BooleanValidator,
     *    "regexField" : new gadgets.config.RegExValidator(/foo.+/);
     *  );
     *
     * This can be used recursively as well to validate sub-objects.
     *
     * @param {Object} test The object to test against.
     */
    LikeValidator : function(test) {
      return function(data) {
        for (var member in test) {
          var t = test[member];
          if (!t(data[member])) {
            return false;
          }
        }
        return true;
      };
    }
  };
}();
<|endoftext|>


<commit_before>/*
 * jQuery Ajax Queue v0.1
 * https://github.com/hubgit/jquery-ajax-queue
 *
 * Copyright 2014 Alf Eaton
 * Released under the MIT license
 * http://git.macropus.org/mit-license/
 *
 * Date: 2014-01-19
 */
(function($) {
    $.ajaxQueue = function(params, options) {
        var item = $.extend(new QueueItem(params), options);

        if (item.priority) {
            queue.items.unshift(item);
        } else {
            queue.items.push(item);
        }

        queue.next();

        return item.deferred.promise();
    };

    $.ajaxQueue.concurrent = function(concurrent) {
        queue.concurrent = concurrent;
    };

    var QueueItem = function(params) {
        this.params = params;
        this.tries = 1;
        this.priority = false;
        this.delay = { rate: 10000, server: 5000 };
        this.deferred = $.Deferred();
    };

    QueueItem.prototype.run = function() {
        var item = this;
        var request = $.ajax(item.params);

        request.done(function(data, textStatus, jqXHR) {
            queue.currentCount--;
            item.deferred.resolve(data, textStatus, jqXHR);
            queue.next();
        });

        request.fail(function(jqXHR, textStatus, errorThrown) {
            queue.currentCount--;

            switch (jqXHR.status) {
                case 403: // rate-limited
                    queue.stop(item.delay.rate);
                    queue.items.unshift(item); // add this item back to the queue
                    item.deferred.notify(jqXHR, textStatus, item);
                    break;

                case 500: // server error
                case 503: // unknown error
                    queue.stop(item.delay.server);

                    if (--item.tries) {
                        queue.items.unshift(item); // add this item back to the queue
                        item.deferred.notify(jqXHR, textStatus, item);
                    } else {
                        item.deferred.reject(jqXHR, textStatus, errorThrown);
                    }
                    break;

                default:
                    item.deferred.reject(jqXHR, textStatus, errorThrown);
                    queue.next();
                    break;
            }
        });
    };

    var queue = {
        items: [],
        concurrent: 1,
        currentCount: 0,
        stopped: false,

        stop: function(delay) {
            this.stopped = true;

            if (delay) {
                window.setTimeout(function() {
                    queue.start();
                }, delay);
            }
        },

        start: function()  {
            this.stopped = false;
            this.next();
        },

        clear: function(){
            this.items = [];
            this.currentCount = 0;
        },

        next: function() {
            if (this.stopped) {
                return;
            }

            while (this.items.length && this.currentCount < this.concurrent) {
                this.currentCount++;
                this.items.shift().run();
            }
        }
    };
})(jQuery);
<commit_msg>v0.2 adds concurrent requests<commit_after>/*
 * jQuery Ajax Queue v0.2
 * https://github.com/hubgit/jquery-ajax-queue
 *
 * Copyright 2014 Alf Eaton
 * Released under the MIT license
 * http://git.macropus.org/mit-license/
 *
 * Date: 2014-01-22
 */
(function($) {
    $.ajaxQueue = function(params, options) {
        var item = $.extend(new QueueItem(params), options);

        if (item.priority) {
            queue.items.unshift(item);
        } else {
            queue.items.push(item);
        }

        queue.next();

        return item.deferred.promise();
    };

    $.ajaxQueue.concurrent = function(concurrent) {
        queue.concurrent = concurrent;
    };

    var QueueItem = function(params) {
        this.params = params;
        this.tries = 1;
        this.priority = false;
        this.delay = { rate: 10000, server: 5000 };
        this.deferred = $.Deferred();
    };

    QueueItem.prototype.run = function() {
        var item = this;
        var request = $.ajax(item.params);

        request.done(function(data, textStatus, jqXHR) {
            queue.currentCount--;
            item.deferred.resolve(data, textStatus, jqXHR);
            queue.next();
        });

        request.fail(function(jqXHR, textStatus, errorThrown) {
            queue.currentCount--;

            switch (jqXHR.status) {
                case 403: // rate-limited
                    queue.stop(item.delay.rate);
                    queue.items.unshift(item); // add this item back to the queue
                    item.deferred.notify(jqXHR, textStatus, item);
                    break;

                case 500: // server error
                case 503: // unknown error
                    queue.stop(item.delay.server);

                    if (--item.tries) {
                        queue.items.unshift(item); // add this item back to the queue
                        item.deferred.notify(jqXHR, textStatus, item);
                    } else {
                        item.deferred.reject(jqXHR, textStatus, errorThrown);
                    }
                    break;

                default:
                    item.deferred.reject(jqXHR, textStatus, errorThrown);
                    queue.next();
                    break;
            }
        });
    };

    var queue = {
        items: [],
        concurrent: 1,
        currentCount: 0,
        stopped: false,

        stop: function(delay) {
            this.stopped = true;

            if (delay) {
                window.setTimeout(function() {
                    queue.start();
                }, delay);
            }
        },

        start: function()  {
            this.stopped = false;
            this.next();
        },

        clear: function(){
            this.items = [];
            this.currentCount = 0;
        },

        next: function() {
            if (this.stopped) {
                return;
            }

            while (this.items.length && this.currentCount < this.concurrent) {
                this.currentCount++;
                this.items.shift().run();
            }
        }
    };
})(jQuery);
<|endoftext|>


<commit_before>/**
 * jQuery Bar Rating Plugin
 *
 * http://github.com/netboy/jquery-bar-rating
 *
 * Copyright (c) 2012 Kazik Pietruszewski
 *
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */
(function ($) {
    var BarRating, root, hasTouch;
    root = typeof window !== "undefined" && window !== null ? window : global;
    hasTouch = 'ontouchstart' in root;

    root.BarRating = BarRating = (function () {

        function BarRating() {
            this.show = function () {
                var $this = $(this.elem),
                    userOptions = this.options,
                    $widget,
                    $all,
                    updateRating,
                    clickEvent = hasTouch ? 'touchstart' : 'click';

                // run only once
                if (!$this.data('barrating')) {

                    $this.data('barrating', {
                        currentRatingValue:$this.val(), // initial rating based on the OPTION value
                        currentRatingText:$('option:selected', $this).text()
                    });

                    $widget = $('<div />', { 'class':'bar-rating' }).insertAfter(this.elem);

                    // first OPTION empty - allow deselecting of ratings
                    $this.data('barrating').deselectable = (!$this.find('option:first').val()) ? true : false;

                    // create A elements that will replace OPTIONs
                    $this.find('option').each(function () {
                        var val, text, $a, $span;

                        val = $(this).val();

                        // create ratings - but only if val is defined
                        if (val) {
                            text = $(this).text();
                            $a = $('<a />', { href:'#', 'data-rating-value':val, 'data-rating-text':text });
                            $span = $('<span />', { text:(userOptions.showValues) ? text : '' });

                            $widget.append($a.append($span));
                        }

                    });

                    if (userOptions.showSelectedRating) {
                        $widget.append($('<div />', { text:'', 'class':'current-rating' }));

                        // update text on rating change
                        $widget.find('.current-rating').on('ratingchange',
                            function () {
                                $(this).text($this.data('barrating').currentRatingText);
                            }).trigger('ratingchange');

                    }

                    // will be reused later
                    updateRating = function () {
                        // some rating was already selected?
                        if ($this.data('barrating').currentRatingValue !== undefined) {
                            $widget.find('a[data-rating-value="' + $this.data('barrating').currentRatingValue + '"]')
                                .addClass('selected current')
                                .prevAll().addClass('selected');
                        }
                    };

                    updateRating();

                    $all = $widget.find('a');

                    // make sure click event doesn't cause trouble on touch devices
                    if (hasTouch) {
                        $all.on('click', function (event) {
                            event.preventDefault();
                        });
                    }

                    $all.on(clickEvent, function (event) {
                        var $a = $(this);

                        event.preventDefault();

                        $all.removeClass('active selected');
                        $a.addClass('selected')
                            .prevAll().addClass('selected');

                        value = $a.attr('data-rating-value');
                        text = $a.attr('data-rating-text');

                        // is current and deselectable?
                        if ($a.hasClass('current') && $this.data('barrating').deselectable) {
                            $a.removeClass('selected current').prevAll().removeClass('selected current');
                            value = '', text = '';
                        } else {
                            $all.removeClass('current');
                            $a.addClass('current')
                        }

                        // remember selected rating
                        $this.data('barrating').currentRatingValue = value;
                        $this.data('barrating').currentRatingText = text;

                        // change selected OPTION in the select box (now hidden)
                        $this.find('option').attr('selected', false);
                        $this.find('option[value="' + value + '"]').attr('selected', true);

                        $widget.find('.current-rating').trigger('ratingchange');

                        // onSelect callback
                        userOptions.onSelect.call(
                            this,
                            $this.data('barrating').currentRatingValue,
                            $this.data('barrating').currentRatingText
                        );

                        return false;

                    });

                    // attach mouseenter/mouseleave event handlers
                    if (!hasTouch) {

                        $all.on({
                            mouseenter:function () {
                                var $a = $(this);
                                $all.removeClass('active').removeClass('selected');
                                $a.addClass('active').prevAll().addClass('active');
                            }
                        });

                        $widget.on({
                            mouseleave:function () {
                                $all.removeClass('active');
                                updateRating();
                            }
                        });
                    }

                    // hide the select box
                    $this.hide();
                }
            }
            this.destroy = function () {
                var $this = $(this.elem);

                $this.removeData('barrating');
                $('.bar-rating, .bar-rating a').off().remove();

                // show the select box
                $this.show();
            }
        }

        BarRating.prototype.init = function (options, elem) {
            var self;
            self = this;
            self.elem = elem;

            return self.options = $.extend({}, $.fn.barrating.defaults, options);
        };

        return BarRating;

    })();

    $.fn.barrating = function (method, options) {
        return this.each(function () {
            var plugin = new BarRating();

            // plugin works with select fields
            if (!$(this).is('select')) {
                $.error('Sorry, this plugin only works with select fields.');
            }

            // method supplied
            if (plugin.hasOwnProperty(method)) {
                plugin.init(options, this);
                return plugin[method]();

            // no method supplied or only options supplied
            } else if (typeof method === 'object' || !method) {
                options = method;
                plugin.init(options, this);
                return plugin.show();

            } else {
                $.error('Method ' + method + ' does not exist on jQuery.barrating');
            }

        });
    };
    return $.fn.barrating.defaults = {
        showValues:false, // display rating values on the bars?
        showSelectedRating:true, // append a div with a rating to the widget?
        onSelect:function (value, text) {
        } // callback fired when a rating is selected
    };
})(jQuery);
<commit_msg>fix for a "global leak detected" error (mocha)<commit_after>/**
 * jQuery Bar Rating Plugin
 *
 * http://github.com/netboy/jquery-bar-rating
 *
 * Copyright (c) 2012 Kazik Pietruszewski
 *
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */
(function ($) {
    var BarRating, root, hasTouch;
    root = typeof window !== "undefined" && window !== null ? window : global;
    hasTouch = 'ontouchstart' in root;

    root.BarRating = BarRating = (function () {

        function BarRating() {
            this.show = function () {
                var $this = $(this.elem),
                    userOptions = this.options,
                    $widget,
                    $all,
                    updateRating,
                    clickEvent = hasTouch ? 'touchstart' : 'click';

                // run only once
                if (!$this.data('barrating')) {

                    $this.data('barrating', {
                        currentRatingValue:$this.val(), // initial rating based on the OPTION value
                        currentRatingText:$('option:selected', $this).text()
                    });

                    $widget = $('<div />', { 'class':'bar-rating' }).insertAfter(this.elem);

                    // first OPTION empty - allow deselecting of ratings
                    $this.data('barrating').deselectable = (!$this.find('option:first').val()) ? true : false;

                    // create A elements that will replace OPTIONs
                    $this.find('option').each(function () {
                        var val, text, $a, $span;

                        val = $(this).val();

                        // create ratings - but only if val is defined
                        if (val) {
                            text = $(this).text();
                            $a = $('<a />', { href:'#', 'data-rating-value':val, 'data-rating-text':text });
                            $span = $('<span />', { text:(userOptions.showValues) ? text : '' });

                            $widget.append($a.append($span));
                        }

                    });

                    if (userOptions.showSelectedRating) {
                        $widget.append($('<div />', { text:'', 'class':'current-rating' }));

                        // update text on rating change
                        $widget.find('.current-rating').on('ratingchange',
                            function () {
                                $(this).text($this.data('barrating').currentRatingText);
                            }).trigger('ratingchange');

                    }

                    // will be reused later
                    updateRating = function () {
                        // some rating was already selected?
                        if ($this.data('barrating').currentRatingValue !== undefined) {
                            $widget.find('a[data-rating-value="' + $this.data('barrating').currentRatingValue + '"]')
                                .addClass('selected current')
                                .prevAll().addClass('selected');
                        }
                    };

                    updateRating();

                    $all = $widget.find('a');

                    // make sure click event doesn't cause trouble on touch devices
                    if (hasTouch) {
                        $all.on('click', function (event) {
                            event.preventDefault();
                        });
                    }

                    $all.on(clickEvent, function (event) {
                        var $a = $(this),
                            value,
                            text;

                        event.preventDefault();

                        $all.removeClass('active selected');
                        $a.addClass('selected')
                            .prevAll().addClass('selected');

                        value = $a.attr('data-rating-value');
                        text = $a.attr('data-rating-text');

                        // is current and deselectable?
                        if ($a.hasClass('current') && $this.data('barrating').deselectable) {
                            $a.removeClass('selected current').prevAll().removeClass('selected current');
                            value = '', text = '';
                        } else {
                            $all.removeClass('current');
                            $a.addClass('current')
                        }

                        // remember selected rating
                        $this.data('barrating').currentRatingValue = value;
                        $this.data('barrating').currentRatingText = text;

                        // change selected OPTION in the select box (now hidden)
                        $this.find('option').attr('selected', false);
                        $this.find('option[value="' + value + '"]').attr('selected', true);

                        $widget.find('.current-rating').trigger('ratingchange');

                        // onSelect callback
                        userOptions.onSelect.call(
                            this,
                            $this.data('barrating').currentRatingValue,
                            $this.data('barrating').currentRatingText
                        );

                        return false;

                    });

                    // attach mouseenter/mouseleave event handlers
                    if (!hasTouch) {

                        $all.on({
                            mouseenter:function () {
                                var $a = $(this);
                                $all.removeClass('active').removeClass('selected');
                                $a.addClass('active').prevAll().addClass('active');
                            }
                        });

                        $widget.on({
                            mouseleave:function () {
                                $all.removeClass('active');
                                updateRating();
                            }
                        });
                    }

                    // hide the select box
                    $this.hide();
                }
            }
            this.destroy = function () {
                var $this = $(this.elem);

                $this.removeData('barrating');
                $('.bar-rating, .bar-rating a').off().remove();

                // show the select box
                $this.show();
            }
        }

        BarRating.prototype.init = function (options, elem) {
            var self;
            self = this;
            self.elem = elem;

            return self.options = $.extend({}, $.fn.barrating.defaults, options);
        };

        return BarRating;

    })();

    $.fn.barrating = function (method, options) {
        return this.each(function () {
            var plugin = new BarRating();

            // plugin works with select fields
            if (!$(this).is('select')) {
                $.error('Sorry, this plugin only works with select fields.');
            }

            // method supplied
            if (plugin.hasOwnProperty(method)) {
                plugin.init(options, this);
                return plugin[method]();

            // no method supplied or only options supplied
            } else if (typeof method === 'object' || !method) {
                options = method;
                plugin.init(options, this);
                return plugin.show();

            } else {
                $.error('Method ' + method + ' does not exist on jQuery.barrating');
            }

        });
    };
    return $.fn.barrating.defaults = {
        showValues:false, // display rating values on the bars?
        showSelectedRating:true, // append a div with a rating to the widget?
        onSelect:function (value, text) {
        } // callback fired when a rating is selected
    };
})(jQuery);
<|endoftext|>


<commit_before>var DataTable = function (element, options) {
    this.$element = $(element);
    this.options = $.extend({}, $.fn.dataTable.defaults, options);
    this.content = '';
    this.pagination = '';
    this.data = {};
    this.totalCount = 0;
    this.init();

    return this;
}

DataTable.prototype.init = function(){
    var dataTable = this;
    var sleep;

    if (this.options.source != ''){
        var filter = {};
        for (var key in this.options.filters) {

            var reloadTable = function(){
                clearInterval(sleep);
                sleep = setTimeout(function(){
                    dataTable.options.page = 1;
                    dataTable.init();
                }, 700);
            }

            this.options.filters[key].off('keyup, change').on('keyup, change', reloadTable);
            filter[key] = this.options.filters[key].val();
        }

        var offset = (this.options.page - 1) * this.options.limit;

        // loading
        var tdCount = $('thead th', this.$element).size();
        var loading = '
' +
            '
' +
            '
Загрузка...
' +
            '
' +
            '
';

        var cookieName = window.location.pathname.split('/').join('') + '_' + this.$element.attr('id') + '_orderby';
        if (typeof $.cookie(cookieName) !== 'undefined') {
            this.options.orderby = $.parseJSON($.cookie(cookieName)) || this.options.orderby;
        }

        var cookieName = window.location.pathname.split('/').join('') + '_' + this.$element.attr('id') + '_limit';
        if (typeof $.cookie(cookieName) !== 'undefined') {
            this.options.limit = $.parseJSON($.cookie(cookieName)) || this.options.limit;
        }

        // Table headers init
        dataTable.renderHeader();

        $('tbody', this.$element).html(loading);

        $.ajax({
            url: this.options.source,
            dataType: 'JSON',
            data: {
                options: {
                    orderby: this.options.orderby,
                    limit: this.options.limit,
                    offset: offset,
                    filters: filter,
                    data: this.options.data
                }
            },
            success: function(res){
                if (res.status == 'ok'){
                    dataTable.data = res.data;
                    dataTable.totalCount = res.data.totalCount;
                    dataTable.render(res.data.data);
                } else {
                    var count = $('thead th', dataTable.$element).size();
                    $('tbody', this.$element).html('
' + dataTable.options.notFound + '
');
                }

                dataTable.options.onRender(res);
            }
        });
    }
}

DataTable.prototype.render = function(data){
    var dataTable = this;
    this.content = '';

    if (data.length > 0){
        for (key in data) {
            this.renderRow(data[key])
        }
        this.pagination = this.buildPagination();

        $('tbody', this.$element).html(this.content);

        this.removePagination();

        if (this.pagination != ''){
            $(this.$element).after(this.pagination);

            $('.paginationLimit select').select2({
                minimumResultsForSearch: Infinity
            });

            var pagination = $(this.$element).next();

            $('.pages button.btn', pagination).click(function(){
                if (!$(this).hasClass('active')){
                    dataTable.options.page = parseInt($(this).html());
                    dataTable.init();
                    return false;
                }
            });

            if (dataTable.options.limitChangeEnabled) {
                $('.paginationLimit a').click(function () {
                    var limit = $(this).data('value');
                    if (limit != dataTable.options.limit) {
                        dataTable.options.limit = parseInt(limit);
                        var cookieName = window.location.pathname.split('/').join('') + '_' + dataTable.$element.attr('id') + '_limit';
                        $.cookie(cookieName, JSON.stringify(dataTable.options.limit));
                        dataTable.init();
                        return false;
                    }
                });
            }
        }
    } else {
        var tdCount = $('thead th', this.$element).size();
        this.removePagination();
        this.content = '
' +
            this.options.notFound +
            '
';
        $('tbody', this.$element).html(this.content);
    }
}

DataTable.prototype.renderRow = function(row){
    var line = '';

    if (row !== undefined && row.class !== undefined){
        line += '
';
        delete row.class;
    } else {
        line += '
';
    }

    var once = false
    for (key in row) {
        /* Strange Safari bug fix */
        if (once && key == 0) break;
        once = true;

        line += '' + row[key] + '	';
    }
    line += '
';
    this.content += line;

    return line;
}

DataTable.prototype.renderHeader = function(){
    var dataTable = this;
    var dataTableId = dataTable.$element.attr('id');

    $('thead th', dataTable.$element).removeClass('sort').removeClass('asc').removeClass('desc');

    for (var key in dataTable.options.cols) {
        if (dataTable.options.cols[key].sortable == undefined
            || ( dataTable.options.cols[key].sortable !== undefined && dataTable.options.cols[key].sortable)) {
            var hCol = $('thead th:eq(' + key + ')', dataTable.$element);
            if (dataTable.options.cols[key].order != false) hCol.addClass('sort');
            for (var sortKey in dataTable.options.orderby) {
                if (dataTable.options.orderby[sortKey][0] == parseInt(key) + 1) {
                    hCol.addClass(dataTable.options.orderby[sortKey][1].toLowerCase());
                }
            }
        }
    }

    $(document).off('click', '#' + dataTableId + '.dataTable th.sort').on('click', '#' + dataTableId + '.dataTable th.sort', function(e) {
        if ($(this).hasClass('asc')){
            dataTable.options.orderby = [[$(this).index() + 1, 'desc']];
        } else {
            dataTable.options.orderby = [[$(this).index() + 1, 'asc']];
        }

        var cookieName = window.location.pathname.split('/').join('') + '_' + dataTable.$element.attr('id') + '_orderby';
        $.cookie(cookieName, JSON.stringify(dataTable.options.orderby));
        dataTable.init();
    });
}

DataTable.prototype.buildPagination = function(){
    var pagination = '';
    var paused = false;
    var page = this.options.page;

    var pages = Math.ceil(this.totalCount / this.options.limit);

    pagination += '
';

    if (pages > 1){
        pagination += '
';
        for (var i = 1; i <= pages; i++) {
            if ((i > 2 && i < page - 2) || (i < pages-2 && i > page + 2)) {
                if (!paused){
                    pagination += '
';
                    paused = true;
                }
                continue;
            }

            if (paused) {
                pagination += '
';
                paused = false;
            }
            pagination += '<button class="btn';
            if (i == page) pagination += ' active';
            pagination += '">' + i + '</button>';
        }
        pagination += '
';

    }

    if (this.options.limitChangeEnabled) {
        pagination += '
'
            + '<select class="select">'
            + '<option value="10" data-value="10" data-id="limit">10</option>'
            + '<option value="20" data-value="20" data-id="limit">20</option>'
            + '<option value="50" data-value="50" data-id="limit">50</option>'
            + '<option value="100" data-value="100" data-id="limit">100</option>'
            + '</select>'
            + '
';
    }

    pagination += '
';

    return pagination;
}

DataTable.prototype.removePagination = function(){
    if ($(this.$element).next().hasClass('dataTable_pagination')) {
        $(this.$element).next().remove();
    }
}

$.fn.dataTable = function(options) {
    return new DataTable(this, options);
}

$.fn.dataTable.Constructor = DataTable;

$.fn.dataTable.defaults = {
    onRender: function(){},
    orderby: [],
    source: '',
    cols: [],
    limit: 50,
    page: 1,
    filters: {},
    notFound: 'не найдено ни одной записи',
    limitChangeEnabled: true
}<commit_msg>loading text update & minor fixes<commit_after>var DataTable = function (element, options) {
    this.$element = $(element);
    this.options = $.extend({}, $.fn.dataTable.defaults, options);
    this.content = '';
    this.pagination = '';
    this.data = {};
    this.totalCount = 0;
    this.init();

    return this;
}

DataTable.prototype.init = function(){
    var dataTable = this;
    var sleep;

    if (dataTable.options.source != ''){
        var filter = {};
        for (var key in dataTable.options.filters) {

            var reloadTable = function(){
                clearInterval(sleep);
                sleep = setTimeout(function(){
                    dataTable.options.page = 1;
                    dataTable.init();
                }, 700);
            }

            dataTable.options.filters[key].off('keyup, change').on('keyup, change', reloadTable);
            filter[key] = dataTable.options.filters[key].val();
        }

        var offset = (dataTable.options.page - 1) * dataTable.options.limit;

        // loading
        var tdCount = $('thead th', dataTable.$element).size();
        var loading = '
' +
            '
' +
            '
' + DataTable.options.loading + '
' +
            '
' +
            '
';

        var cookieName = window.location.pathname.split('/').join('') + '_' + dataTable.$element.attr('id') + '_orderby';
        if (typeof $.cookie(cookieName) !== 'undefined') {
            dataTable.options.orderby = $.parseJSON($.cookie(cookieName)) || dataTable.options.orderby;
        }

        var cookieName = window.location.pathname.split('/').join('') + '_' + dataTable.$element.attr('id') + '_limit';
        if (typeof $.cookie(cookieName) !== 'undefined') {
            dataTable.options.limit = $.parseJSON($.cookie(cookieName)) || dataTable.options.limit;
        }

        // Table headers init
        dataTable.renderHeader();

        $('tbody', dataTable.$element).html(loading);

        $.ajax({
            url: dataTable.options.source,
            dataType: 'JSON',
            data: {
                options: {
                    orderby: dataTable.options.orderby,
                    limit: dataTable.options.limit,
                    offset: offset,
                    filters: filter,
                    data: dataTable.options.data
                }
            },
            success: function(res){
                if (res.status == 'ok'){
                    dataTable.data = res.data;
                    dataTable.totalCount = res.data.totalCount;
                    dataTable.render(res.data.data);
                } else {
                    var count = $('thead th', dataTable.$element).size();
                    $('tbody', this.$element).html('
' + dataTable.options.notFound + '
');
                }

                dataTable.options.onRender(res);
            }
        });
    }
}

DataTable.prototype.render = function(data){
    var dataTable = this;
    this.content = '';

    if (data.length > 0){
        for (key in data) {
            this.renderRow(data[key])
        }
        this.pagination = this.buildPagination();

        $('tbody', this.$element).html(this.content);

        this.removePagination();

        if (this.pagination != ''){
            $(this.$element).after(this.pagination);

            $('.paginationLimit select').select2({
                minimumResultsForSearch: Infinity
            });

            var pagination = $(this.$element).next();

            $('.pages button.btn', pagination).click(function(){
                if (!$(this).hasClass('active')){
                    dataTable.options.page = parseInt($(this).html());
                    dataTable.init();
                    return false;
                }
            });

            if (dataTable.options.limitChangeEnabled) {
                $('.paginationLimit a').click(function () {
                    var limit = $(this).data('value');
                    if (limit != dataTable.options.limit) {
                        dataTable.options.limit = parseInt(limit);
                        var cookieName = window.location.pathname.split('/').join('') + '_' + dataTable.$element.attr('id') + '_limit';
                        $.cookie(cookieName, JSON.stringify(dataTable.options.limit));
                        dataTable.init();
                        return false;
                    }
                });
            }
        }
    } else {
        var tdCount = $('thead th', this.$element).size();
        this.removePagination();
        this.content = '
' +
            this.options.notFound +
            '
';
        $('tbody', this.$element).html(this.content);
    }
}

DataTable.prototype.renderRow = function(row){
    var line = '';

    if (row !== undefined && row.class !== undefined){
        line += '
';
        delete row.class;
    } else {
        line += '
';
    }

    var once = false
    for (key in row) {
        /* Strange Safari bug fix */
        if (once && key == 0) break;
        once = true;

        line += '' + row[key] + '	';
    }
    line += '
';
    this.content += line;

    return line;
}

DataTable.prototype.renderHeader = function(){
    var dataTable = this;
    var dataTableId = dataTable.$element.attr('id');

    $('thead th', dataTable.$element).removeClass('sort').removeClass('asc').removeClass('desc');

    for (var key in dataTable.options.cols) {
        if (dataTable.options.cols[key].sortable == undefined
            || ( dataTable.options.cols[key].sortable !== undefined && dataTable.options.cols[key].sortable)) {
            var hCol = $('thead th:eq(' + key + ')', dataTable.$element);
            if (dataTable.options.cols[key].order != false) hCol.addClass('sort');
            for (var sortKey in dataTable.options.orderby) {
                if (dataTable.options.orderby[sortKey][0] == parseInt(key) + 1) {
                    hCol.addClass(dataTable.options.orderby[sortKey][1].toLowerCase());
                }
            }
        }
    }

    $(document).off('click', '#' + dataTableId + '.dataTable th.sort').on('click', '#' + dataTableId + '.dataTable th.sort', function(e) {
        if ($(this).hasClass('asc')){
            dataTable.options.orderby = [[$(this).index() + 1, 'desc']];
        } else {
            dataTable.options.orderby = [[$(this).index() + 1, 'asc']];
        }

        var cookieName = window.location.pathname.split('/').join('') + '_' + dataTable.$element.attr('id') + '_orderby';
        $.cookie(cookieName, JSON.stringify(dataTable.options.orderby));
        dataTable.init();
    });
}

DataTable.prototype.buildPagination = function(){
    var pagination = '';
    var paused = false;
    var page = this.options.page;

    var pages = Math.ceil(this.totalCount / this.options.limit);

    pagination += '
';

    if (pages > 1){
        pagination += '
';
        for (var i = 1; i <= pages; i++) {
            if ((i > 2 && i < page - 2) || (i < pages-2 && i > page + 2)) {
                if (!paused){
                    pagination += '
';
                    paused = true;
                }
                continue;
            }

            if (paused) {
                pagination += '
';
                paused = false;
            }
            pagination += '<button class="btn';
            if (i == page) pagination += ' active';
            pagination += '">' + i + '</button>';
        }
        pagination += '
';

    }

    if (this.options.limitChangeEnabled) {
        pagination += '
'
            + '<select class="select">'
            + '<option value="10" data-value="10" data-id="limit">10</option>'
            + '<option value="20" data-value="20" data-id="limit">20</option>'
            + '<option value="50" data-value="50" data-id="limit">50</option>'
            + '<option value="100" data-value="100" data-id="limit">100</option>'
            + '</select>'
            + '
';
    }

    pagination += '
';

    return pagination;
}

DataTable.prototype.removePagination = function(){
    if ($(this.$element).next().hasClass('dataTable_pagination')) {
        $(this.$element).next().remove();
    }
}

$.fn.dataTable = function(options) {
    return new DataTable(this, options);
}

$.fn.dataTable.Constructor = DataTable;

$.fn.dataTable.defaults = {
    onRender: function(){},
    orderby: [],
    source: '',
    cols: [],
    limit: 50,
    page: 1,
    filters: {},
    notFound: 'не найдено ни одной записи',
    loading: 'загрузка...',
    limitChangeEnabled: true,
}<|endoftext|>


<commit_before>/*globals HTMLCollection, jQuery*/
(function ($) {
  "use strict";
  [HTMLCollection, NodeList].forEach(function (collection) {
    collection.prototype.toArray = function () {
      return Array.prototype.slice.call(this);
    };
  });

  $.fn.easyTable = function ( action, options ) {
    var opts = $.extend( {}, $.fn.easyTable.defaults, options ),
    element = this.get(0),
    dummyTable = document.getElementById(element.id + "-fixed"),
    thead = element.getElementsByTagName('thead').item(0) || dummyTable.getElementsByTagName('thead').item(0),
    headers = thead.getElementsByTagName('th'),
    tbody = element.getElementsByTagName('tbody').item(0),
    rows = tbody.getElementsByTagName('tr');

  function _fixTableHeader() {
    if (! dummyTable) {
      element.removeChild(thead);
      dummyTable = element.cloneNode(true);
      dummyTable.appendChild(thead);
      dummyTable.removeChild(dummyTable.getElementsByTagName('tbody').item(0));
      dummyTable.id = dummyTable.id + "-fixed";
      element.parentNode.parentNode.insertBefore(dummyTable, element.parentNode);
      _fixColumnsWidths();
    }
  }

  function _unfixTableHeader() {
    if (dummyTable) {
      var Aheaders = headers.toArray();
      for (var index in Aheaders) {
        if (Aheaders.hasOwnProperty(index)) {
          Aheaders[index].style.width = "";
          Aheaders[index].style.width = "";
        }
      }
      element.insertBefore(thead, tbody);
      dummyTable.parentNode.removeChild(dummyTable);
    }
  }

  function _fixColumnsWidths() {
    var fisrtRow = rows.toArray()[0],
    fisrtLineData = fisrtRow.getElementsByTagName('td').toArray(),
    AHeaders = headers.toArray();
    for (var index in AHeaders ) {
      if (AHeaders.hasOwnProperty(index)) {
        AHeaders[index].style.width = fisrtLineData[index].offsetWidth + 'px';
      }
    }
    AHeaders[index].style.width = fisrtLineData[index].offsetWidth + _scrollbarWidth() + 'px';
  }

  function _scrollbarWidth() {
    var outer = document.createElement("div"),
    widthNoScroll,
    inner,
    widthWithScroll;
    outer.style.visibility = "hidden";
    outer.style.width = "100px";
    outer.style.msOverflowStyle = "scrollbar";
    document.body.appendChild(outer);
    widthNoScroll = outer.offsetWidth;
    outer.style.overflow = "scroll";
    inner = document.createElement("div");
    inner.style.width = "100%";
    outer.appendChild(inner);
    widthWithScroll = inner.offsetWidth;
    outer.parentNode.removeChild(outer);
    return widthNoScroll - widthWithScroll;
  }

  function _addNewRow() {
    var newRow = document.createElement('tr'),
    values = opts.values || opts.columnsValues,
    ids = opts.ids || opts.columnsIDs,
    names = opts.names || opts.ColumnsNames,
    td;
    values.forEach(function (value, index) {
      td = document.createElement('td');
      td.innerHTML = value;
      if (ids && ids[index]) {
        td.id = ids[index];
      }
      if (names && names[index]) {
        td.setAttribute('name',names[index]);
      }
      newRow.appendChild(td);
    });
    tbody.appendChild(newRow);
  }

  function _toogleEditContent() {
    $.fn.easyTable.defaults.edit = ! $.fn.easyTable.defaults.edit;
    if ($.fn.easyTable.defaults.edit) {
      rows.toArray().forEach(function (row) {
        row.getElementsByTagName('td').toArray().forEach( function (data) {
          data.ondblclick  = _editContent;
        });
      });
    } else {
      rows.toArray().forEach(function (row) {
        row.getElementsByTagName('td').toArray().forEach( function (data) {
          data.ondblclick  = undefined;
        });
      });
    }
  }

  function _editContent() {
    /*jshint validthis: true */
    var textArea = document.createElement('textarea');
    textArea.value = this.innerHTML;
    textArea.style.width = this.style.width + 'px';
    textArea.style.height = this.style.height + 'px';
    textArea.style.resize = 'none';
    textArea.onblur = _removeTextArea;
    this.innerHTML = "";
    this.ondblclick = undefined;
    this.appendChild(textArea);
    _fixColumnsWidths();
  }

  function _removeTextArea() {
    /*jshint validthis: true */
    var parentNode = this.parentNode;
    this.parentNode.removeChild(this);
    parentNode.innerHTML = this.value;
    parentNode.ondblclick = _editContent;
    _fixColumnsWidths();
  }

  function _removeRow() {
    if (opts.indexes) {
      opts.indexes.sort().reverse();
      opts.indexes.forEach(function (index) {
        rows[index].parentNode.removeChild(rows[index]);
      });
    }
  }

  function _removeAllRows() {
    opts.indexes = [];
    for (var index = 0; index < rows.length ; index++) {
      opts.indexes.push(Number(index));
    }
    _removeRow();
  }

  function _sortByColumn() {
    if (opts.column == 0 || opts.column) {
      var ordered = rows.toArray().sort(function(a, b) {
        var A = a.cells.item(opts.column).innerHTML,
        B = b.cells.item(opts.column).innerHTML;
        if (opts.replacement) {
          A = A.replace(opts.replacement.from, opts.replacement.to || '');
          B = B.replace(opts.replacement.from, opts.replacement.to || '');
        }
        return smartSort(A, B);
      }),
      row;
      while (row = rows.toArray().shift()) {
        tbody.removeChild(row);
      }
      while ( row = ordered.shift()) {
        tbody.appendChild(row);
      }
    }
  }

  function smartSort(A, B) {
    if ( isNaN(A) || isNaN(B) ) {
      return A.localeCompare(B);
    } else {
      return Number(A) - Number(B);
    }
  }

  switch ( action ) {

    case 'fixedHead':
      _fixTableHeader();
      break;

    case 'undoFixedHead':
      _unfixTableHeader();
      break;

    case 'addRow':
      _addNewRow();
      break;

    case 'edit':
    case 'editRowContent':
      _toogleEditContent();
      break;

    case 'removeRow':
      _removeRow();
      break;

    case 'removeAllRows':
      _removeAllRows();
      break;

    case 'sort':
      _sortByColumn();
      break;
    }
    return this;
};

  $.fn.easyTable.defaults = {
    indexes: null,
    values: null,
    names: null,
    ids: null,
    edit: false,
    column: null,
    replacement: null
  };

})(jQuery);
<commit_msg>Fixing columns widths for Windows SOs<commit_after>/*globals HTMLCollection, jQuery*/
(function ($) {
  "use strict";
  [HTMLCollection, NodeList].forEach(function (collection) {
    collection.prototype.toArray = function () {
      return Array.prototype.slice.call(this);
    };
  });

  $.fn.easyTable = function ( action, options ) {
    var opts = $.extend( {}, $.fn.easyTable.defaults, options ),
    element = this.get(0),
    dummyTable = document.getElementById(element.id + "-fixed"),
    thead = element.tHead || dummyTable.tHead,
    headers = thead.rows.item(0).cells,
    tbody = element.tBodies.item(0),
    rows = tbody.rows;

  function _fixTableHeader() {
     if (! dummyTable) {
        element.removeChild(thead);
        dummyTable = element.cloneNode(true);
        dummyTable.appendChild(thead);
        dummyTable.removeChild(dummyTable.tBodies.item(0));
        dummyTable.id += "-fixed";
        dummyTable.style.width = element.offsetWidth + 'px';
        element.parentNode.parentNode.insertBefore(dummyTable, element.parentNode);
        _fixColumnsWidths();
      }
  }

  function _unfixTableHeader() {
    if (dummyTable) {
      var Aheaders = headers.toArray();
      for (var index in Aheaders) {
        if (Aheaders.hasOwnProperty(index)) {
          Aheaders[index].style.width = "";
          rows[0].cells[index].style.width = '';
        }
      }
      element.insertBefore(thead, tbody);
      dummyTable.parentNode.removeChild(dummyTable);
    }
  }

  function _fixColumnsWidths() {
    if(dummyTable) {
      var fisrtRow = rows[0],
      fisrtLineData = fisrtRow.cells,
      AHeaders = headers.toArray(),
      pWidth;
      for (var index in AHeaders ) {
        if (AHeaders.hasOwnProperty(index)) {
          pWidth = (fisrtLineData[index].offsetWidth/tbody.offsetWidth) * 100 + '%';
          AHeaders[index].style.width = pWidth;
          rows[0].cells[index].style.width = pWidth;
        }
      }
    }
  }

  function _addNewRow() {
    var newRow = document.createElement('tr'),
    values = opts.values || opts.columnsValues,
    ids = opts.ids || opts.columnsIDs,
    names = opts.names || opts.ColumnsNames,
    td;
    values.forEach(function (value, index) {
      td = document.createElement('td');
      td.innerHTML = value;
      if (ids && ids[index]) {
        td.id = ids[index];
      }
      if (names && names[index]) {
        td.setAttribute('name',names[index]);
      }
      newRow.appendChild(td);
    });
    tbody.appendChild(newRow);
  }

  function _toogleEditContent() {
    $.fn.easyTable.defaults.edit = ! $.fn.easyTable.defaults.edit;
    if ($.fn.easyTable.defaults.edit) {
      rows.toArray().forEach(function (row) {
        row.getElementsByTagName('td').toArray().forEach( function (data) {
          data.ondblclick  = _editContent;
        });
      });
    } else {
      rows.toArray().forEach(function (row) {
        row.getElementsByTagName('td').toArray().forEach( function (data) {
          data.ondblclick  = undefined;
        });
      });
    }
  }

  function _editContent() {
    /*jshint validthis: true */
    var textArea = document.createElement('textarea');
    textArea.value = this.innerHTML;
    textArea.style.width = this.style.width + 'px';
    textArea.style.height = this.style.height + 'px';
    textArea.style.resize = 'none';
    textArea.onblur = _removeTextArea;
    this.innerHTML = "";
    this.ondblclick = undefined;
    this.appendChild(textArea);
	 _fixColumnsWidths();
  }

  function _removeTextArea() {
    /*jshint validthis: true */
    var parentNode = this.parentNode;
    this.parentNode.removeChild(this);
    parentNode.innerHTML = this.value;
    parentNode.ondblclick = _editContent;
    _fixColumnsWidths();
  }

  function _removeRow() {
    if (opts.indexes) {
      opts.indexes.sort(function (a, b) {
		return Number(a) - Number(b);
	  }).reverse();
      opts.indexes.forEach(function (index) {
        rows[index].parentNode.removeChild(rows[index]);
      });
    }
  }

  function _removeAllRows() {
    opts.indexes = [];
    for (var index = 0; index < rows.length ; index++) {
      opts.indexes.push(Number(index));
    }
    _removeRow();
  }

  function _sortByColumn() {
    if (opts.column === 0 || opts.column) {
      var ordered = rows.toArray().sort(function(a, b) {
        var A = a.cells.item(opts.column).innerHTML,
			B = b.cells.item(opts.column).innerHTML;
        if (opts.replacement) {
          A = A.replace(opts.replacement.from, opts.replacement.to || '');
          B = B.replace(opts.replacement.from, opts.replacement.to || '');
        }
        return smartSort(A, B);
      }),
      row;
      while (row = rows.toArray().shift()) {
        tbody.removeChild(row);
      }
      while ( row = ordered.shift()) {
        tbody.appendChild(row);
      }
    }
  }

  function smartSort(A, B) {
    if ( isNaN(A) || isNaN(B) ) {
      return A.localeCompare(B);
    } else {
      return Number(A) - Number(B);
    }
  }

  switch ( action ) {

    case 'fixedHead':
      _fixTableHeader();
      break;

    case 'undoFixedHead':
      _unfixTableHeader();
      break;

    case 'addRow':
      _addNewRow();
      break;

    case 'edit':
    case 'editRowContent':
      _toogleEditContent();
      break;

    case 'removeRow':
      _removeRow();
      break;

    case 'removeAllRows':
      _removeAllRows();
      break;

    case 'sort':
      _sortByColumn();
      break;
    }
    return this;
};

  $.fn.easyTable.defaults = {
    indexes: null,
    values: null,
    names: null,
    ids: null,
    edit: false,
    column: null,
    replacement: null
  };

})(jQuery);
<|endoftext|>