<commit_before>/*
Copyright 2017 Google Inc. 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.
*/

// This file create the main interface of the app.
'use strict';

(function() {

var loadTests = function(testType) {
  // We have to make it compatible to the legacy url format.
  var testName = testType.substr(0, testType.indexOf('-'));
  testName = util.MakeCapitalName(testName) + 'Test';
  return window[testName]();
};

var parseParam = function(param, defaultValue) {
  var regex = new RegExp('(\\?|\\&)' + param + '=([-:,\\w]+)', 'g');
  var value = regex.exec(document.URL);
  return value ? value[2] : defaultValue;
};

var parseParams = function(testSuiteConfig) {
  var config = {};
  config.testType = parseParam('test_type', testSuiteConfig.defaultTestSuite);
  config.timestamp = parseParam('timestamp');
  config.command = parseParam('command');
  config.timeout = parseParam('timeout', TestBase.timeout);
  config.logging = !util.stringToBoolean(parseParam('disable_log', false));
  config.loop = util.stringToBoolean(parseParam('loop', false));
  config.stoponfailure = util.stringToBoolean(
      parseParam('stoponfailure', false));
  config.enablewebm = util.stringToBoolean(
      parseParam('enablewebm', testSuiteConfig.enablewebm));
  config.tests = parseParam('tests');
  config.exclude = parseParam('exclude');
  config.testsMask = parseParam('tests_mask', '');
  config.testid = parseParam('testid', '');

  // Overloaded run command to support browsers that have limitations on extra
  // parameters. Example usage: command=run:1,2,3
  if (config.command.indexOf(':') != -1) {
    var commandSplit = config.command.split(':');
    config.command = commandSplit[0];
    config.tests = commandSplit[1];
  }
  return config;
};

var configureHarness = function(testSuiteConfig) {
  harnessConfig.controlMediaFormatSelection =
      testSuiteConfig.controlMediaFormatSelection;
  harnessConfig.recycleVideoTag = true;
  TestBase.timeout = harnessConfig.timeout;

  if (harnessConfig.testsMask) {
    harnessConfig.testsMask += '0';
  } else if (harnessConfig.tests) {
    harnessConfig.tests = harnessConfig.tests.split(',').
        map(function(x) {return parseInt(x);}).
        sort(function(a, b) {return a - b;});
    for (var i = 0; i < harnessConfig.tests.length; ++i) {
      var index = harnessConfig.tests[i] * 1 - 1;
      if (index < 0)
        continue;
      harnessConfig.testsMask = util.resize(
          harnessConfig.testsMask, index, '0');
      harnessConfig.testsMask += '1';
    }
    harnessConfig.testsMask += '0';
  } else if (harnessConfig.exclude) {
    harnessConfig.exclude = harnessConfig.exclude.split(',').
        map(function(x) {return parseInt(x);}).
        sort(function(a, b) {return a - b;});
    for (var i = 0; i < harnessConfig.exclude.length; ++i) {
      var index = harnessConfig.exclude[i] * 1 - 1;
      if (index < 0)
        continue;
      harnessConfig.testsMask = util.resize(
          harnessConfig.testsMask, index, '1');
      harnessConfig.testsMask += '0';
    }
    harnessConfig.testsMask += '1';
  }

  if (!harnessConfig.testsMask) {
    harnessConfig.testsMask = '1';
  }
};

var reloadPageWithTimestamp = function() {
  var newTimeStamp = (new Date()).getTime();
  if (!/\?/.test(document.URL)) {
    window.location = document.URL + '?timestamp=' + newTimeStamp;
  } else {
    window.location = document.URL + '&timestamp=' + newTimeStamp;
  }
};

var createLogger = function() {
  window.LOG = function() {
    if (!harnessConfig.logging)
      return;
    var output = document.getElementById('output');
    var text = '';

    for (var i = 0; i < arguments.length; ++i)
      text += arguments[i].toString() + ' ';

    console.log(text);
    output.value = text + '\n' + output.value;
  };
};

window.globalRunner = null;

var createRunner = function(testSuite, testSuiteVer, testsMask) {
  var runner = new TestExecutor(testSuite, testsMask, testSuiteVer);

  // Expose the runner so outside/injected scripts can read it.
  window.globalRunner = runner;

  var id = 0;
  runner.getNewVideoTag = function() {
    var testarea = document.getElementById('testarea');
    var vid = 'v' + id;
    if (harnessConfig.recycleVideoTag)
      ++id;
    if (!document.getElementById(vid)) {
      testarea.innerHTML = '';
      testarea.appendChild(util.createElement('video', vid, 'box-right'));
      document.getElementById(vid).controls = true;
    }
    return document.getElementById(vid);
  };

  runner.getControlContainer = function() {
    return document.getElementById('control');
  };

  runner.initialize();
  return runner;
};

window.startMseTest = function(testSuiteVer) {
  setupMsePortability(testSuiteVer);
  var testSuiteVersion = testSuiteVersions[testSuiteVer];
  window.harnessConfig = parseParams(testSuiteVersion.config);
  if (!harnessConfig.timestamp) {
    reloadPageWithTimestamp();
    return;
  }
  if (!testSuiteVersion.testSuites.indexOf(harnessConfig.testType) === -1) {
    alert('Cannot find test type ' + harnessConfig.testType);
    throw 'Cannot find test type ' + harnessConfig.testType;
  }

  configureHarness(testSuiteVersion.config);
  createLogger();

  var testSuite = loadTests(harnessConfig.testType);
  var runner = createRunner(testSuite, testSuiteVer, harnessConfig.testsMask);
  if (harnessConfig.command === 'run')
    runner.startTest(0, runner.testList.length);
};

})();
<commit_msg>Added default for command parameter.<commit_after>/*
Copyright 2017 Google Inc. 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.
*/

// This file create the main interface of the app.
'use strict';

(function() {

var loadTests = function(testType) {
  // We have to make it compatible to the legacy url format.
  var testName = testType.substr(0, testType.indexOf('-'));
  testName = util.MakeCapitalName(testName) + 'Test';
  return window[testName]();
};

var parseParam = function(param, defaultValue) {
  var regex = new RegExp('(\\?|\\&)' + param + '=([-:,\\w]+)', 'g');
  var value = regex.exec(document.URL);
  return value ? value[2] : defaultValue;
};

var parseParams = function(testSuiteConfig) {
  var config = {};
  config.testType = parseParam('test_type', testSuiteConfig.defaultTestSuite);
  config.timestamp = parseParam('timestamp');
  config.command = parseParam('command', '');
  config.timeout = parseParam('timeout', TestBase.timeout);
  config.logging = !util.stringToBoolean(parseParam('disable_log', false));
  config.loop = util.stringToBoolean(parseParam('loop', false));
  config.stoponfailure = util.stringToBoolean(
      parseParam('stoponfailure', false));
  config.enablewebm = util.stringToBoolean(
      parseParam('enablewebm', testSuiteConfig.enablewebm));
  config.tests = parseParam('tests');
  config.exclude = parseParam('exclude');
  config.testsMask = parseParam('tests_mask', '');
  config.testid = parseParam('testid', '');

  // Overloaded run command to support browsers that have limitations on extra
  // parameters. Example usage: command=run:1,2,3
  if (config.command.indexOf(':') != -1) {
    var commandSplit = config.command.split(':');
    config.command = commandSplit[0];
    config.tests = commandSplit[1];
  }
  return config;
};

var configureHarness = function(testSuiteConfig) {
  harnessConfig.controlMediaFormatSelection =
      testSuiteConfig.controlMediaFormatSelection;
  harnessConfig.recycleVideoTag = true;
  TestBase.timeout = harnessConfig.timeout;

  if (harnessConfig.testsMask) {
    harnessConfig.testsMask += '0';
  } else if (harnessConfig.tests) {
    harnessConfig.tests = harnessConfig.tests.split(',').
        map(function(x) {return parseInt(x);}).
        sort(function(a, b) {return a - b;});
    for (var i = 0; i < harnessConfig.tests.length; ++i) {
      var index = harnessConfig.tests[i] * 1 - 1;
      if (index < 0)
        continue;
      harnessConfig.testsMask = util.resize(
          harnessConfig.testsMask, index, '0');
      harnessConfig.testsMask += '1';
    }
    harnessConfig.testsMask += '0';
  } else if (harnessConfig.exclude) {
    harnessConfig.exclude = harnessConfig.exclude.split(',').
        map(function(x) {return parseInt(x);}).
        sort(function(a, b) {return a - b;});
    for (var i = 0; i < harnessConfig.exclude.length; ++i) {
      var index = harnessConfig.exclude[i] * 1 - 1;
      if (index < 0)
        continue;
      harnessConfig.testsMask = util.resize(
          harnessConfig.testsMask, index, '1');
      harnessConfig.testsMask += '0';
    }
    harnessConfig.testsMask += '1';
  }

  if (!harnessConfig.testsMask) {
    harnessConfig.testsMask = '1';
  }
};

var reloadPageWithTimestamp = function() {
  var newTimeStamp = (new Date()).getTime();
  if (!/\?/.test(document.URL)) {
    window.location = document.URL + '?timestamp=' + newTimeStamp;
  } else {
    window.location = document.URL + '&timestamp=' + newTimeStamp;
  }
};

var createLogger = function() {
  window.LOG = function() {
    if (!harnessConfig.logging)
      return;
    var output = document.getElementById('output');
    var text = '';

    for (var i = 0; i < arguments.length; ++i)
      text += arguments[i].toString() + ' ';

    console.log(text);
    output.value = text + '\n' + output.value;
  };
};

window.globalRunner = null;

var createRunner = function(testSuite, testSuiteVer, testsMask) {
  var runner = new TestExecutor(testSuite, testsMask, testSuiteVer);

  // Expose the runner so outside/injected scripts can read it.
  window.globalRunner = runner;

  var id = 0;
  runner.getNewVideoTag = function() {
    var testarea = document.getElementById('testarea');
    var vid = 'v' + id;
    if (harnessConfig.recycleVideoTag)
      ++id;
    if (!document.getElementById(vid)) {
      testarea.innerHTML = '';
      testarea.appendChild(util.createElement('video', vid, 'box-right'));
      document.getElementById(vid).controls = true;
    }
    return document.getElementById(vid);
  };

  runner.getControlContainer = function() {
    return document.getElementById('control');
  };

  runner.initialize();
  return runner;
};

window.startMseTest = function(testSuiteVer) {
  setupMsePortability(testSuiteVer);
  var testSuiteVersion = testSuiteVersions[testSuiteVer];
  window.harnessConfig = parseParams(testSuiteVersion.config);
  if (!harnessConfig.timestamp) {
    reloadPageWithTimestamp();
    return;
  }
  if (!testSuiteVersion.testSuites.indexOf(harnessConfig.testType) === -1) {
    alert('Cannot find test type ' + harnessConfig.testType);
    throw 'Cannot find test type ' + harnessConfig.testType;
  }

  configureHarness(testSuiteVersion.config);
  createLogger();

  var testSuite = loadTests(harnessConfig.testType);
  var runner = createRunner(testSuite, testSuiteVer, harnessConfig.testsMask);
  if (harnessConfig.command === 'run')
    runner.startTest(0, runner.testList.length);
};

})();
<|endoftext|>


<commit_before>// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

/**
 * Parses the key definition syntax used for user keyboard customizations.
 *
 * @constructor
 */
hterm.Parser = function() {
  /**
   * @type {string} The source string.
   */
  this.source = '';

  /**
   * @type {number} The current position.
   */
  this.pos = 0;

  /**
   * @type {?string} The character at the current position.
   */
  this.ch = null;
};

/**
 * @param {string} message
 * @return {!Error}
 */
hterm.Parser.prototype.error = function(message) {
  return new Error('Parse error at ' + this.pos + ': ' + message);
};

/** @return {boolean} */
hterm.Parser.prototype.isComplete = function() {
  return this.pos == this.source.length;
};

/**
 * @param {string} source
 * @param {number=} pos
 */
hterm.Parser.prototype.reset = function(source, pos = 0) {
  this.source = source;
  this.pos = pos;
  this.ch = source.substr(0, 1);
};

/**
 * Parse a key sequence.
 *
 * A key sequence is zero or more of the key modifiers defined in
 * hterm.Parser.identifiers.modifierKeys followed by a key code.  Key
 * codes can be an integer or an identifier from
 * hterm.Parser.identifiers.keyCodes.  Modifiers and keyCodes should be joined
 * by the dash character.
 *
 * An asterisk "*" can be used to indicate that the unspecified modifiers
 * are optional.
 *
 * For example:
 *   A: Matches only an unmodified "A" character.
 *   65: Same as above.
 *   0x41: Same as above.
 *   Ctrl+A: Matches only Ctrl+A.
 *   Ctrl+65: Same as above.
 *   Ctrl+0x41: Same as above.
 *   Ctrl+Shift+A: Matches only Ctrl+Shift+A.
 *   Ctrl+*+A: Matches Ctrl+A, as well as any other key sequence that includes
 *     at least the Ctrl and A keys.
 *
 * @return {!hterm.Keyboard.KeyDown} An object with shift, ctrl, alt, meta,
 *     keyCode properties.
 */
hterm.Parser.prototype.parseKeySequence = function() {
  var rv = {
    keyCode: null
  };

  for (var k in hterm.Parser.identifiers.modifierKeys) {
    rv[hterm.Parser.identifiers.modifierKeys[k]] = false;
  }

  while (this.pos < this.source.length) {
    this.skipSpace();

    var token = this.parseToken();
    if (token.type == 'integer') {
      rv.keyCode = token.value;

    } else if (token.type == 'identifier') {
      var ucValue = token.value.toUpperCase();
      if (ucValue in hterm.Parser.identifiers.modifierKeys &&
          hterm.Parser.identifiers.modifierKeys.hasOwnProperty(ucValue)) {
        var mod = hterm.Parser.identifiers.modifierKeys[ucValue];
        if (rv[mod] && rv[mod] != '*') {
          throw this.error('Duplicate modifier: ' + token.value);
        }
        rv[mod] = true;

      } else if (ucValue in hterm.Parser.identifiers.keyCodes &&
                 hterm.Parser.identifiers.keyCodes.hasOwnProperty(ucValue)) {
        rv.keyCode = hterm.Parser.identifiers.keyCodes[ucValue];

      } else {
        throw this.error('Unknown key: ' + token.value);
      }

    } else if (token.type == 'symbol') {
      if (token.value == '*') {
        for (var id in hterm.Parser.identifiers.modifierKeys) {
          var p = hterm.Parser.identifiers.modifierKeys[id];
          if (!rv[p]) {
            rv[p] =  '*';
          }
        }
      } else {
        throw this.error('Unexpected symbol: ' + token.value);
      }
    } else {
      throw this.error('Expected integer or identifier');
    }

    this.skipSpace();

    if (this.ch !== '-' && this.ch !== '+') {
      break;
    }

    if (rv.keyCode != null) {
      throw this.error('Extra definition after target key');
    }

    this.advance(1);
  }

  if (rv.keyCode == null) {
    throw this.error('Missing target key');
  }

  return rv;
};

/** @return {string} */
hterm.Parser.prototype.parseKeyAction = function() {
  this.skipSpace();

  var token = this.parseToken();

  if (token.type == 'string') {
    return token.value;
  }

  if (token.type == 'identifier') {
    if (token.value in hterm.Parser.identifiers.actions &&
        hterm.Parser.identifiers.actions.hasOwnProperty(token.value)) {
      return hterm.Parser.identifiers.actions[token.value];
    }

    throw this.error('Unknown key action: ' + token.value);
  }

  throw this.error('Expected string or identifier');

};

/** @return {boolean} */
hterm.Parser.prototype.peekString = function() {
  return this.ch == '\'' || this.ch == '"';
};

/** @return {boolean} */
hterm.Parser.prototype.peekIdentifier = function() {
  return !!this.ch.match(/[a-z_]/i);
};

/** @return {boolean} */
hterm.Parser.prototype.peekInteger = function() {
  return !!this.ch.match(/[0-9]/);
};

/** @return {!Object} */
hterm.Parser.prototype.parseToken = function() {
  if (this.ch == '*') {
    var rv = {type: 'symbol', value: this.ch};
    this.advance(1);
    return rv;
  }

  if (this.peekIdentifier()) {
    return {type: 'identifier', value: this.parseIdentifier()};
  }

  if (this.peekString()) {
    return {type: 'string', value: this.parseString()};
  }

  if (this.peekInteger()) {
    return {type: 'integer', value: this.parseInteger()};
  }

  throw this.error('Unexpected token');
};

/** @return {string} */
hterm.Parser.prototype.parseIdentifier = function() {
  if (!this.peekIdentifier()) {
    throw this.error('Expected identifier');
  }

  return this.parsePattern(/[a-z0-9_]+/ig);
};

/** @return {number} */
hterm.Parser.prototype.parseInteger = function() {
  if (this.ch == '0' && this.pos < this.source.length - 1 &&
      this.source.substr(this.pos + 1, 1) == 'x') {
    return parseInt(this.parsePattern(/0x[0-9a-f]+/gi), undefined);
  }

  return parseInt(this.parsePattern(/\d+/g), 10);
};

/**
 * Parse a single or double quoted string.
 *
 * The current position should point at the initial quote character.  Single
 * quoted strings will be treated literally, double quoted will process escapes.
 *
 * TODO(rginda): Variable interpolation.
 *
 * @return {string}
 */
hterm.Parser.prototype.parseString = function() {
  var result = '';

  var quote = this.ch;
  if (quote != '"' && quote != '\'') {
    throw this.error('String expected');
  }

  this.advance(1);

  var re = new RegExp('[\\\\' + quote + ']', 'g');

  while (this.pos < this.source.length) {
    re.lastIndex = this.pos;
    if (!re.exec(this.source)) {
      throw this.error('Unterminated string literal');
    }

    result += this.source.substring(this.pos, re.lastIndex - 1);

    this.advance(re.lastIndex - this.pos - 1);

    if (quote == '"' && this.ch == '\\') {
      this.advance(1);
      result += this.parseEscape();
      continue;
    }

    if (quote == '\'' && this.ch == '\\') {
      result += this.ch;
      this.advance(1);
      continue;
    }

    if (this.ch == quote) {
      this.advance(1);
      return result;
    }
  }

  throw this.error('Unterminated string literal');
};

/**
 * Parse an escape code from the current position (which should point to
 * the first character AFTER the leading backslash.)
 *
 * @return {string}
 */
hterm.Parser.prototype.parseEscape = function() {
  var map = {
    '"': '"',
    '\'': '\'',
    '\\': '\\',
    'a': '\x07',
    'b': '\x08',
    'e': '\x1b',
    'f': '\x0c',
    'n': '\x0a',
    'r': '\x0d',
    't': '\x09',
    'v': '\x0b',
    'x': function() {
      var value = this.parsePattern(/[a-z0-9]{2}/ig);
      return String.fromCharCode(parseInt(value, 16));
    },
    'u': function() {
      var value = this.parsePattern(/[a-z0-9]{4}/ig);
      return String.fromCharCode(parseInt(value, 16));
    }
  };

  if (!(this.ch in map && map.hasOwnProperty(this.ch))) {
    throw this.error('Unknown escape: ' + this.ch);
  }

  var value = map[this.ch];
  this.advance(1);

  if (typeof value == 'function') {
    value = value.call(this);
  }

  return value;
};

/**
 * Parse the given pattern starting from the current position.
 *
 * @param {!RegExp} pattern A pattern representing the characters to span.  MUST
 *   include the "global" RegExp flag.
 * @return {string}
 */
hterm.Parser.prototype.parsePattern = function(pattern) {
  if (!pattern.global) {
    throw this.error('Internal error: Span patterns must be global');
  }

  pattern.lastIndex = this.pos;
  var ary = pattern.exec(this.source);

  if (!ary || pattern.lastIndex - ary[0].length != this.pos) {
    throw this.error('Expected match for: ' + pattern);
  }

  this.pos = pattern.lastIndex - 1;
  this.advance(1);

  return ary[0];
};

/**
 * Advance the current position.
 *
 * @param {number} count
 */
hterm.Parser.prototype.advance = function(count) {
  this.pos += count;
  this.ch = this.source.substr(this.pos, 1);
};

/**
 * @param {string=} expect A list of valid non-whitespace characters to
 *   terminate on.
 * @return {void}
 */
hterm.Parser.prototype.skipSpace = function(expect = undefined) {
  if (!/\s/.test(this.ch)) {
    return;
  }

  var re = /\s+/gm;
  re.lastIndex = this.pos;

  var source = this.source;
  if (re.exec(source)) {
    this.pos = re.lastIndex;
  }

  this.ch = this.source.substr(this.pos, 1);

  if (expect) {
    if (this.ch.indexOf(expect) == -1) {
      throw this.error(`Expected one of ${expect}, found: ${this.ch}`);
    }
  }
};
<commit_msg>eslint: enable radix check for parseInt<commit_after>// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

/**
 * Parses the key definition syntax used for user keyboard customizations.
 *
 * @constructor
 */
hterm.Parser = function() {
  /**
   * @type {string} The source string.
   */
  this.source = '';

  /**
   * @type {number} The current position.
   */
  this.pos = 0;

  /**
   * @type {?string} The character at the current position.
   */
  this.ch = null;
};

/**
 * @param {string} message
 * @return {!Error}
 */
hterm.Parser.prototype.error = function(message) {
  return new Error('Parse error at ' + this.pos + ': ' + message);
};

/** @return {boolean} */
hterm.Parser.prototype.isComplete = function() {
  return this.pos == this.source.length;
};

/**
 * @param {string} source
 * @param {number=} pos
 */
hterm.Parser.prototype.reset = function(source, pos = 0) {
  this.source = source;
  this.pos = pos;
  this.ch = source.substr(0, 1);
};

/**
 * Parse a key sequence.
 *
 * A key sequence is zero or more of the key modifiers defined in
 * hterm.Parser.identifiers.modifierKeys followed by a key code.  Key
 * codes can be an integer or an identifier from
 * hterm.Parser.identifiers.keyCodes.  Modifiers and keyCodes should be joined
 * by the dash character.
 *
 * An asterisk "*" can be used to indicate that the unspecified modifiers
 * are optional.
 *
 * For example:
 *   A: Matches only an unmodified "A" character.
 *   65: Same as above.
 *   0x41: Same as above.
 *   Ctrl+A: Matches only Ctrl+A.
 *   Ctrl+65: Same as above.
 *   Ctrl+0x41: Same as above.
 *   Ctrl+Shift+A: Matches only Ctrl+Shift+A.
 *   Ctrl+*+A: Matches Ctrl+A, as well as any other key sequence that includes
 *     at least the Ctrl and A keys.
 *
 * @return {!hterm.Keyboard.KeyDown} An object with shift, ctrl, alt, meta,
 *     keyCode properties.
 */
hterm.Parser.prototype.parseKeySequence = function() {
  var rv = {
    keyCode: null
  };

  for (var k in hterm.Parser.identifiers.modifierKeys) {
    rv[hterm.Parser.identifiers.modifierKeys[k]] = false;
  }

  while (this.pos < this.source.length) {
    this.skipSpace();

    var token = this.parseToken();
    if (token.type == 'integer') {
      rv.keyCode = token.value;

    } else if (token.type == 'identifier') {
      var ucValue = token.value.toUpperCase();
      if (ucValue in hterm.Parser.identifiers.modifierKeys &&
          hterm.Parser.identifiers.modifierKeys.hasOwnProperty(ucValue)) {
        var mod = hterm.Parser.identifiers.modifierKeys[ucValue];
        if (rv[mod] && rv[mod] != '*') {
          throw this.error('Duplicate modifier: ' + token.value);
        }
        rv[mod] = true;

      } else if (ucValue in hterm.Parser.identifiers.keyCodes &&
                 hterm.Parser.identifiers.keyCodes.hasOwnProperty(ucValue)) {
        rv.keyCode = hterm.Parser.identifiers.keyCodes[ucValue];

      } else {
        throw this.error('Unknown key: ' + token.value);
      }

    } else if (token.type == 'symbol') {
      if (token.value == '*') {
        for (var id in hterm.Parser.identifiers.modifierKeys) {
          var p = hterm.Parser.identifiers.modifierKeys[id];
          if (!rv[p]) {
            rv[p] =  '*';
          }
        }
      } else {
        throw this.error('Unexpected symbol: ' + token.value);
      }
    } else {
      throw this.error('Expected integer or identifier');
    }

    this.skipSpace();

    if (this.ch !== '-' && this.ch !== '+') {
      break;
    }

    if (rv.keyCode != null) {
      throw this.error('Extra definition after target key');
    }

    this.advance(1);
  }

  if (rv.keyCode == null) {
    throw this.error('Missing target key');
  }

  return rv;
};

/** @return {string} */
hterm.Parser.prototype.parseKeyAction = function() {
  this.skipSpace();

  var token = this.parseToken();

  if (token.type == 'string') {
    return token.value;
  }

  if (token.type == 'identifier') {
    if (token.value in hterm.Parser.identifiers.actions &&
        hterm.Parser.identifiers.actions.hasOwnProperty(token.value)) {
      return hterm.Parser.identifiers.actions[token.value];
    }

    throw this.error('Unknown key action: ' + token.value);
  }

  throw this.error('Expected string or identifier');

};

/** @return {boolean} */
hterm.Parser.prototype.peekString = function() {
  return this.ch == '\'' || this.ch == '"';
};

/** @return {boolean} */
hterm.Parser.prototype.peekIdentifier = function() {
  return !!this.ch.match(/[a-z_]/i);
};

/** @return {boolean} */
hterm.Parser.prototype.peekInteger = function() {
  return !!this.ch.match(/[0-9]/);
};

/** @return {!Object} */
hterm.Parser.prototype.parseToken = function() {
  if (this.ch == '*') {
    var rv = {type: 'symbol', value: this.ch};
    this.advance(1);
    return rv;
  }

  if (this.peekIdentifier()) {
    return {type: 'identifier', value: this.parseIdentifier()};
  }

  if (this.peekString()) {
    return {type: 'string', value: this.parseString()};
  }

  if (this.peekInteger()) {
    return {type: 'integer', value: this.parseInteger()};
  }

  throw this.error('Unexpected token');
};

/** @return {string} */
hterm.Parser.prototype.parseIdentifier = function() {
  if (!this.peekIdentifier()) {
    throw this.error('Expected identifier');
  }

  return this.parsePattern(/[a-z0-9_]+/ig);
};

/** @return {number} */
hterm.Parser.prototype.parseInteger = function() {
  if (this.ch == '0' && this.pos < this.source.length - 1 &&
      this.source.substr(this.pos + 1, 1) == 'x') {
    /* eslint-disable radix */
    return parseInt(this.parsePattern(/0x[0-9a-f]+/gi), undefined);
  }

  return parseInt(this.parsePattern(/\d+/g), 10);
};

/**
 * Parse a single or double quoted string.
 *
 * The current position should point at the initial quote character.  Single
 * quoted strings will be treated literally, double quoted will process escapes.
 *
 * TODO(rginda): Variable interpolation.
 *
 * @return {string}
 */
hterm.Parser.prototype.parseString = function() {
  var result = '';

  var quote = this.ch;
  if (quote != '"' && quote != '\'') {
    throw this.error('String expected');
  }

  this.advance(1);

  var re = new RegExp('[\\\\' + quote + ']', 'g');

  while (this.pos < this.source.length) {
    re.lastIndex = this.pos;
    if (!re.exec(this.source)) {
      throw this.error('Unterminated string literal');
    }

    result += this.source.substring(this.pos, re.lastIndex - 1);

    this.advance(re.lastIndex - this.pos - 1);

    if (quote == '"' && this.ch == '\\') {
      this.advance(1);
      result += this.parseEscape();
      continue;
    }

    if (quote == '\'' && this.ch == '\\') {
      result += this.ch;
      this.advance(1);
      continue;
    }

    if (this.ch == quote) {
      this.advance(1);
      return result;
    }
  }

  throw this.error('Unterminated string literal');
};

/**
 * Parse an escape code from the current position (which should point to
 * the first character AFTER the leading backslash.)
 *
 * @return {string}
 */
hterm.Parser.prototype.parseEscape = function() {
  var map = {
    '"': '"',
    '\'': '\'',
    '\\': '\\',
    'a': '\x07',
    'b': '\x08',
    'e': '\x1b',
    'f': '\x0c',
    'n': '\x0a',
    'r': '\x0d',
    't': '\x09',
    'v': '\x0b',
    'x': function() {
      var value = this.parsePattern(/[a-z0-9]{2}/ig);
      return String.fromCharCode(parseInt(value, 16));
    },
    'u': function() {
      var value = this.parsePattern(/[a-z0-9]{4}/ig);
      return String.fromCharCode(parseInt(value, 16));
    }
  };

  if (!(this.ch in map && map.hasOwnProperty(this.ch))) {
    throw this.error('Unknown escape: ' + this.ch);
  }

  var value = map[this.ch];
  this.advance(1);

  if (typeof value == 'function') {
    value = value.call(this);
  }

  return value;
};

/**
 * Parse the given pattern starting from the current position.
 *
 * @param {!RegExp} pattern A pattern representing the characters to span.  MUST
 *   include the "global" RegExp flag.
 * @return {string}
 */
hterm.Parser.prototype.parsePattern = function(pattern) {
  if (!pattern.global) {
    throw this.error('Internal error: Span patterns must be global');
  }

  pattern.lastIndex = this.pos;
  var ary = pattern.exec(this.source);

  if (!ary || pattern.lastIndex - ary[0].length != this.pos) {
    throw this.error('Expected match for: ' + pattern);
  }

  this.pos = pattern.lastIndex - 1;
  this.advance(1);

  return ary[0];
};

/**
 * Advance the current position.
 *
 * @param {number} count
 */
hterm.Parser.prototype.advance = function(count) {
  this.pos += count;
  this.ch = this.source.substr(this.pos, 1);
};

/**
 * @param {string=} expect A list of valid non-whitespace characters to
 *   terminate on.
 * @return {void}
 */
hterm.Parser.prototype.skipSpace = function(expect = undefined) {
  if (!/\s/.test(this.ch)) {
    return;
  }

  var re = /\s+/gm;
  re.lastIndex = this.pos;

  var source = this.source;
  if (re.exec(source)) {
    this.pos = re.lastIndex;
  }

  this.ch = this.source.substr(this.pos, 1);

  if (expect) {
    if (this.ch.indexOf(expect) == -1) {
      throw this.error(`Expected one of ${expect}, found: ${this.ch}`);
    }
  }
};
<|endoftext|>


<commit_before>/*!
 * Hux Blog v1.7.0 (http://huxpro.github.io)
 * Copyright 2017 Hux <huxpro@gmail.com>
 */

$(document).ready(function(){$("table").wrap("

"),$("table").addClass("table")}),$(document).ready(function(){$('iframe[src*="youtube.com"]').wrap('

'),$('iframe[src*="youtube.com"]').addClass("embed-responsive-item"),$('iframe[src*="vimeo.com"]').wrap('

'),$('iframe[src*="vimeo.com"]').addClass("embed-responsive-item")}),jQuery(document).ready(function(a){var b=1170;if(a(window).width()>b){var c=a(".navbar-custom").height(),d=a(".intro-header .container").height();a(window).on("scroll",{previousTop:0},function(){var b=a(window).scrollTop(),e=a(".side-catalog");b<this.previousTop?b>0&&a(".navbar-custom").hasClass("is-fixed")?a(".navbar-custom").addClass("is-visible"):a(".navbar-custom").removeClass("is-visible is-fixed"):(a(".navbar-custom").removeClass("is-visible"),b>c&&!a(".navbar-custom").hasClass("is-fixed")&&a(".navbar-custom").addClass("is-fixed")),this.previousTop=b,e.show(),b>d+41?e.addClass("fixed"):e.removeClass("fixed")})}});<commit_msg>hux.min.js add baidu<commit_after>/*!
 * Hux Blog v1.7.0 (http://huxpro.github.io)
 * Copyright 2017 Hux <huxpro@gmail.com>
 */

$(document).ready(function(){$("table").wrap("

"),$("table").addClass("table")}),$(document).ready(function(){$('iframe[src*="youtube.com"]').wrap('

'),$('iframe[src*="youtube.com"]').addClass("embed-responsive-item"),$('iframe[src*="vimeo.com"]').wrap('

'),$('iframe[src*="vimeo.com"]').addClass("embed-responsive-item")}),jQuery(document).ready(function(a){var b=1170;if(a(window).width()>b){var c=a(".navbar-custom").height(),d=a(".intro-header .container").height();a(window).on("scroll",{previousTop:0},function(){var b=a(window).scrollTop(),e=a(".side-catalog");b<this.previousTop?b>0&&a(".navbar-custom").hasClass("is-fixed")?a(".navbar-custom").addClass("is-visible"):a(".navbar-custom").removeClass("is-visible is-fixed"):(a(".navbar-custom").removeClass("is-visible"),b>c&&!a(".navbar-custom").hasClass("is-fixed")&&a(".navbar-custom").addClass("is-fixed")),this.previousTop=b,e.show(),b>d+41?e.addClass("fixed"):e.removeClass("fixed")})}});

$(function(){
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
        bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
    }
    else {
        bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
});<|endoftext|>


<commit_before>(function($, window, document, undefined){
    $.widget("qcode.dbRow", {
	options: {
	    'type': 'update'
	},
	_create: function(){
	    this.state = 'current';
	    this.error = undefined;
            // AJAX headers
            this.headers = {
                Accept: "application/json,text/xml"
            }
	},
	getGrid: function(){
	    return this.element.closest('table');
	},
	getColgroup: function(){
	    return this.getGrid().children('colgroup');
	},
	getCurrentCell: function() {
	    return this.getGrid().dbGrid('getCurrentCell');
	},
	getState: function(){
	    return this.state;
	},
	getRowData: function(){
	    // Return object with name/value pairs of row data
	    var data = {};
	    this.element.children('td').each(function() {
		var cell = $(this);
		var colName = cell.dbCell('getCol').attr('name');
		var cellValue = cell.dbCell('getValue');
		data[colName] = cellValue;
	    });
	    return data;
	},
	setState: function(newState){
	    // Set the state of this row
	    var grid = this.getGrid();
	    var oldState = this.state;
	    var message;
            var messageType = 'notice';

	    switch (newState) {
	    case 'dirty':
		if ( oldState === 'current' && this.option('type') === 'add' ) {
		    // Append New Row
		    grid.dbGrid('createNewRow');
		}
		if ( oldState === 'current' || oldState === 'error' ) {
		    var span = $('').text('save').click(this.save.bind(this)).addClass('action save');
		    var message = $('').text('Editing ... to ').append(span).append(', type Ctrl+S');
		}
		break;
	    case 'current':
		message = "Saved.";
		break;
	    case 'updating':
		message = "Updating ...";
		break;
	    case 'error':
		message = this.error;
                messageType = 'error';
		break;
	    default:
		$.error('Invalid state');
		return;
	    }

	    this.element.removeClass("current dirty updating error");
	    this.element.addClass(newState);
            // Notify plugins such as statusFrame
            this.element.trigger('message', [{
                type: messageType,
                html: message
            }]);
	    this.state = newState;
	    this.getCurrentCell().dbCell('editor', 'repaint');
	    this.element.trigger('dbRowStateChange');
	},
	rowIn: function(){
	    // Update NavCounter and statusBarMsg
	    var row = this.element;
	    var grid = this.getGrid();

	    // Custom Event: Trigger any dbRowIn events bound to this table
	    row.trigger('dbRowIn');

	    if ( this.error ) {
                this.element.trigger('message', [{
                    type: 'error',
                    html: this.error
                }]);
	    }
	    grid.dbGrid('setNavCounter', row.index());
	},
	rowOut: function(){
	    // Save row if dirty
	    // Custom Event: Trigger any dbRowOut events bound to this table
	    this.element.trigger('dbRowOut');

	    if ( this.state === 'dirty' ) {
		this.save();
	    }
	},
	save: function(async){
	    // Save this row, using an add or update url as appropriate
	    var grid = this.getGrid();

	    if ( this.state === "updating" ) {
		return false;
	    }
	    switch(this.option('type')){
	    case "update":
		var url = grid.dbGrid('option', "updateURL");
		break;
	    case "add":
		var url = grid.dbGrid('option', "addURL");
		break;
	    }
	    if ( ! url ) {
		$.error('Could not ' + this.option('type') + ' record - no url provided');
	    }
	    this.action(this.option('type'), url, async);
	},
	action: function(action, url, async){
	    // Perform the given action (add, update, delete), by submitting row data to the server.
	    var grid = this.getGrid();

	    async = coalesce(async, true);
	    if ( action === 'add' || action === 'update' || action === 'delete' ) {
		this.setState('updating');
	    }
	    grid.dbGrid('getCurrentCell').dbCell('write');

	    var urlPieces = splitURL(url);
	    var path = urlPieces.path;
	    var data = $.extend(urlPieces.data, this.getRowData());
	    httpPost(path, data, this.actionReturn.bind(this, action), this.actionReturnError.bind(this, action), async, this.headers);
	    this.element.trigger('dbRowAction', [action]);
	},
	actionReturn: function(action, data, jqXHR){
	    // Called on successful return from a server action (add, update or delete)
	    var grid = this.getGrid();
            var contentType = jqXHR.getResponseHeader('Content-Type');

            switch(contentType) {
            case 'application/json; charset=utf-8':
                // JSON response
                // Check if redirect action given
                if ( data.action && data.action.redirect ) {
                    window.location.href = data.action.redirect.value;
                    return;
                }

                // Set row values if response valid otherwise feedback errors.
                if ( data.status === 'valid' ) {
                    this.jsonSetValues(data);
                } else {
                    // User errors
                    this.error = this._jsonParseErrors(data);
                    this.setState('error');
                    return;
                }
                break;
            case 'text/xml; charset=utf-8':
                // XML response
                // Check for user errors
                var xmlError = $(data).find('error').first();
                if ( xmlError.length == 1 ) {
                    this.error = xmlError.text();
                    this.setState('error');
                    return;
                } else {
                    // No errors - set row values
                    this.xmlSetValues(data);
                }
                break;
            default:
                // Not XML or JSON
                this.error = 'Expected XML or JSON but got ' + contentType;
                this.setState('error');
                qcode.alert(this.error);
                return;
            }

	    this.error = undefined;

	    switch(action) {
	    case 'update':
		this.setState('current');
		break;
	    case 'add':
		// Once added, a record becomes an updatable record
		this.option('type', 'update');
		this.setState('current');
		grid.dbGrid('incrRecCount', 1);
		break;
	    }

	    // For add and update, we want to handle incoming data before triggering event handlers. For delete, we want event handlers to trigger first.
	    this.element.trigger('dbRowActionReturn', [action, data, jqXHR]);

	    if ( action == 'delete' ) {
		// When a record is deleted, remove it from the DOM.
		grid.dbGrid('removeRow',this.element)
		grid.dbGrid('incrRecCount', -1);
                grid.trigger('message', [{
                    type: 'notice',
                    html: 'Deleted.'
                }]);
		this.destroy();
	    }
	},
	actionReturnError: function(action, errorType, data, jqXHR) {
            // Handler for errors returned from server.
            switch(errorType) {
            case "NAVIGATION":
                return;
            default:
                var contentType = jqXHR.getResponseHeader('Content-Type');

                switch(contentType) {
                case "application/json; charset=utf-8":
                    // JSON response
                    this.error = this._jsonParseErrors(data);
                    break;
                case "text/xml; charset=utf-8":
                    // XML response
                    var xmlError = $(data).find('error').first();
                    if ( xmlError.length == 1 ) {
                        this.error = xmlError.text();
                    }
                    break;
                default:
                    this.error = data;
                }
            }

            this.setState('error');
            qcode.alert(this.error);
	},
	xmlSetValues: function(xmlDoc) {
	    // Update row, calculated & external html values,
	    // Display info and alert messages
	    var grid = this.getGrid();
	    var currentCell = grid.dbGrid('getCurrentCell')
	    var dbRow = this;

	    // Update row with record values in xmlDoc response
	    var rec = $('records record', xmlDoc).first();
	    if ( rec.size() ) {
		rec.children().each(function() {
		    var xmlNode = $(this);
		    var colName = xmlNode.prop('nodeName');
		    var value = xmlNode.text()
		    dbRow.setCellValue(colName, value);
		});
		if ( currentCell.size() && this.element.find(currentCell).size() ) {
		    currentCell.dbCell('cellIn', 'preserve');
		}
	    }

	    // Update 'Calculated' elements within grid
	    $('records > calculated', xmlDoc).children().each(function() {
		xmlNode = $(this);
		var id = xmlNode.prop('nodeName');
		var value = xmlNode.text();
		$('#' + id, grid).setObjectValue(value);
	    });

	    // Update html elements external to the grid
	    $('records > html', xmlDoc).children().each(function() {
		xmlNode = $(this);
		var id = xmlNode.prop('nodeName');
		var value = xmlNode.text();
		behave(
                    $('#' + id + ',[name="' + id + '"]').setObjectValue(value)
                );
	    });

	    // Display info message in statusBar
	    var xmlNode = $('records > info', xmlDoc);
	    if ( xmlNode.size() ) {
                this.element.trigger('message', [{
                    type: 'info',
                    html: xmlNode.text()
                }]);
	    }

	    // Alert
	    var xmlNode = $('records > alert', xmlDoc);
	    if ( xmlNode.size() ) {
		qcode.alert(xmlNode.text());
	    }
	},
        jsonSetValues: function(json) {
            // Update row, calculated & external html values, and display messages.
            var grid = this.getGrid();
            var currentCell = grid.dbGrid('getCurrentCell');
            var dbRow = this;

            // Update row record values
            if ( json.record ) {
                $.each(json.record, function(name, properties) {
                    dbRow.setCellValue(name, properties.value);
                });

                if ( currentCell.size() && this.element.find(currentCell).size() ) {
		    currentCell.dbCell('cellIn', 'end');
		}
            }

            // Update 'calculated' elements
            if ( json.calculated ) {
                $.each(json.calculated, function(name, value) {
                    $('#' + name, grid).setObjectValue(value);
                });
            }

            // Update html elements outwith the grid
            if ( json.html ) {
                $.each(json.html, function(name, value) {
                    behave(
                        $('#' + name + ',[name=' + name + ']').setObjectValue(value)
                    );
                });
            }

            // Display messages
            var element = this.element;
            if ( json.message && (!json.action || !json.action.redirect) ) {
                $.each(json.message, function(type, properties) {
                    switch(type) {
                    case 'notify':
                        element.trigger('message', [{
                            type: 'info',
                            html: properties.value
                        }]);
                        break;
                    case 'alert':
                        qcode.alert(properties.value);
                        break;
                    }
                });
            }
        },
	setCellValue: function(colName, value){
	    // Set the value of the cell corresponding to colName.
	    var colIndex = $('col[name='+colName+']', this.getColgroup()).index();
	    if ( colIndex !== -1 ) {
		var cell = this.element.children('td').eq(colIndex);
		cell.dbCell('setValue',value);
	    }
	},
	"delete": function(async){
	    var grid = this.getGrid();
	    var url = grid.dbGrid('option', 'deleteURL');
	    if ( ! url ) {
		$.error('Could not delete record - no url provided');
	    }
	    this.action('delete', url, async);
	},
        _jsonParseErrors: function(json) {
            // Parses error messages in the JSON object and returns an HTML formatted string.
            var errors = [];

            // Add messages from invalid record items.
            $.each(json.record, function(name, properties) {
                if ( !properties.valid ) {
                    errors.push(properties.message);
                }
            });

            // Check for error message
            if ( json.message && json.message.error ) {
                errors.push(json.message.error.value);
            }

            if ( errors.length == 1 ) {
                // Return the error
                return errors[0];
            } else if ( errors.length > 1 ) {
                // Return errors as an unordered list
                var errorList = $('

');
                $.each(errors, function(index, value) {
                    errorList.append('
' + value + '
')
                });
                return errorList;
            } else {
                throw "No error descriptions found!"
            }
        }
    });
})(jQuery, window, document);
<commit_msg>Add a class to the row being edited.<commit_after>(function($, window, document, undefined){
    $.widget("qcode.dbRow", {
	options: {
	    'type': 'update'
	},
	_create: function(){
	    this.state = 'current';
	    this.error = undefined;
            // AJAX headers
            this.headers = {
                Accept: "application/json,text/xml"
            }
	},
	getGrid: function(){
	    return this.element.closest('table');
	},
	getColgroup: function(){
	    return this.getGrid().children('colgroup');
	},
	getCurrentCell: function() {
	    return this.getGrid().dbGrid('getCurrentCell');
	},
	getState: function(){
	    return this.state;
	},
	getRowData: function(){
	    // Return object with name/value pairs of row data
	    var data = {};
	    this.element.children('td').each(function() {
		var cell = $(this);
		var colName = cell.dbCell('getCol').attr('name');
		var cellValue = cell.dbCell('getValue');
		data[colName] = cellValue;
	    });
	    return data;
	},
	setState: function(newState){
	    // Set the state of this row
	    var grid = this.getGrid();
	    var oldState = this.state;
	    var message;
            var messageType = 'notice';

	    switch (newState) {
	    case 'dirty':
		if ( oldState === 'current' && this.option('type') === 'add' ) {
		    // Append New Row
		    grid.dbGrid('createNewRow');
		}
		if ( oldState === 'current' || oldState === 'error' ) {
		    var span = $('').text('save').click(this.save.bind(this)).addClass('action save');
		    var message = $('').text('Editing ... to ').append(span).append(', type Ctrl+S');
		}
		break;
	    case 'current':
		message = "Saved.";
		break;
	    case 'updating':
		message = "Updating ...";
		break;
	    case 'error':
		message = this.error;
                messageType = 'error';
		break;
	    default:
		$.error('Invalid state');
		return;
	    }

	    this.element.removeClass("current dirty updating error");
	    this.element.addClass(newState);
            // Notify plugins such as statusFrame
            this.element.trigger('message', [{
                type: messageType,
                html: message
            }]);
	    this.state = newState;
	    this.getCurrentCell().dbCell('editor', 'repaint');
	    this.element.trigger('dbRowStateChange');
	},
	rowIn: function(){
	    // Update NavCounter and statusBarMsg
	    var row = this.element;
	    var grid = this.getGrid();

            row.addClass('editing');

	    // Custom Event: Trigger any dbRowIn events bound to this table
	    row.trigger('dbRowIn');

	    if ( this.error ) {
                this.element.trigger('message', [{
                    type: 'error',
                    html: this.error
                }]);
	    }
	    grid.dbGrid('setNavCounter', row.index());
	},
	rowOut: function(){
	    // Save row if dirty

            this.element.removeClass('editing');

            // Custom Event: Trigger any dbRowOut events bound to this table
	    this.element.trigger('dbRowOut');

	    if ( this.state === 'dirty' ) {
		this.save();
	    }
	},
	save: function(async){
	    // Save this row, using an add or update url as appropriate
	    var grid = this.getGrid();

	    if ( this.state === "updating" ) {
		return false;
	    }
	    switch(this.option('type')){
	    case "update":
		var url = grid.dbGrid('option', "updateURL");
		break;
	    case "add":
		var url = grid.dbGrid('option', "addURL");
		break;
	    }
	    if ( ! url ) {
		$.error('Could not ' + this.option('type') + ' record - no url provided');
	    }
	    this.action(this.option('type'), url, async);
	},
	action: function(action, url, async){
	    // Perform the given action (add, update, delete), by submitting row data to the server.
	    var grid = this.getGrid();

	    async = coalesce(async, true);
	    if ( action === 'add' || action === 'update' || action === 'delete' ) {
		this.setState('updating');
	    }
	    grid.dbGrid('getCurrentCell').dbCell('write');

	    var urlPieces = splitURL(url);
	    var path = urlPieces.path;
	    var data = $.extend(urlPieces.data, this.getRowData());
	    httpPost(path, data, this.actionReturn.bind(this, action), this.actionReturnError.bind(this, action), async, this.headers);
	    this.element.trigger('dbRowAction', [action]);
	},
	actionReturn: function(action, data, jqXHR){
	    // Called on successful return from a server action (add, update or delete)
	    var grid = this.getGrid();
            var contentType = jqXHR.getResponseHeader('Content-Type');

            switch(contentType) {
            case 'application/json; charset=utf-8':
                // JSON response
                // Check if redirect action given
                if ( data.action && data.action.redirect ) {
                    window.location.href = data.action.redirect.value;
                    return;
                }

                // Set row values if response valid otherwise feedback errors.
                if ( data.status === 'valid' ) {
                    this.jsonSetValues(data);
                } else {
                    // User errors
                    this.error = this._jsonParseErrors(data);
                    this.setState('error');
                    return;
                }
                break;
            case 'text/xml; charset=utf-8':
                // XML response
                // Check for user errors
                var xmlError = $(data).find('error').first();
                if ( xmlError.length == 1 ) {
                    this.error = xmlError.text();
                    this.setState('error');
                    return;
                } else {
                    // No errors - set row values
                    this.xmlSetValues(data);
                }
                break;
            default:
                // Not XML or JSON
                this.error = 'Expected XML or JSON but got ' + contentType;
                this.setState('error');
                qcode.alert(this.error);
                return;
            }

	    this.error = undefined;

	    switch(action) {
	    case 'update':
		this.setState('current');
		break;
	    case 'add':
		// Once added, a record becomes an updatable record
		this.option('type', 'update');
		this.setState('current');
		grid.dbGrid('incrRecCount', 1);
		break;
	    }

	    // For add and update, we want to handle incoming data before triggering event handlers. For delete, we want event handlers to trigger first.
	    this.element.trigger('dbRowActionReturn', [action, data, jqXHR]);

	    if ( action == 'delete' ) {
		// When a record is deleted, remove it from the DOM.
		grid.dbGrid('removeRow',this.element)
		grid.dbGrid('incrRecCount', -1);
                grid.trigger('message', [{
                    type: 'notice',
                    html: 'Deleted.'
                }]);
		this.destroy();
	    }
	},
	actionReturnError: function(action, errorType, data, jqXHR) {
            // Handler for errors returned from server.
            switch(errorType) {
            case "NAVIGATION":
                return;
            default:
                var contentType = jqXHR.getResponseHeader('Content-Type');

                switch(contentType) {
                case "application/json; charset=utf-8":
                    // JSON response
                    this.error = this._jsonParseErrors(data);
                    break;
                case "text/xml; charset=utf-8":
                    // XML response
                    var xmlError = $(data).find('error').first();
                    if ( xmlError.length == 1 ) {
                        this.error = xmlError.text();
                    }
                    break;
                default:
                    this.error = data;
                }
            }

            this.setState('error');
            qcode.alert(this.error);
	},
	xmlSetValues: function(xmlDoc) {
	    // Update row, calculated & external html values,
	    // Display info and alert messages
	    var grid = this.getGrid();
	    var currentCell = grid.dbGrid('getCurrentCell')
	    var dbRow = this;

	    // Update row with record values in xmlDoc response
	    var rec = $('records record', xmlDoc).first();
	    if ( rec.size() ) {
		rec.children().each(function() {
		    var xmlNode = $(this);
		    var colName = xmlNode.prop('nodeName');
		    var value = xmlNode.text()
		    dbRow.setCellValue(colName, value);
		});
		if ( currentCell.size() && this.element.find(currentCell).size() ) {
		    currentCell.dbCell('cellIn', 'preserve');
		}
	    }

	    // Update 'Calculated' elements within grid
	    $('records > calculated', xmlDoc).children().each(function() {
		xmlNode = $(this);
		var id = xmlNode.prop('nodeName');
		var value = xmlNode.text();
		$('#' + id, grid).setObjectValue(value);
	    });

	    // Update html elements external to the grid
	    $('records > html', xmlDoc).children().each(function() {
		xmlNode = $(this);
		var id = xmlNode.prop('nodeName');
		var value = xmlNode.text();
		behave(
                    $('#' + id + ',[name="' + id + '"]').setObjectValue(value)
                );
	    });

	    // Display info message in statusBar
	    var xmlNode = $('records > info', xmlDoc);
	    if ( xmlNode.size() ) {
                this.element.trigger('message', [{
                    type: 'info',
                    html: xmlNode.text()
                }]);
	    }

	    // Alert
	    var xmlNode = $('records > alert', xmlDoc);
	    if ( xmlNode.size() ) {
		qcode.alert(xmlNode.text());
	    }
	},
        jsonSetValues: function(json) {
            // Update row, calculated & external html values, and display messages.
            var grid = this.getGrid();
            var currentCell = grid.dbGrid('getCurrentCell');
            var dbRow = this;

            // Update row record values
            if ( json.record ) {
                $.each(json.record, function(name, properties) {
                    dbRow.setCellValue(name, properties.value);
                });

                if ( currentCell.size() && this.element.find(currentCell).size() ) {
		    currentCell.dbCell('cellIn', 'end');
		}
            }

            // Update 'calculated' elements
            if ( json.calculated ) {
                $.each(json.calculated, function(name, value) {
                    $('#' + name, grid).setObjectValue(value);
                });
            }

            // Update html elements outwith the grid
            if ( json.html ) {
                $.each(json.html, function(name, value) {
                    behave(
                        $('#' + name + ',[name=' + name + ']').setObjectValue(value)
                    );
                });
            }

            // Display messages
            var element = this.element;
            if ( json.message && (!json.action || !json.action.redirect) ) {
                $.each(json.message, function(type, properties) {
                    switch(type) {
                    case 'notify':
                        element.trigger('message', [{
                            type: 'info',
                            html: properties.value
                        }]);
                        break;
                    case 'alert':
                        qcode.alert(properties.value);
                        break;
                    }
                });
            }
        },
	setCellValue: function(colName, value){
	    // Set the value of the cell corresponding to colName.
	    var colIndex = $('col[name='+colName+']', this.getColgroup()).index();
	    if ( colIndex !== -1 ) {
		var cell = this.element.children('td').eq(colIndex);
		cell.dbCell('setValue',value);
	    }
	},
	"delete": function(async){
	    var grid = this.getGrid();
	    var url = grid.dbGrid('option', 'deleteURL');
	    if ( ! url ) {
		$.error('Could not delete record - no url provided');
	    }
	    this.action('delete', url, async);
	},
        _jsonParseErrors: function(json) {
            // Parses error messages in the JSON object and returns an HTML formatted string.
            var errors = [];

            // Add messages from invalid record items.
            $.each(json.record, function(name, properties) {
                if ( !properties.valid ) {
                    errors.push(properties.message);
                }
            });

            // Check for error message
            if ( json.message && json.message.error ) {
                errors.push(json.message.error.value);
            }

            if ( errors.length == 1 ) {
                // Return the error
                return errors[0];
            } else if ( errors.length > 1 ) {
                // Return errors as an unordered list
                var errorList = $('

');
                $.each(errors, function(index, value) {
                    errorList.append('
' + value + '
')
                });
                return errorList;
            } else {
                throw "No error descriptions found!"
            }
        }
    });
})(jQuery, window, document);
<|endoftext|>


<commit_before>/*global lib, JSON */
lib.saveable = function (_public, _protected, key) {
    if (!JSON) {
        throw new Error("JSON does not exist");
    }

    _public.save = function () {
        return lib.storage.setItem(key, JSON.stringify(_public.attributes()));
    };

    _public.fetch = function () {
        var data = lib.storage.getItem(key);
        if (data) {
            data = JSON.parse(data);
        }
        return data;
    };

    _public.loadWithDefaults = function (initialData) {
        var saved = _public.fetch();

        if (saved) {
            _public.attributes(saved);
        } else {
            _public.attributes(initialData);
            _public.save();
        }
    };

    _public.clearStorage = function () {
        lib.storage.setItem(key, undefined);
    };

    return _public;
};
<commit_msg>Add saveable#mergeNewest that takes timestamps into account.<commit_after>/*global lib, JSON */
lib.saveable = function (_public, _protected, key) {
    if (!JSON) {
        throw new Error("JSON does not exist");
    }

    _public.save = function () {
        return lib.storage.setItem(key, JSON.stringify(_public.attributes()));
    };

    _public.fetch = function () {
        var data = lib.storage.getItem(key);
        if (data) {
            data = JSON.parse(data);
        }
        return data;
    };

    _public.loadWithDefaults = function (initialData) {
        var saved = _public.fetch();

        if (saved) {
            _public.attributes(saved);
        } else {
            _public.attributes(initialData);
            _public.save();
        }
    };

    // Only integrate and save the hash of 'timestampedData' if its fetched_at
    // property is a more recent (integer) timestamp than that which is already saved.
    _public.mergeNewest = function (timestampedData) {
        var saved = _public.fetch();

        if (!saved || !saved.fetched_at || saved.fetched_at < timestampedData.fetched_at) {
            _public.attributes(timestampedData);
            _public.save();
        } else {
            _public.attributes(saved);
        }
    };

    _public.clearStorage = function () {
        lib.storage.setItem(key, undefined);
    };

    return _public;
};
<|endoftext|>


<commit_before>/**
 * Copyright 2014 IBM Corp. 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.
 */
/*global $:false */

var utils = require('./utils');
var Microphone = require('./Microphone');

// Mini WS callback API, so we can initialize
// with model and token in URI, plus
// start message
exports.initSocket = function(options, onlistening, onmessage, onerror) {
  var model = options.model || 'en-US_BroadbandModel';
  var token = options.token;
  var message = options.message || {'action': 'start'};
  var url = options.serviceURI || 'wss://stream-s.watsonplatform.net/speech-to-text-beta/api/v1/recognize?watson-token='
    + token
    + '&model=' + model;
  console.log('URL model', model);
  var socket = new WebSocket(url);
  socket.onopen = function(evt) {
    console.log('ws opened');
    socket.send(JSON.stringify(message));
  };
  socket.onmessage = function(evt) {
    var msg = JSON.parse(evt.data);
    console.log('evt', evt);
    if (msg.state === 'listening') {
      onlistening(socket);
    }
    onmessage(msg, socket);
  };
  socket.onerror = function(evt) {
    var err = evt.data;
    onerror(err, socket);
  };
}

<commit_msg>Factor out socket initialization into separate module<commit_after>/**
 * Copyright 2014 IBM Corp. 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.
 */
/*global $:false */

var utils = require('./utils');
var Microphone = require('./Microphone');

// Mini WS callback API, so we can initialize
// with model and token in URI, plus
// start message
exports.initSocket = function(options, onlistening, onmessage, onerror) {
  function withDefault(val, defaultVal) {
    return typeof val === 'undefined' ? defaultVal : val;
  }
  var token = options.token;
  var model = options.model || 'en-US_BroadbandModel';
  var message = options.message || {'action': 'start'};
  var sessionPermissions = withDefault(options.sessionPermissions, JSON.parse(localStorage.getItem('sessionPermissions')));
  var sessionPermissionsQueryParam = sessionPermissions ? '0' : '1';
  var url = options.serviceURI || 'wss://stream-s.watsonplatform.net/speech-to-text-beta/api/v1/recognize?watson-token='
    + token
    + '&X-WDC-PL-OPT-OUT=' + sessionPermissionsQueryParam
    + '&model=' + model;
  console.log('URL model', model);
  var socket = new WebSocket(url);
  socket.onopen = function(evt) {
    console.log('ws opened');
    socket.send(JSON.stringify(message));
  };
  socket.onmessage = function(evt) {
    var msg = JSON.parse(evt.data);
    console.log('evt', evt);
    if (msg.state === 'listening') {
      onlistening(socket);
    }
    onmessage(msg, socket);
  };
  socket.onerror = function(evt) {
    var err = evt.data;
    onerror(err, socket);
  };
}

<|endoftext|>


<commit_before>var _ = require('underscore')
  , EventEmitter = require('events').EventEmitter
  , inherits = require('util').inherits
  , async = require('async')
  , pcmUtils = require('pcm-boilerplate')
  , utils = require('./utils')
  , constants = require('./constants')
  , BLOCK_SIZE = constants.BLOCK_SIZE
  , AudioBuffer = require('audiobuffer')
  , AudioDestinationNode = require('./AudioDestinationNode')
  , AudioBufferSourceNode = require('./AudioBufferSourceNode')
  , GainNode = require('./GainNode')
  , ScriptProcessorNode = require('./ScriptProcessorNode')
  , Speaker

try {
  Speaker = require('speaker')
} catch(err) {
  console.warn('Speaker is not available, fix this if you need sound playback.')
}

var AudioContext = module.exports = function() {
  var self = this
    , outBuff

  /*Object.defineProperty(this, 'currentTime', {
    writable: false,
    get: function() {}
  })*/

  Object.defineProperty(this, 'destination', {
    writable: false,
    value: new AudioDestinationNode(this)
  })

  /*Object.defineProperty(this, 'sampleRate', {
    writable: false,
    value: {} // TODO
  })

  Object.defineProperty(this, 'listener', {
    writable: false,
    value: {} // TODO
  })*/
  this.currentTime = 0
  this.sampleRate = 44100

  // Tools for encoding the audio received to PCM data
  var format = {
    numberOfChannels: 2,  // 2 channels
    bitDepth: 16,         // 16-bit samples
    sampleRate: 44100     // 44,100 Hz sample rate
  }

  if (Speaker) this.outStream = new Speaker(format)
  else this.outStream = null
  this._encoder = pcmUtils.BufferEncoder(format)
  this._frame = 0
  this._playing = true

  // When a new connection is established, start to pull audio
  this.destination._inputs[0].on('connection', function() {
    if (!self.outStream) throw new Error('you need to set outStream to send the audio somewhere')
    async.whilst(
      function() { return self._playing },
      function(next) {
        outBuff = self.destination._tick()
        // If there is space in the output stream's buffers, we write,
        // otherwise we wait for 'drain'
        self._frame += BLOCK_SIZE
        self.currentTime = self._frame * 1/self.sampleRate
        if (self.outStream.write(self._encoder(outBuff._data))) next()
        else self.outStream.once('drain', next)
      },
      function(err) {
        if (err) return self.emit('error', err)
      }
    )
  })
}
inherits(AudioContext, EventEmitter)

_.extend(AudioContext.prototype, {

  createBuffer: function(numberOfChannels, length, sampleRate) {
    return AudioBuffer.zeros(numberOfChannels, length, sampleRate)
  },

  decodeAudioData: function(audioData, successCallback, errorCallback) {
    utils.decodeAudioData(audioData, function(err, audioBuffer) {
      if (err) errorCallback(err)
      else successCallback(audioBuffer)
    })
  },

  createBufferSource: function() {
    return new AudioBufferSourceNode(this)
  },

  createGain: function() {
    return new GainNode(this)
  },

  createScriptProcessor: function(bufferSize, numberOfInputChannels, numberOfOutputChannels) {
    return new ScriptProcessorNode(this, bufferSize, numberOfInputChannels, numberOfOutputChannels)
  },

  /*
{

    readonly attribute AudioDestinationNode destination;
    readonly attribute float sampleRate;
    readonly attribute double currentTime;
    readonly attribute AudioListener listener;

    // AudioNode creation
    MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement);

    MediaStreamAudioSourceNode createMediaStreamSource(MediaStream mediaStream);
    MediaStreamAudioDestinationNode createMediaStreamDestination();

    AnalyserNode createAnalyser();
    DelayNode createDelay(optional double maxDelayTime = 1.0);
    BiquadFilterNode createBiquadFilter();
    WaveShaperNode createWaveShaper();
    PannerNode createPanner();
    ConvolverNode createConvolver();

    ChannelSplitterNode createChannelSplitter(optional unsigned long numberOfOutputs = 6);
    ChannelMergerNode createChannelMerger(optional unsigned long numberOfInputs = 6);

    DynamicsCompressorNode createDynamicsCompressor();

    OscillatorNode createOscillator();
    PeriodicWave createPeriodicWave(Float32Array real, Float32Array imag);

}
  */

  _kill: function() {
    var self = this
    this._playing = false
    if (self.outStream.close) self.outStream.close()
    else self.outStream.end()
  },

  collectNodes: function(node, allNodes) {
    var self = this
    allNodes = allNodes || []
    node = node || this.destination
    _.chain(node._inputs)
      .pluck('sources')
      .reduce(function(all, sources) { return all.concat(sources) }, [])
      .pluck('node').value()
      .forEach(function(upstreamNode) {
        if (!_.contains(allNodes, upstreamNode)) {
          allNodes.push(upstreamNode)
          self.collectNodes(upstreamNode, allNodes)
        }
      })
    return allNodes
  }

})

<commit_msg>Fixes runtime-error and performance issue<commit_after>var _ = require('underscore')
  , EventEmitter = require('events').EventEmitter
  , inherits = require('util').inherits
  , async = require('async')
  , pcmUtils = require('pcm-boilerplate')
  , utils = require('./utils')
  , constants = require('./constants')
  , BLOCK_SIZE = constants.BLOCK_SIZE
  , AudioBuffer = require('audiobuffer')
  , AudioDestinationNode = require('./AudioDestinationNode')
  , AudioBufferSourceNode = require('./AudioBufferSourceNode')
  , GainNode = require('./GainNode')
  , ScriptProcessorNode = require('./ScriptProcessorNode')
  , Speaker

try {
  Speaker = require('speaker')
} catch(err) {
  console.warn('Speaker is not available, fix this if you need sound playback.')
}

var AudioContext = module.exports = function() {
  var self = this
    , outBuff

  /*Object.defineProperty(this, 'currentTime', {
    writable: false,
    get: function() {}
  })*/

  Object.defineProperty(this, 'destination', {
    writable: false,
    value: new AudioDestinationNode(this)
  })

  /*Object.defineProperty(this, 'sampleRate', {
    writable: false,
    value: {} // TODO
  })

  Object.defineProperty(this, 'listener', {
    writable: false,
    value: {} // TODO
  })*/
  this.currentTime = 0
  this.sampleRate = 44100

  // Tools for encoding the audio received to PCM data
  var format = {
    numberOfChannels: 2,  // 2 channels
    bitDepth: 16,         // 16-bit samples
    sampleRate: 44100     // 44,100 Hz sample rate
  }

  if (Speaker) this.outStream = new Speaker(format)
  else this.outStream = null
  this._encoder = pcmUtils.BufferEncoder(format)
  this._frame = 0
  this._playing = true
  this._audioOutLoopRunning = false

  // When a new connection is established, start to pull audio
  this.destination._inputs[0].on('connection', function() {
    if (self._audioOutLoopRunning) return
    if (!self.outStream) throw new Error('you need to set outStream to send the audio somewhere')
    self._audioOutLoopRunning = true
    async.whilst(
      function() { return self._playing },
      function(next) {
        outBuff = self.destination._tick()
        // If there is space in the output stream's buffers, we write,
        // otherwise we wait for 'drain'
        self._frame += BLOCK_SIZE
        self.currentTime = self._frame * 1/self.sampleRate
        if (self.outStream.write(self._encoder(outBuff._data))) next()
        else self.outStream.once('drain', next)
      },
      function(err) {
        self._audioOutLoopRunning = false
        if (err) return self.emit('error', err)
      }
    )
  })
}
inherits(AudioContext, EventEmitter)

_.extend(AudioContext.prototype, {

  createBuffer: function(numberOfChannels, length, sampleRate) {
    return new AudioBuffer(numberOfChannels, length, sampleRate)
  },

  decodeAudioData: function(audioData, successCallback, errorCallback) {
    utils.decodeAudioData(audioData, function(err, audioBuffer) {
      if (err) errorCallback(err)
      else successCallback(audioBuffer)
    })
  },

  createBufferSource: function() {
    return new AudioBufferSourceNode(this)
  },

  createGain: function() {
    return new GainNode(this)
  },

  createScriptProcessor: function(bufferSize, numberOfInputChannels, numberOfOutputChannels) {
    return new ScriptProcessorNode(this, bufferSize, numberOfInputChannels, numberOfOutputChannels)
  },

  /*
{

    readonly attribute AudioDestinationNode destination;
    readonly attribute float sampleRate;
    readonly attribute double currentTime;
    readonly attribute AudioListener listener;

    // AudioNode creation
    MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement);

    MediaStreamAudioSourceNode createMediaStreamSource(MediaStream mediaStream);
    MediaStreamAudioDestinationNode createMediaStreamDestination();

    AnalyserNode createAnalyser();
    DelayNode createDelay(optional double maxDelayTime = 1.0);
    BiquadFilterNode createBiquadFilter();
    WaveShaperNode createWaveShaper();
    PannerNode createPanner();
    ConvolverNode createConvolver();

    ChannelSplitterNode createChannelSplitter(optional unsigned long numberOfOutputs = 6);
    ChannelMergerNode createChannelMerger(optional unsigned long numberOfInputs = 6);

    DynamicsCompressorNode createDynamicsCompressor();

    OscillatorNode createOscillator();
    PeriodicWave createPeriodicWave(Float32Array real, Float32Array imag);

}
  */

  _kill: function() {
    var self = this
    this._playing = false
    if (self.outStream.close) self.outStream.close()
    else self.outStream.end()
  },

  collectNodes: function(node, allNodes) {
    var self = this
    allNodes = allNodes || []
    node = node || this.destination
    _.chain(node._inputs)
      .pluck('sources')
      .reduce(function(all, sources) { return all.concat(sources) }, [])
      .pluck('node').value()
      .forEach(function(upstreamNode) {
        if (!_.contains(allNodes, upstreamNode)) {
          allNodes.push(upstreamNode)
          self.collectNodes(upstreamNode, allNodes)
        }
      })
    return allNodes
  }

})

<|endoftext|>


<commit_before>const React = require('react')
const lodash = require('lodash')
const scrollIntoView = require('dom-scroll-into-view')

let _debugStates = []

let Autocomplete = React.createClass({

  propTypes: {
    value: React.PropTypes.any,
    onChange: React.PropTypes.func,
    onSelect: React.PropTypes.func,
    shouldItemRender: React.PropTypes.func,
    renderItem: React.PropTypes.func.isRequired,
    menuStyle: React.PropTypes.object,
    inputProps: React.PropTypes.object,
    labelText: React.PropTypes.string,
    wrapperProps: React.PropTypes.object,
    wrapperStyle: React.PropTypes.object
  },

  getDefaultProps () {
    return {
      value: '',
      wrapperProps: {},
      wrapperStyle: {
        display: 'inline-block'
      },
      inputProps: {},
      labelText: '',
      onChange () {},
      onSelect (value, item) {},
      renderMenu (items, value, style) {
        return

      },
      shouldItemRender () { return true },
      menuStyle: {
        borderRadius: '3px',
        boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
        background: 'rgba(255, 255, 255, 0.9)',
        padding: '2px 0',
        fontSize: '90%',
        position: 'fixed',
        overflow: 'auto',
        maxHeight: '50%', // TODO: don't cheat, let it flow to the bottom
      }
    }
  },

  getInitialState () {
    return {
      isOpen: false,
      highlightedIndex: null,
    }
  },

  componentWillMount () {
    this.id = lodash.uniqueId('autocomplete-');
    this._ignoreBlur = false
    this._performAutoCompleteOnUpdate = false
    this._performAutoCompleteOnKeyUp = false
  },

  componentWillReceiveProps () {
    this._performAutoCompleteOnUpdate = true
  },

  componentDidUpdate (prevProps, prevState) {
    if (this.state.isOpen === true && prevState.isOpen === false)
      this.setMenuPositions()

    if (this.state.isOpen && this._performAutoCompleteOnUpdate) {
      this._performAutoCompleteOnUpdate = false
      this.maybeAutoCompleteText()
    }

    this.maybeScrollItemIntoView()
  },

  maybeScrollItemIntoView () {
    if (this.state.isOpen === true && this.state.highlightedIndex !== null) {
      var itemNode = this.refs[`item-${this.state.highlightedIndex}`]
      var menuNode = this.refs.menu
      scrollIntoView(itemNode, menuNode, { onlyScrollIfNeeded: true })
    }
  },

  handleKeyDown (event) {
    if (this.keyDownHandlers[event.key])
      this.keyDownHandlers[event.key].call(this, event)
    else {
      this.setState({
        highlightedIndex: null,
        isOpen: true
      })
    }
  },

  handleChange (event) {
    this._performAutoCompleteOnKeyUp = true
    this.props.onChange(event, event.target.value)
  },

  handleKeyUp () {
    if (this._performAutoCompleteOnKeyUp) {
      this._performAutoCompleteOnKeyUp = false
      this.maybeAutoCompleteText()
    }
  },

  keyDownHandlers: {
    ArrowDown (event) {
      event.preventDefault()
      var { highlightedIndex } = this.state
      var index = (
        highlightedIndex === null ||
        highlightedIndex === this.getFilteredItems().length - 1
      ) ?  0 : highlightedIndex + 1
      this._performAutoCompleteOnKeyUp = true
      this.setState({
        highlightedIndex: index,
        isOpen: true,
      })
    },

    ArrowUp (event) {
      event.preventDefault()
      var { highlightedIndex } = this.state
      var index = (
        highlightedIndex === 0 ||
        highlightedIndex === null
      ) ? this.getFilteredItems().length - 1 : highlightedIndex - 1
      this._performAutoCompleteOnKeyUp = true
      this.setState({
        highlightedIndex: index,
        isOpen: true,
      })
    },

    Enter (event) {
      if (this.state.isOpen === false) {
        // menu is closed so there is no selection to accept -> do nothing
        return
      }
      else if (this.state.highlightedIndex == null) {
        // input has focus but no menu item is selected + enter is hit -> close the menu, highlight whatever's in input
        this.setState({
          isOpen: false
        }, () => {
          this.refs.input.select()
        })
      }
      else {
        // text entered + menu item has been highlighted + enter is hit -> update value to that of selected menu item, close the menu
        var item = this.getFilteredItems()[this.state.highlightedIndex]
        var value = this.props.getItemValue(item)
        this.setState({
          isOpen: false,
          highlightedIndex: null
        }, () => {
          //this.refs.input.focus() // TODO: file issue
          this.refs.input.setSelectionRange(
            value.length,
            value.length
          )
          this.props.onSelect(value, item)
        })
      }
    },

    Escape (event) {
      this.setState({
        highlightedIndex: null,
        isOpen: false
      })
    }
  },

  getFilteredItems () {
    let items = this.props.items

    if (this.props.shouldItemRender) {
      items = items.filter((item) => (
        this.props.shouldItemRender(item, this.props.value)
      ))
    }

    if (this.props.sortItems) {
      items.sort((a, b) => (
        this.props.sortItems(a, b, this.props.value)
      ))
    }

    return items
  },

  maybeAutoCompleteText () {
    if (this.props.value === '')
      return
    var { highlightedIndex } = this.state
    var items = this.getFilteredItems()
    if (items.length === 0)
      return
    var matchedItem = highlightedIndex !== null ?
      items[highlightedIndex] : items[0]
    var itemValue = this.props.getItemValue(matchedItem)
    var itemValueDoesMatch = (itemValue.toLowerCase().indexOf(
      this.props.value.toLowerCase()
    ) === 0)
    if (itemValueDoesMatch) {
      var node = this.refs.input
      var setSelection = () => {
        node.value = itemValue
        node.setSelectionRange(this.props.value.length, itemValue.length)
      }
      if (highlightedIndex === null)
        this.setState({ highlightedIndex: 0 }, setSelection)
      else
        setSelection()
    }
  },

  setMenuPositions () {
    var node = this.refs.input
    var rect = node.getBoundingClientRect()
    var computedStyle = global.window.getComputedStyle(node)
    var marginBottom = parseInt(computedStyle.marginBottom, 10) || 0;
    var marginLeft = parseInt(computedStyle.marginLeft, 10) || 0;
    var marginRight = parseInt(computedStyle.marginRight, 10) || 0;
    this.setState({
      menuTop: rect.bottom + marginBottom,
      menuLeft: rect.left + marginLeft,
      menuWidth: rect.width + marginLeft + marginRight
    })
  },

  highlightItemFromMouse (index) {
    this.setState({ highlightedIndex: index })
  },

  selectItemFromMouse (item) {
    var value = this.props.getItemValue(item);
    this.setState({
      isOpen: false,
      highlightedIndex: null
    }, () => {
      this.props.onSelect(value, item)
      this.refs.input.focus()
      this.setIgnoreBlur(false)
    })
  },

  setIgnoreBlur (ignore) {
    this._ignoreBlur = ignore
  },

  renderMenu () {
    var items = this.getFilteredItems().map((item, index) => {
      var element = this.props.renderItem(
        item,
        this.state.highlightedIndex === index,
        {cursor: 'default'}
      )
      return React.cloneElement(element, {
        onMouseDown: () => this.setIgnoreBlur(true),
        onMouseEnter: () => this.highlightItemFromMouse(index),
        onClick: () => this.selectItemFromMouse(item),
        ref: `item-${index}`,
      })
    })
    var style = {
      left: this.state.menuLeft,
      top: this.state.menuTop,
      minWidth: this.state.menuWidth,
    }
    var menu = this.props.renderMenu(items, this.props.value, style)
    return React.cloneElement(menu, { ref: 'menu' })
  },

  handleInputBlur () {
    if (this._ignoreBlur)
      return
    this.setState({
      isOpen: false,
      highlightedIndex: null
    })
  },

  handleInputFocus () {
    if (this._ignoreBlur)
      return
    this.setState({ isOpen: true })
  },

  handleInputClick () {
    if (this.state.isOpen === false)
      this.setState({ isOpen: true })
  },

  render () {
    if (this.props.debug) { // you don't like it, you love it
      _debugStates.push({
        id: _debugStates.length,
        state: this.state
      })
    }
    return (

          {this.props.labelText}

        <input
          {...this.props.inputProps}
          role="combobox"
          aria-autocomplete="both"
          ref="input"
          onFocus={this.handleInputFocus}
          onBlur={this.handleInputBlur}
          onChange={(event) => this.handleChange(event)}
          onKeyDown={(event) => this.handleKeyDown(event)}
          onKeyUp={(event) => this.handleKeyUp(event)}
          onClick={this.handleInputClick}
          value={this.props.value}
          id={this.id}
        />
        {this.state.isOpen && this.renderMenu()}
        {this.props.debug && (

            {JSON.stringify(_debugStates.slice(_debugStates.length - 5, _debugStates.length), null, 2)}

        )}

    )
  }
})

module.exports = Autocomplete

<commit_msg>[fixed] Workaround for selection bug in IE, fixes #80<commit_after>const React = require('react')
const lodash = require('lodash')
const scrollIntoView = require('dom-scroll-into-view')

let _debugStates = []

let Autocomplete = React.createClass({

  propTypes: {
    value: React.PropTypes.any,
    onChange: React.PropTypes.func,
    onSelect: React.PropTypes.func,
    shouldItemRender: React.PropTypes.func,
    renderItem: React.PropTypes.func.isRequired,
    menuStyle: React.PropTypes.object,
    inputProps: React.PropTypes.object,
    labelText: React.PropTypes.string,
    wrapperProps: React.PropTypes.object,
    wrapperStyle: React.PropTypes.object
  },

  getDefaultProps () {
    return {
      value: '',
      wrapperProps: {},
      wrapperStyle: {
        display: 'inline-block'
      },
      inputProps: {},
      labelText: '',
      onChange () {},
      onSelect (value, item) {},
      renderMenu (items, value, style) {
        return

      },
      shouldItemRender () { return true },
      menuStyle: {
        borderRadius: '3px',
        boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
        background: 'rgba(255, 255, 255, 0.9)',
        padding: '2px 0',
        fontSize: '90%',
        position: 'fixed',
        overflow: 'auto',
        maxHeight: '50%', // TODO: don't cheat, let it flow to the bottom
      }
    }
  },

  getInitialState () {
    return {
      isOpen: false,
      highlightedIndex: null,
    }
  },

  componentWillMount () {
    this.id = lodash.uniqueId('autocomplete-');
    this._ignoreBlur = false
    this._performAutoCompleteOnUpdate = false
    this._performAutoCompleteOnKeyUp = false
  },

  componentWillReceiveProps () {
    this._performAutoCompleteOnUpdate = true
  },

  componentDidUpdate (prevProps, prevState) {
    if (this.state.isOpen === true && prevState.isOpen === false)
      this.setMenuPositions()

    if (this.state.isOpen && this._performAutoCompleteOnUpdate) {
      this._performAutoCompleteOnUpdate = false
      this.maybeAutoCompleteText()
    }

    this.maybeScrollItemIntoView()
  },

  maybeScrollItemIntoView () {
    if (this.state.isOpen === true && this.state.highlightedIndex !== null) {
      var itemNode = this.refs[`item-${this.state.highlightedIndex}`]
      var menuNode = this.refs.menu
      scrollIntoView(itemNode, menuNode, { onlyScrollIfNeeded: true })
    }
  },

  handleKeyDown (event) {
    if (this.keyDownHandlers[event.key])
      this.keyDownHandlers[event.key].call(this, event)
    else {
      const { selectionStart, value } = event.target
      if (value === this.state.value)
        // Nothing changed, no need to do anything. This also prevents
        // our workaround below from nuking user-made selections
        return
      this.setState({
        highlightedIndex: null,
        isOpen: true
      }, () => {
        // Restore caret position before autocompletion process
        // to work around a setSelectionRange bug in IE (#80)
        this.refs.input.selectionStart = selectionStart
      })
    }
  },

  handleChange (event) {
    this._performAutoCompleteOnKeyUp = true
    this.props.onChange(event, event.target.value)
  },

  handleKeyUp () {
    if (this._performAutoCompleteOnKeyUp) {
      this._performAutoCompleteOnKeyUp = false
      this.maybeAutoCompleteText()
    }
  },

  keyDownHandlers: {
    ArrowDown (event) {
      event.preventDefault()
      var { highlightedIndex } = this.state
      var index = (
        highlightedIndex === null ||
        highlightedIndex === this.getFilteredItems().length - 1
      ) ?  0 : highlightedIndex + 1
      this._performAutoCompleteOnKeyUp = true
      this.setState({
        highlightedIndex: index,
        isOpen: true,
      })
    },

    ArrowUp (event) {
      event.preventDefault()
      var { highlightedIndex } = this.state
      var index = (
        highlightedIndex === 0 ||
        highlightedIndex === null
      ) ? this.getFilteredItems().length - 1 : highlightedIndex - 1
      this._performAutoCompleteOnKeyUp = true
      this.setState({
        highlightedIndex: index,
        isOpen: true,
      })
    },

    Enter (event) {
      if (this.state.isOpen === false) {
        // menu is closed so there is no selection to accept -> do nothing
        return
      }
      else if (this.state.highlightedIndex == null) {
        // input has focus but no menu item is selected + enter is hit -> close the menu, highlight whatever's in input
        this.setState({
          isOpen: false
        }, () => {
          this.refs.input.select()
        })
      }
      else {
        // text entered + menu item has been highlighted + enter is hit -> update value to that of selected menu item, close the menu
        var item = this.getFilteredItems()[this.state.highlightedIndex]
        var value = this.props.getItemValue(item)
        this.setState({
          isOpen: false,
          highlightedIndex: null
        }, () => {
          //this.refs.input.focus() // TODO: file issue
          this.refs.input.setSelectionRange(
            value.length,
            value.length
          )
          this.props.onSelect(value, item)
        })
      }
    },

    Escape (event) {
      this.setState({
        highlightedIndex: null,
        isOpen: false
      })
    }
  },

  getFilteredItems () {
    let items = this.props.items

    if (this.props.shouldItemRender) {
      items = items.filter((item) => (
        this.props.shouldItemRender(item, this.props.value)
      ))
    }

    if (this.props.sortItems) {
      items.sort((a, b) => (
        this.props.sortItems(a, b, this.props.value)
      ))
    }

    return items
  },

  maybeAutoCompleteText () {
    if (this.props.value === '')
      return
    var { highlightedIndex } = this.state
    var items = this.getFilteredItems()
    if (items.length === 0)
      return
    var matchedItem = highlightedIndex !== null ?
      items[highlightedIndex] : items[0]
    var itemValue = this.props.getItemValue(matchedItem)
    var itemValueDoesMatch = (itemValue.toLowerCase().indexOf(
      this.props.value.toLowerCase()
    ) === 0)
    if (itemValueDoesMatch) {
      var node = this.refs.input
      var setSelection = () => {
        node.value = itemValue
        node.setSelectionRange(this.props.value.length, itemValue.length)
      }
      if (highlightedIndex === null)
        this.setState({ highlightedIndex: 0 }, setSelection)
      else
        setSelection()
    }
  },

  setMenuPositions () {
    var node = this.refs.input
    var rect = node.getBoundingClientRect()
    var computedStyle = global.window.getComputedStyle(node)
    var marginBottom = parseInt(computedStyle.marginBottom, 10) || 0;
    var marginLeft = parseInt(computedStyle.marginLeft, 10) || 0;
    var marginRight = parseInt(computedStyle.marginRight, 10) || 0;
    this.setState({
      menuTop: rect.bottom + marginBottom,
      menuLeft: rect.left + marginLeft,
      menuWidth: rect.width + marginLeft + marginRight
    })
  },

  highlightItemFromMouse (index) {
    this.setState({ highlightedIndex: index })
  },

  selectItemFromMouse (item) {
    var value = this.props.getItemValue(item);
    this.setState({
      isOpen: false,
      highlightedIndex: null
    }, () => {
      this.props.onSelect(value, item)
      this.refs.input.focus()
      this.setIgnoreBlur(false)
    })
  },

  setIgnoreBlur (ignore) {
    this._ignoreBlur = ignore
  },

  renderMenu () {
    var items = this.getFilteredItems().map((item, index) => {
      var element = this.props.renderItem(
        item,
        this.state.highlightedIndex === index,
        {cursor: 'default'}
      )
      return React.cloneElement(element, {
        onMouseDown: () => this.setIgnoreBlur(true),
        onMouseEnter: () => this.highlightItemFromMouse(index),
        onClick: () => this.selectItemFromMouse(item),
        ref: `item-${index}`,
      })
    })
    var style = {
      left: this.state.menuLeft,
      top: this.state.menuTop,
      minWidth: this.state.menuWidth,
    }
    var menu = this.props.renderMenu(items, this.props.value, style)
    return React.cloneElement(menu, { ref: 'menu' })
  },

  handleInputBlur () {
    if (this._ignoreBlur)
      return
    this.setState({
      isOpen: false,
      highlightedIndex: null
    })
  },

  handleInputFocus () {
    if (this._ignoreBlur)
      return
    this.setState({ isOpen: true })
  },

  handleInputClick () {
    if (this.state.isOpen === false)
      this.setState({ isOpen: true })
  },

  render () {
    if (this.props.debug) { // you don't like it, you love it
      _debugStates.push({
        id: _debugStates.length,
        state: this.state
      })
    }
    return (

          {this.props.labelText}

        <input
          {...this.props.inputProps}
          role="combobox"
          aria-autocomplete="both"
          ref="input"
          onFocus={this.handleInputFocus}
          onBlur={this.handleInputBlur}
          onChange={(event) => this.handleChange(event)}
          onKeyDown={(event) => this.handleKeyDown(event)}
          onKeyUp={(event) => this.handleKeyUp(event)}
          onClick={this.handleInputClick}
          value={this.props.value}
          id={this.id}
        />
        {this.state.isOpen && this.renderMenu()}
        {this.props.debug && (

            {JSON.stringify(_debugStates.slice(_debugStates.length - 5, _debugStates.length), null, 2)}

        )}

    )
  }
})

module.exports = Autocomplete

<|endoftext|>