<commit_before>var path = require('path');
var util = require('util');
var _ = require('lodash');
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var RenderContext = require('./RenderContext');
var helpers = require('./helpers');

function Spec (options) {
  // Assigns a blank set of features if none are specified via the
  // options object. Any property not specified in the spec.json will
  // be filled in wiht the defaults listed here
  _.defaults(this, options, {
    baseDirectory: process.cwd(),
    features: [],
    excludeEndpoints: {},
    parent: options.inherit || null // Support both `parent` and `inherit` syntaxes
  });

  this._parentPromise = null;
}

Spec.load = function (baseDirectory) {
  // Loads the directory, finds the spec.json file, and parses it as JSON.
  // This JSON is then used to instantiate a new Spec object.
  return fs
    .readFileAsync(path.resolve(baseDirectory, 'spec.json'), 'utf8')
    .then(JSON.parse)
    .then(function (options) {
      var spec = new Spec(options);
      spec.baseDirectory = baseDirectory;
      return spec;
    });
};

Spec.prototype.hasParent = function () {
  return !!this.parent;
};

Spec.prototype.getParentSpec = function () {
  if (!this.hasParent()) {
    return Promise.resolve(null);
  }

  if (!this._parentPromise) {
    this._parentPromise = Spec.load(path.resolve(this.baseDirectory, this.parent));
  }

  return this._parentPromise;
};

Spec.prototype.getAst = function () {
  var _this = this;
  var fileFoundIndex = {};

  function recurse (spec) {

      // This function looks at the Spec.features array, and
      // tries to map all the values into .md file filenames.
      // If the files are not found, it is recorded as not having been
      // found in fileFoundIndex, and an empty string is placed in the
      // corresponding location in the returned array.

    return Promise
      .map(_this.features, function (filename) {
        var pathname = path.resolve(spec.baseDirectory, filename.replace(/\.md$/, '') + '.md');

        return fs.readFileAsync(pathname, 'utf-8')
          .tap(function () {
            fileFoundIndex[filename] = true;
          })
          .catch(function (err) {
            if (fileFoundIndex[filename] !== true) {
              fileFoundIndex[filename] = false;
            }

            return "Place holder for "  + filename;
          });
      })
      .then(function (features) {
        // Converts the blueprint markdown file to an Abstract Syntax Tree
        // in JSON.
        return helpers.blueprintToAst(features.join('\n\n'));
      })
      .then(function (ast) {
        return spec
          .getParentSpec()
          .then(function (parentSpec) {
            if (parentSpec) {
              // If a parent Spec was found, re-run this function on that spec
              // Merge the resulting AST with the current AST using helper func
              return Promise.join(ast, recurse(parentSpec), helpers.mergeAst);
            }
            // If there was no parent spec, return the final AST
            return ast;
          });
      })
      .tap(function (ast) {
        // Removes resources (endpoints and models) from the AST
        // that were excluded in the spec
        ast.resourceGroups = helpers.removeAstResources(ast.resourceGroups, spec.excludeEndpoints);
      });
  }

  return recurse(this)
    .tap(function (ast) {
      // Tap each run of recurse function to check if any
      // files in the spec currently being transformed to an
      // AST were not found. Throws an error if files are missing.
      var unfoundFiles = [];

      _.each(fileFoundIndex, function (wasFound, filename) {
        if (wasFound === false) {
          unfoundFiles.push(filename);
        }
      });

      if (unfoundFiles.length > 0) {
        throw new Spec.FeaturesNotFoundError(unfoundFiles);
      }
    });
};

// This is the main public interface.
// Load up a spec from a directory using
// Spec.load, and then call the renderBlueprint
// function on it to generate a blueprint.md file.
Spec.prototype.renderBlueprint = function () {

  var _this = this;
  var ctx = this.createRenderContext();

  return this
    .getAst() // Convert spec file into a JSON AST of the API
    .then(function(ast){ return helpers.alphabetizeAST(ast, _this);})
    .then(helpers.astToBlueprint) // Convert this AST into a blueprint
    .then(ctx.renderTemplate.bind(ctx)); // Replace all placeholders with file contents
};

Spec.prototype.createRenderContext = function () {
  return new RenderContext(this);
};

Spec.FeaturesNotFoundError = function (files) {
  Error.call(this);
  this.code = 'features_not_found';
  this.message = 'Features not found: ' + files.join(', ');
};

util.inherits(Spec.FeaturesNotFoundError, Error);

module.exports = Spec;
<commit_msg>Remove debug output<commit_after>var path = require('path');
var util = require('util');
var _ = require('lodash');
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var RenderContext = require('./RenderContext');
var helpers = require('./helpers');

function Spec (options) {
  // Assigns a blank set of features if none are specified via the
  // options object. Any property not specified in the spec.json will
  // be filled in wiht the defaults listed here
  _.defaults(this, options, {
    baseDirectory: process.cwd(),
    features: [],
    excludeEndpoints: {},
    parent: options.inherit || null // Support both `parent` and `inherit` syntaxes
  });

  this._parentPromise = null;
}

Spec.load = function (baseDirectory) {
  // Loads the directory, finds the spec.json file, and parses it as JSON.
  // This JSON is then used to instantiate a new Spec object.
  return fs
    .readFileAsync(path.resolve(baseDirectory, 'spec.json'), 'utf8')
    .then(JSON.parse)
    .then(function (options) {
      var spec = new Spec(options);
      spec.baseDirectory = baseDirectory;
      return spec;
    });
};

Spec.prototype.hasParent = function () {
  return !!this.parent;
};

Spec.prototype.getParentSpec = function () {
  if (!this.hasParent()) {
    return Promise.resolve(null);
  }

  if (!this._parentPromise) {
    this._parentPromise = Spec.load(path.resolve(this.baseDirectory, this.parent));
  }

  return this._parentPromise;
};

Spec.prototype.getAst = function () {
  var _this = this;
  var fileFoundIndex = {};

  function recurse (spec) {

      // This function looks at the Spec.features array, and
      // tries to map all the values into .md file filenames.
      // If the files are not found, it is recorded as not having been
      // found in fileFoundIndex, and an empty string is placed in the
      // corresponding location in the returned array.

    return Promise
      .map(_this.features, function (filename) {
        var pathname = path.resolve(spec.baseDirectory, filename.replace(/\.md$/, '') + '.md');

        return fs.readFileAsync(pathname, 'utf-8')
          .tap(function () {
            fileFoundIndex[filename] = true;
          })
          .catch(function (err) {
            if (fileFoundIndex[filename] !== true) {
              fileFoundIndex[filename] = false;
            }

            return "";
          });
      })
      .then(function (features) {
        // Converts the blueprint markdown file to an Abstract Syntax Tree
        // in JSON.
        return helpers.blueprintToAst(features.join('\n\n'));
      })
      .then(function (ast) {
        return spec
          .getParentSpec()
          .then(function (parentSpec) {
            if (parentSpec) {
              // If a parent Spec was found, re-run this function on that spec
              // Merge the resulting AST with the current AST using helper func
              return Promise.join(ast, recurse(parentSpec), helpers.mergeAst);
            }
            // If there was no parent spec, return the final AST
            return ast;
          });
      })
      .tap(function (ast) {
        // Removes resources (endpoints and models) from the AST
        // that were excluded in the spec
        ast.resourceGroups = helpers.removeAstResources(ast.resourceGroups, spec.excludeEndpoints);
      });
  }

  return recurse(this)
    .tap(function (ast) {
      // Tap each run of recurse function to check if any
      // files in the spec currently being transformed to an
      // AST were not found. Throws an error if files are missing.
      var unfoundFiles = [];

      _.each(fileFoundIndex, function (wasFound, filename) {
        if (wasFound === false) {
          unfoundFiles.push(filename);
        }
      });

      if (unfoundFiles.length > 0) {
        throw new Spec.FeaturesNotFoundError(unfoundFiles);
      }
    });
};

// This is the main public interface.
// Load up a spec from a directory using
// Spec.load, and then call the renderBlueprint
// function on it to generate a blueprint.md file.
Spec.prototype.renderBlueprint = function () {

  var _this = this;
  var ctx = this.createRenderContext();

  return this
    .getAst() // Convert spec file into a JSON AST of the API
    .then(function(ast){ return helpers.alphabetizeAST(ast, _this);})
    .then(helpers.astToBlueprint) // Convert this AST into a blueprint
    .then(ctx.renderTemplate.bind(ctx)); // Replace all placeholders with file contents
};

Spec.prototype.createRenderContext = function () {
  return new RenderContext(this);
};

Spec.FeaturesNotFoundError = function (files) {
  Error.call(this);
  this.code = 'features_not_found';
  this.message = 'Features not found: ' + files.join(', ');
};

util.inherits(Spec.FeaturesNotFoundError, Error);

module.exports = Spec;
<|endoftext|>


<commit_before>import $ from 'jquery';
import householdIncomeData from '../data/household_income.json';
export default function createChoroplethTests() {

  // returns a random index number
  function getRandomIndex(max) {
    return Math.floor(Math.random() * max);
  }

  describe('#ChoroplethTests', function() {

    describe('#Content', function() {

      it('1. My choropleth should have a title with a corresponding id="title"', function() {
        FCC_Global.assert.isNotNull(document.getElementById('title'), 'Could not find element with id="title"');
      })

      it('2. My choropleth should have a description with a corresponding id="description"', function() {
        FCC_Global.assert.isNotNull(document.getElementById('description'), 'Could not find element with id="description"');
      })
      it('3. My choropleth should have areas with a corresponding class="area" that represent the data', function() {
         FCC_Global.assert.isAbove(document.querySelectorAll('.area').length, 0, "Could not find any elements with class=\"area\"");
      })
      it('4. There should be at least 4 different fill colors used for the areas', function(){
        const areas = document.querySelectorAll('.area');
        var uniqueColors = [];

        for(var i = 0; i < areas.length; i++) {
          var areaColor = areas[i].style.fill || areas[i].getAttribute('fill');

          // if the current color isn't in the uniqueColors arr, push it
          if(uniqueColors.indexOf(areaColor) === -1){
             uniqueColors.push(areaColor);
          }
        }
        FCC_Global.assert.isAtLeast(uniqueColors.length, 4, 'There should be more than four fill colors used for the areas');
      })

      it('5. Each area will have the properties "data-fips" and "data-income" containing their corresponding fips and income values', function(){
        const areas = document.querySelectorAll('.area');
        FCC_Global.assert.isAbove(areas.length, 0, "Could not find any elements with a class=\"area\"");

        for(var i=0; i<areas.length; i++){
          var area = areas[i];
          FCC_Global.assert.isNotNull(area.getAttribute("data-fips"), "Could not find property 'data-fips' in area")
          FCC_Global.assert.isNotNull(area.getAttribute("data-income"), "Could not find property 'data-income' in area")
        }
      })

      it('6. My choropleth should have an area for each provided data point', function(){
        const areas = document.querySelectorAll('.area');

        // fix to match final data set
        FCC_Global.assert.equal(areas.length, 3142)
      })
      it('7.  The cells should have data-??? values that match the sample data', function(){
        const areas = document.querySelectorAll('.area');
        var uniqueFips = [];
        for(var i=0; i< areas.length; i++){
          var fips = areas[i].getAttribute('data-fips');
          // if the current color isn't in the uniqueColors arr, push it
          if(uniqueFips.indexOf(fips) === -1){
             uniqueFips.push(fips);
          }
        }
        FCC_Global.assert.equal(uniqueFips.length, 3142, "Fips values should be unique")

        const householdIncomeDataFips = householdIncomeData.map((ele) => {
          return ele['fips']
        })

        //check if the fips is in the sample data
        for(var j = 0; j < uniqueFips.length; j++) {
          console.log('unique fip: ', +uniqueFips[j]);
          console.log('test: ', householdIncomeDataFips.indexOf(+uniqueFips[j]))

          FCC_Global.assert.notEqual(householdIncomeDataFips.indexOf(+uniqueFips[j]), -1, "Fips value not found in sample data")
        }
        //check if the fips matches the right income

        // fix to match final data set
        //FCC_Global.assert.equal(areas.length, 3142)
      })
      it('9. My choropleth should have a legend with corresponding id="legend"', function(){
        FCC_Global.assert.isNotNull(document.getElementById('legend'), 'Could not find element with id="legend"');
      })
      it('9. There should be at least 4 different fill colors used for the legend', function(){
        const rects = document.querySelectorAll('#legend rect');
        var uniqueColors = [];

        for(var i = 0; i < rects.length; i++) {
          var areaColor = rects[i].style.fill || rects[i].getAttribute('fill');

          // if the current color isn't in the uniqueColors arr, push it
          if(uniqueColors.indexOf(areaColor) === -1){
             uniqueColors.push(areaColor);
          }
        }
        FCC_Global.assert.isAtLeast(uniqueColors.length, 4, 'There should be at least four fill colors used for the legend');
      })

      it('10.  I can mouse over an area and see a tooltip with a corresponding id="tooltip" which displays more information about the area ', function(){

        const firstRequestTimeout = 100;
        const secondRequestTimeout = 2000;
        this.timeout(firstRequestTimeout + secondRequestTimeout + 1000);
        FCC_Global.assert.isNotNull(document.getElementById('tooltip'), 'There should be an element with id="tooltip"');

        function getToolTipStatus(tooltip) {
          // jQuery's :hidden selector checks if the element or its parents have a display of none, a type of hidden, or height/width set to 0
          // if the element is hidden with opacity=0 or visibility=hidden, jQuery's :hidden will return false because it takes up space in the DOM
          // this test combines jQuery's :hidden with tests for opacity and visbility to cover most use cases (z-index and potentially others are not tested)
          if($(tooltip).is(':hidden') || tooltip.style.opacity === '0' || tooltip.style.visibility === 'hidden'){
            return 'hidden'
          } else {
            return 'visible'
          }
        }

        const tooltip = document.getElementById('tooltip');

        const areas = document.querySelectorAll('.area');

        // place mouse on random bar and check if tooltip is visible
        const randomIndex = getRandomIndex(areas.length);
        var randomArea = areas[randomIndex];
        randomArea.dispatchEvent(new MouseEvent('mouseover'));

        // promise is used to prevent test from ending prematurely
        return new Promise((resolve, reject) => {
          // timeout is used to accomodate tooltip transitions
          setTimeout( _ => {
            if(getToolTipStatus(tooltip) !== 'visible') {
              reject(new Error('Tooltip should be visible when mouse is on an area'))
            }

            // remove mouse from cell and check if tooltip is hidden again
            randomArea.dispatchEvent(new MouseEvent('mouseout'));
            setTimeout( _ => {
              if(getToolTipStatus(tooltip) !== 'hidden') {
                reject(new Error('Tooltip should be hidden when mouse is not on an area'))
              } else {
                resolve()
              }
            }, secondRequestTimeout)
          }, firstRequestTimeout)
        })
      })
      it('11. My tooltip should have a \"data-income\" property that corresponds to the given income of the active area', function() {
        const tooltip = document.getElementById('tooltip');
        FCC_Global.assert.isNotNull(tooltip.getAttribute("data-income"), 'Could not find property \"data-income\" in tooltip');

        const areas = document.querySelectorAll('.area');

        const randomIndex = getRandomIndex(areas.length);

        var randomArea = areas[randomIndex];

        randomArea.dispatchEvent(new MouseEvent('mouseover'));

        FCC_Global.assert.equal(tooltip.getAttribute('data-income'), randomArea.getAttribute('data-income'), 'Tooltip\'s \"data-income\" property should be equal to the active areas\'s \"data-income\" property');

        //clear out tooltip
        randomArea.dispatchEvent(new MouseEvent('mouseoff'));

      })
//       1. My choropleth should have a title with a corresponding id="title"
//       2. My choropleth should have a description with a corresponding id="description"
//       3. My choropleth should have cells with a corresponding class="cell" that represent the data
//       4. There should be at least 4 different fill colors used for the cells
//       5. Each cell will have the properties "data-county", "data-state", "data-????" containing their corresponding county, state, and ??? values
//       6. My choropleth should have an area for each provided data point
//       6. The data-income" of each cell should be within the range of the data
//       7. My choropleth should have cells that align with the corresponding ??? - don’t know how to word this.  Need more tests to test accuracy of choropleth

//       7a. The cells should have data-??? values that match the sample data
//       7a. The cells should have data-??? values that match the sample data of that cell’s data-county and data-state. Something like that

//       8. My choropleth should have a legend with corresponding id="legend"
//       9. I can mouse over a cell and see a tooltip with a corresponding id="tooltip" which displays more information about the cell
//       10. My tooltip should have a "data-???" property that corresponds to the given year of the active cell

    });

  });
}
<commit_msg>added more choropleth tests<commit_after>import $ from 'jquery';
import householdIncomeData from '../data/household_income.json';
export default function createChoroplethTests() {

  // returns a random index number
  function getRandomIndex(max) {
    return Math.floor(Math.random() * max);
  }

  describe('#ChoroplethTests', function() {

    describe('#Content', function() {

      it('1. My choropleth should have a title with a corresponding id="title"', function() {
        FCC_Global.assert.isNotNull(document.getElementById('title'), 'Could not find element with id="title"');
      })

      it('2. My choropleth should have a description with a corresponding id="description"', function() {
        FCC_Global.assert.isNotNull(document.getElementById('description'), 'Could not find element with id="description"');
      })
      it('3. My choropleth should have areas with a corresponding class="area" that represent the data', function() {
         FCC_Global.assert.isAbove(document.querySelectorAll('.area').length, 0, "Could not find any elements with class=\"area\"");
      })
      it('4. There should be at least 4 different fill colors used for the areas', function(){
        const areas = document.querySelectorAll('.area');
        var uniqueColors = [];

        for(var i = 0; i < areas.length; i++) {
          var areaColor = areas[i].style.fill || areas[i].getAttribute('fill');

          // if the current color isn't in the uniqueColors arr, push it
          if(uniqueColors.indexOf(areaColor) === -1){
             uniqueColors.push(areaColor);
          }
        }
        FCC_Global.assert.isAtLeast(uniqueColors.length, 4, 'There should be more than four fill colors used for the areas');
      })

      it('5. Each area will have the properties "data-fips" and "data-income" containing their corresponding fips and income values', function(){
        const areas = document.querySelectorAll('.area');
        FCC_Global.assert.isAbove(areas.length, 0, "Could not find any elements with a class=\"area\"");

        for(var i=0; i<areas.length; i++){
          var area = areas[i];
          FCC_Global.assert.isNotNull(area.getAttribute("data-fips"), "Could not find property 'data-fips' in area")
          FCC_Global.assert.isNotNull(area.getAttribute("data-income"), "Could not find property 'data-income' in area")
        }
      })

      it('6. My choropleth should have an area for each provided data point', function(){
        const areas = document.querySelectorAll('.area');

        // fix to match final data set
        FCC_Global.assert.equal(areas.length, 3142)
      })
      it('7.  The cells should have data-??? values that match the sample data', function(){
        const areas = document.querySelectorAll('.area');
        var uniqueFipsFromChoropleth = [];
        var incomesFromChoropleth = [];
        for(var i=0; i< areas.length; i++){
          var fips = areas[i].getAttribute('data-fips');
          // if the current color isn't in the uniqueColors arr, push it
          if(uniqueFipsFromChoropleth.indexOf(fips) === -1){
             uniqueFipsFromChoropleth.push(+fips);
             incomesFromChoropleth.push(areas[i].getAttribute('data-income'));
          }
        }
        FCC_Global.assert.equal(uniqueFipsFromChoropleth.length, 3142, "Fips values should be unique")

        const householdIncomeDataFips = householdIncomeData.map((ele) => {
          return +ele['fips']
        })

        //check if the fips in the sample data exist on the choropleth
        for(var j = 0; j < householdIncomeDataFips.length; j++) {

          FCC_Global.assert.notEqual(uniqueFipsFromChoropleth.indexOf(+householdIncomeDataFips[j]), -1, "Choropleth does not contain all sample data fips")
        }

        //check if the fips from choropleth matches the right income
        for(var j = 0; j < uniqueFipsFromChoropleth.length; j++) {
          var fips = uniqueFipsFromChoropleth[j];
          var income = incomesFromChoropleth[j];
          var result = householdIncomeData.filter(function( data ) {
            return data.fips == fips;
          });

          if(result[0]){
            FCC_Global.assert.equal(result[0].household_income, income, "Income from choropleth does not match income from sample data")
          }
          else{
            console.log("fips from choropleth not found in sample data");
          }
        }

        // fix to match final data set
        //FCC_Global.assert.equal(areas.length, 3142)
      })
      it('9. My choropleth should have a legend with corresponding id="legend"', function(){
        FCC_Global.assert.isNotNull(document.getElementById('legend'), 'Could not find element with id="legend"');
      })
      it('9. There should be at least 4 different fill colors used for the legend', function(){
        const rects = document.querySelectorAll('#legend rect');
        var uniqueColors = [];

        for(var i = 0; i < rects.length; i++) {
          var areaColor = rects[i].style.fill || rects[i].getAttribute('fill');

          // if the current color isn't in the uniqueColors arr, push it
          if(uniqueColors.indexOf(areaColor) === -1){
             uniqueColors.push(areaColor);
          }
        }
        FCC_Global.assert.isAtLeast(uniqueColors.length, 4, 'There should be at least four fill colors used for the legend');
      })

      it('10.  I can mouse over an area and see a tooltip with a corresponding id="tooltip" which displays more information about the area ', function(){

        const firstRequestTimeout = 100;
        const secondRequestTimeout = 2000;
        this.timeout(firstRequestTimeout + secondRequestTimeout + 1000);
        FCC_Global.assert.isNotNull(document.getElementById('tooltip'), 'There should be an element with id="tooltip"');

        function getToolTipStatus(tooltip) {
          // jQuery's :hidden selector checks if the element or its parents have a display of none, a type of hidden, or height/width set to 0
          // if the element is hidden with opacity=0 or visibility=hidden, jQuery's :hidden will return false because it takes up space in the DOM
          // this test combines jQuery's :hidden with tests for opacity and visbility to cover most use cases (z-index and potentially others are not tested)
          if($(tooltip).is(':hidden') || tooltip.style.opacity === '0' || tooltip.style.visibility === 'hidden'){
            return 'hidden'
          } else {
            return 'visible'
          }
        }

        const tooltip = document.getElementById('tooltip');

        const areas = document.querySelectorAll('.area');

        // place mouse on random bar and check if tooltip is visible
        const randomIndex = getRandomIndex(areas.length);
        var randomArea = areas[randomIndex];
        randomArea.dispatchEvent(new MouseEvent('mouseover'));

        // promise is used to prevent test from ending prematurely
        return new Promise((resolve, reject) => {
          // timeout is used to accomodate tooltip transitions
          setTimeout( _ => {
            if(getToolTipStatus(tooltip) !== 'visible') {
              reject(new Error('Tooltip should be visible when mouse is on an area'))
            }

            // remove mouse from cell and check if tooltip is hidden again
            randomArea.dispatchEvent(new MouseEvent('mouseout'));
            setTimeout( _ => {
              if(getToolTipStatus(tooltip) !== 'hidden') {
                reject(new Error('Tooltip should be hidden when mouse is not on an area'))
              } else {
                resolve()
              }
            }, secondRequestTimeout)
          }, firstRequestTimeout)
        })
      })
      it('11. My tooltip should have a \"data-income\" property that corresponds to the given income of the active area', function() {
        const tooltip = document.getElementById('tooltip');
        FCC_Global.assert.isNotNull(tooltip.getAttribute("data-income"), 'Could not find property \"data-income\" in tooltip');

        const areas = document.querySelectorAll('.area');

        const randomIndex = getRandomIndex(areas.length);

        var randomArea = areas[randomIndex];

        randomArea.dispatchEvent(new MouseEvent('mouseover'));

        FCC_Global.assert.equal(tooltip.getAttribute('data-income'), randomArea.getAttribute('data-income'), 'Tooltip\'s \"data-income\" property should be equal to the active areas\'s \"data-income\" property');

        //clear out tooltip
        randomArea.dispatchEvent(new MouseEvent('mouseoff'));

      })
//       1. My choropleth should have a title with a corresponding id="title"
//       2. My choropleth should have a description with a corresponding id="description"
//       3. My choropleth should have cells with a corresponding class="cell" that represent the data
//       4. There should be at least 4 different fill colors used for the cells
//       5. Each cell will have the properties "data-county", "data-state", "data-????" containing their corresponding county, state, and ??? values
//       6. My choropleth should have an area for each provided data point
//       6. The data-income" of each cell should be within the range of the data
//       7. My choropleth should have cells that align with the corresponding ??? - don’t know how to word this.  Need more tests to test accuracy of choropleth

//       7a. The cells should have data-??? values that match the sample data
//       7a. The cells should have data-??? values that match the sample data of that cell’s data-county and data-state. Something like that

//       8. My choropleth should have a legend with corresponding id="legend"
//       9. I can mouse over a cell and see a tooltip with a corresponding id="tooltip" which displays more information about the cell
//       10. My tooltip should have a "data-???" property that corresponds to the given year of the active cell

    });

  });
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
/*
 *   Copyright 2014 Guy Bedford (http://guybedford.com)
 *
 *   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.
 */

var ui = require('./lib/ui');
var config = require('./lib/config');
var pkg = require('./lib/package');
var core = require('./lib/core');
var semver = require('./lib/semver');

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

process.on('uncaughtException', function(err) {
  ui.log('err', err.stack || err);
});

// expose API for non-ui
module.exports = core;

if (require.main !== module)
  return;

(function() {
  function showInstructions() {
    ui.log('\n'
      + '  \033[47m\033[1m      \033[0m\n'
      + '  \033[47m\033[93m\033[1m jspm \033[0m\033[90m  '
      + 'Browser Package Management'
      + ' \033[0m\n'
      + '  \033[47m\033[1m      \033[0m\n'
      + '\n'
      + 'jspm install <name[=version]> [-o {package override}] [-f --force] \n'
      + '  install                         Install / update from package.json\n'
      + '  install jquery                  Install a package from the registry\n'
      + '  install npm:underscore          Install latest version from NPM\n'
      + '  install jquery@1.1              Install latest minor version\n'
      + '  install jquery@1.1.1            Install an exact version\n'
      + '  install jquery npm:underscore   Install multiple packages\n'
      + '  install jquery=1.1.1            Install a package to a specific version\n'
      + '  install jquery@1.2=1.2.3        Install a version range to a specific version\n'
      + '\n'
      + 'jspm inject <name[=version]> [-o {package override}] [-f --force] \n'
      + '  inject jquery                   Identical to install, but injects config\n'
      + '                                  only instead of downloading the package\n'
      + '\n'
      + 'jspm uninstall name               Uninstall a package and any orphaned deps\n'
      + '\n'
      + 'jspm update [-f -force]           Check and update existing modules\n'
      + '\n'
      + 'jspm init                         Create / recreate the configuration file\n'
      + '\n'
      + 'jspm dl-loader                    Download the jspm browser loader\n'
      + '\n'
      + 'jspm setmode <mode>\n'
      + '  setmode local                   Switch to locally downloaded libraries\n'
      + '  setmode remote                  Switch to CDN external package sources\n'
      + '  setmode dev                     Switch to the development baseURL\n'
      + '  setmode production              Switch to the production baseURL\n'
      + '\n'
      + ''
      + 'jspm depcache [moduleName]        Stores dep cache in config for flat pipelining\n'
      + 'jspm bundle [moduleName] [file]   Creates a single-file bundle for a module\n'
      + '\n'
      + 'jspm config <property> <value>    Set global configuration\n'
      + '  config github.username githubusername \n'
      + '  config github.password githubpassword \n'
    );
  }

  var args = process.argv.splice(2);
  switch(args[0]) {
    case 'inject':
      var inject = true;

    case 'install':
      var options = readOptions(args, ['--force', '--https', '--override']);
      options.inject = inject;

      var args = options.args;

      var depMap;
      for (var i = 1; i < (options.override || args.length); i++) {
        depMap = depMap || {};
        var name, target;
        var arg = args[i];
        if (arg.indexOf('=') == -1) {
          // install jquery@1.2.3 -> install jquery=^1.2.3
          name = arg.split('@')[0];
          target = arg.split('@')[1] || '';

          // valid semver -> make semver compatibility default
          if (target && target.match(semver.semverRegEx))
            target = '^' + target;
        }
        else {
          name = arg.split('=')[0];
          target = arg.split('=')[1];
        }
        depMap[name] = target;
      }

      if (options.override)
        options.override = eval('(' + args.splice(options.override).join(' ') + ')');

      if (options.https)
        pkg.https = true;

      // no install package -> install from package.json dependencies
      (depMap ? core.install(depMap, options) : core.install(true, options))
      .then(function() {
        return core.checkDlLoader()
      })
      .then(function() {
        return core.setMode(inject ? 'remote' : 'local')
      })
      .then(function() {
        ui.log('');
        ui.log('ok', 'Install complete');
      }, function(err) {
        // something happened (cancel / err)
        ui.log('err', err.stack || err);
        ui.log('warn', 'Installation changes not saved');
        process.exit(1);
      });

    break;
    case 'update':
      var options = readOptions(args, ['--force', '--https']);

      core.install(true, options)
      .then(function() {
        ui.log('');
        ui.log('ok', 'Update complete');
      }, function(err) {
        ui.log('err', err.stack || err);
        ui.log('warn', 'Update changes not saved');
        process.exit(1);
      });

    break;

    case 'uninstall':
      core.uninstall(args.splice(1))
      .then(function(removed) {
        if (removed) {
          ui.log('');
          ui.log('ok', 'Uninstall complete');
        }
        else
          ui.log('info', 'Nothing to remove');
      }, function(err) {
        ui.log('err', err.stack || err);
        ui.log('warn', 'Uninstall changes not saved');
        process.exit(1);
      });
    break;

    case 'clean':
      core.clean();

    break;

    case 'init':
      core.init();
    break;

    case 'prune':
      core.prune();
    break;

    case 'dl-loader':
      core.dlLoader();
    break;

    case 'setmode':
      core.setMode(args[1]);
    break;

    case 'depcache':
      core.depCache(args[1]);
    break;

    case 'bundle':
      core.bundle(args[1], args[2]);
    break;

    case 'build':
      core.build()
    break;

    case 'compile':
      var options = readOptions(args, ['--transpile', '--minify', '--removeJSExtensions'], ['--map', '--format']);
      if (options.map) {
        var mapParts = options.map.split('=');
        options.map = {};
        options.map[mapParts[0]] = mapParts[1];
      }

      build.compileDir(args[1], options)
      .then(function() {
        ui.log('ok', 'Compilation complete');
      }, function(e) {
        ui.log('err', e.stack || e);
      });

    break;
    case 'config':
      var property = args[1];
      var value = args.splice(2).join(' ');
      config.set(property, value);

    break;
    case '--help':
    case '-h':
      showInstructions();

    break;
    default:
      if (args[0])
        ui.log('Invalid argument ' + args[0]);
      showInstructions();
  }
})();

function readOptions(args, flags, settings) {
  settings = settings || [];
  var argOptions = { args: [] };
  for (var i = 0; i < args.length; i++) {
    if (args[i].substr(0, 2) == '--') {
      for (var j = 0; j < flags.length; j++)
        if (flags[j] == args[i])
          argOptions[flags[j].substr(2)] = i;
      for (var j = 0; j < settings.length; j++)
        if (settings[j] == args[i])
          argOptions[settings[j].substr(2)] = args[++i];
    }
    else if (args[i].substr(0, 1) == '-') {
      var opts = args[i].substr(1);
      for (var j = 0; j < opts.length; j++) {
        for (var k = 0; k < flags.length; k++) {
          if (flags[k].substr(2, 1) == opts[j])
            argOptions[flags[k].substr(2)] = argOptions.args.length;
        }
      }
    }
    else
      argOptions.args.push(args[i]);
  }
  return argOptions;
}

<commit_msg>ensure install exits process<commit_after>#!/usr/bin/env node
/*
 *   Copyright 2014 Guy Bedford (http://guybedford.com)
 *
 *   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.
 */

var ui = require('./lib/ui');
var config = require('./lib/config');
var pkg = require('./lib/package');
var core = require('./lib/core');
var semver = require('./lib/semver');

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

process.on('uncaughtException', function(err) {
  ui.log('err', err.stack || err);
});

// expose API for non-ui
module.exports = core;

if (require.main !== module)
  return;

(function() {
  function showInstructions() {
    ui.log('\n'
      + '  \033[47m\033[1m      \033[0m\n'
      + '  \033[47m\033[93m\033[1m jspm \033[0m\033[90m  '
      + 'Browser Package Management'
      + ' \033[0m\n'
      + '  \033[47m\033[1m      \033[0m\n'
      + '\n'
      + 'jspm install <name[=version]> [-o {package override}] [-f --force] \n'
      + '  install                         Install / update from package.json\n'
      + '  install jquery                  Install a package from the registry\n'
      + '  install npm:underscore          Install latest version from NPM\n'
      + '  install jquery@1.1              Install latest minor version\n'
      + '  install jquery@1.1.1            Install an exact version\n'
      + '  install jquery npm:underscore   Install multiple packages\n'
      + '  install jquery=1.1.1            Install a package to a specific version\n'
      + '  install jquery@1.2=1.2.3        Install a version range to a specific version\n'
      + '\n'
      + 'jspm inject <name[=version]> [-o {package override}] [-f --force] \n'
      + '  inject jquery                   Identical to install, but injects config\n'
      + '                                  only instead of downloading the package\n'
      + '\n'
      + 'jspm uninstall name               Uninstall a package and any orphaned deps\n'
      + '\n'
      + 'jspm update [-f -force]           Check and update existing modules\n'
      + '\n'
      + 'jspm init                         Create / recreate the configuration file\n'
      + '\n'
      + 'jspm dl-loader                    Download the jspm browser loader\n'
      + '\n'
      + 'jspm setmode <mode>\n'
      + '  setmode local                   Switch to locally downloaded libraries\n'
      + '  setmode remote                  Switch to CDN external package sources\n'
      + '  setmode dev                     Switch to the development baseURL\n'
      + '  setmode production              Switch to the production baseURL\n'
      + '\n'
      + ''
      + 'jspm depcache [moduleName]        Stores dep cache in config for flat pipelining\n'
      + 'jspm bundle [moduleName] [file]   Creates a single-file bundle for a module\n'
      + '\n'
      + 'jspm config <property> <value>    Set global configuration\n'
      + '  config github.username githubusername \n'
      + '  config github.password githubpassword \n'
    );
  }

  var args = process.argv.splice(2);
  switch(args[0]) {
    case 'inject':
      var inject = true;

    case 'install':
      var options = readOptions(args, ['--force', '--https', '--override']);
      options.inject = inject;

      var args = options.args;

      var depMap;
      for (var i = 1; i < (options.override || args.length); i++) {
        depMap = depMap || {};
        var name, target;
        var arg = args[i];
        if (arg.indexOf('=') == -1) {
          // install jquery@1.2.3 -> install jquery=^1.2.3
          name = arg.split('@')[0];
          target = arg.split('@')[1] || '';

          // valid semver -> make semver compatibility default
          if (target && target.match(semver.semverRegEx))
            target = '^' + target;
        }
        else {
          name = arg.split('=')[0];
          target = arg.split('=')[1];
        }
        depMap[name] = target;
      }

      if (options.override)
        options.override = eval('(' + args.splice(options.override).join(' ') + ')');

      if (options.https)
        pkg.https = true;

      // no install package -> install from package.json dependencies
      (depMap ? core.install(depMap, options) : core.install(true, options))
      .then(function() {
        return core.checkDlLoader()
      })
      .then(function() {
        return core.setMode(inject ? 'remote' : 'local')
      })
      .then(function() {
        ui.log('');
        ui.log('ok', 'Install complete');
        process.exit();
      }, function(err) {
        // something happened (cancel / err)
        ui.log('err', err.stack || err);
        ui.log('warn', 'Installation changes not saved');
        process.exit(1);
      });

    break;
    case 'update':
      var options = readOptions(args, ['--force', '--https']);

      core.install(true, options)
      .then(function() {
        ui.log('');
        ui.log('ok', 'Update complete');
      }, function(err) {
        ui.log('err', err.stack || err);
        ui.log('warn', 'Update changes not saved');
        process.exit(1);
      });

    break;

    case 'uninstall':
      core.uninstall(args.splice(1))
      .then(function(removed) {
        if (removed) {
          ui.log('');
          ui.log('ok', 'Uninstall complete');
        }
        else
          ui.log('info', 'Nothing to remove');
      }, function(err) {
        ui.log('err', err.stack || err);
        ui.log('warn', 'Uninstall changes not saved');
        process.exit(1);
      });
    break;

    case 'clean':
      core.clean();

    break;

    case 'init':
      core.init();
    break;

    case 'prune':
      core.prune();
    break;

    case 'dl-loader':
      core.dlLoader();
    break;

    case 'setmode':
      core.setMode(args[1]);
    break;

    case 'depcache':
      core.depCache(args[1]);
    break;

    case 'bundle':
      core.bundle(args[1], args[2]);
    break;

    case 'build':
      core.build()
    break;

    case 'compile':
      var options = readOptions(args, ['--transpile', '--minify', '--removeJSExtensions'], ['--map', '--format']);
      if (options.map) {
        var mapParts = options.map.split('=');
        options.map = {};
        options.map[mapParts[0]] = mapParts[1];
      }

      build.compileDir(args[1], options)
      .then(function() {
        ui.log('ok', 'Compilation complete');
      }, function(e) {
        ui.log('err', e.stack || e);
      });

    break;
    case 'config':
      var property = args[1];
      var value = args.splice(2).join(' ');
      config.set(property, value);

    break;
    case '--help':
    case '-h':
      showInstructions();

    break;
    default:
      if (args[0])
        ui.log('Invalid argument ' + args[0]);
      showInstructions();
  }
})();

function readOptions(args, flags, settings) {
  settings = settings || [];
  var argOptions = { args: [] };
  for (var i = 0; i < args.length; i++) {
    if (args[i].substr(0, 2) == '--') {
      for (var j = 0; j < flags.length; j++)
        if (flags[j] == args[i])
          argOptions[flags[j].substr(2)] = i;
      for (var j = 0; j < settings.length; j++)
        if (settings[j] == args[i])
          argOptions[settings[j].substr(2)] = args[++i];
    }
    else if (args[i].substr(0, 1) == '-') {
      var opts = args[i].substr(1);
      for (var j = 0; j < opts.length; j++) {
        for (var k = 0; k < flags.length; k++) {
          if (flags[k].substr(2, 1) == opts[j])
            argOptions[flags[k].substr(2)] = argOptions.args.length;
        }
      }
    }
    else
      argOptions.args.push(args[i]);
  }
  return argOptions;
}

<|endoftext|>


<commit_before>'use strict';

var Promise = require('bluebird');
var elastic = Promise.promisifyAll(require('../elastic/data'));
var collectionService = require('../services/collection');
var async = require('async');
var _ = require('lodash');
var dataHelper = require('../helpers/data');

(function(module) {

  /**
   * get document
   */
  module.addDocumentAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      var collection = res || {};
      return elastic.addDocumentAsync({
        index: collection.project,
        type: data.collectionName,
        refresh: data.refresh,
        body: dataHelper.inputMapper(data.body, collection.schema),
        id: data.body.id
      })
    }).then(function(res) {
      return {
        id: res._id,
        collection: res._type,
        project: res._index
      }
    })
  }

  /**
   * update document
   */
  module.updateDocumentAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      return elastic.updateDocumentAsync({
        index: res.project,
        type: data.collectionName,
        body: data.body,
        body: dataHelper.inputMapper(data.body, res.schema),
        id: data.id
      })
    }).then(function(res) {
      return res;
    })
  }

  /**
   * clean documents
   */
  module.cleanDocumentsAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      return elastic.cleanDocumentsAsync({
        index: res.project,
        type: data.collectionName,
      });
    })
  }

  /**
   * delete document
   */
  module.deleteDocumentAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      return elastic.deleteDocumentAsync({
        index: res.project,
        type: data.collectionName,
        id: data.id
      })
    })

    return elastic.deleteDocumentAsync({
      index: data.projectName,
      type: data.collectionName,
      id: data.id
    }).then(function(res) {
      return res._source;
    })
  }

  /**
   * get document
   */
  module.getDocumentAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      return elastic.getDocumentAsync({
        index: res.project,
        type: data.collectionName,
        id: data.id
      })
    })
    .then(function(res) {
      return res._source;
    })
  }

  /**
   * add multiple documents elastic
   * @param {Array} data documents
   * @param {String} projectName
   * @param {String} collectionName
   */
  module.addDocumentsAsync = function(data) {
    var project;
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      project = res.project;
      return elastic.addDocumentsAsync({
        index: project,
        type: data.collectionName,
        refresh: data.refresh,
        body: dataHelper.inputMapper(data.body, res.schema),
      })
    }).then(function(res) {
      return _.pick(_.extend(res, {
        ids: _.map(res.items, function(val) {
          return val.create._id;
        }),
        project: project,
        collection: data.collectionName
      }), 'took', 'errors', 'ids', 'project', 'collection');
    })
  }

  /**
   * add all documents to elastic
   * @param {Array} documents full data
   * @param {String} projectName
   * @param {String} collectionName
   * @param {Integer} batchSize
   * @return {String} inserted documents count
   */
  module.addAllDocuments = function(data, callback) {

    var documents = data.body;
    var limit = documents.length;
    var length = documents.length;

    var batchSize = data.batchSize || 1000;

    var count = 0;

    var projectName = data.projectName;
    var collectionName = data.collectionName;

    async.whilst(
      function () { return length > 0; },
      function (callback) {

        var removed = documents.splice(0, batchSize);
        module.addDocumentsAsync({
          projectName: projectName,
          collectionName: collectionName,
          refresh: data.refresh,
          body: removed
        }).then(function(res) {
          return callback(null, res);
        }).catch(function(err) {
          return callback(err);
        })
        length -= removed.length;
      },
      function (err, res) {
        if (err) {
          console.log(err);
        }
        callback(null, limit + ' documents added');
      }
    );
  }
}(exports));
<commit_msg>fix issue<commit_after>'use strict';

var Promise = require('bluebird');
var elastic = Promise.promisifyAll(require('../elastic/data'));
var collectionService = require('../services/collection');
var async = require('async');
var _ = require('lodash');
var dataHelper = require('../helpers/data');

(function(module) {

  /**
   * get document
   */
  module.addDocumentAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      var collection = res || {};
      return elastic.addDocumentAsync({
        index: collection.project,
        type: data.collectionName,
        refresh: data.refresh,
        body: dataHelper.inputMapper(data.body, collection.schema),
        id: data.body.id
      })
    }).then(function(res) {
      return {
        id: res._id,
        collection: res._type,
        project: res._index
      }
    })
  }

  /**
   * update document
   */
  module.updateDocumentAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      return elastic.updateDocumentAsync({
        index: res.project,
        type: data.collectionName,
        body: dataHelper.inputMapper(data.body, res.schema),
        id: data.id
      })
    }).then(function(res) {
      return res;
    })
  }

  /**
   * clean documents
   */
  module.cleanDocumentsAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      return elastic.cleanDocumentsAsync({
        index: res.project,
        type: data.collectionName,
      });
    })
  }

  /**
   * delete document
   */
  module.deleteDocumentAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      return elastic.deleteDocumentAsync({
        index: res.project,
        type: data.collectionName,
        id: data.id
      })
    })

    return elastic.deleteDocumentAsync({
      index: data.projectName,
      type: data.collectionName,
      id: data.id
    }).then(function(res) {
      return res._source;
    })
  }

  /**
   * get document
   */
  module.getDocumentAsync = function(data) {
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      return elastic.getDocumentAsync({
        index: res.project,
        type: data.collectionName,
        id: data.id
      })
    })
    .then(function(res) {
      return res._source;
    })
  }

  /**
   * add multiple documents elastic
   * @param {Array} data documents
   * @param {String} projectName
   * @param {String} collectionName
   */
  module.addDocumentsAsync = function(data) {
    var project;
    return collectionService.findCollectionAsync({
      name: data.collectionName,
      project: data.projectName
    })
    .then(function(res) {
      project = res.project;
      return elastic.addDocumentsAsync({
        index: project,
        type: data.collectionName,
        refresh: data.refresh,
        body: dataHelper.inputMapper(data.body, res.schema),
      })
    }).then(function(res) {
      return _.pick(_.extend(res, {
        ids: _.map(res.items, function(val) {
          return val.create._id;
        }),
        project: project,
        collection: data.collectionName
      }), 'took', 'errors', 'ids', 'project', 'collection');
    })
  }

  /**
   * add all documents to elastic
   * @param {Array} documents full data
   * @param {String} projectName
   * @param {String} collectionName
   * @param {Integer} batchSize
   * @return {String} inserted documents count
   */
  module.addAllDocuments = function(data, callback) {

    var documents = data.body;
    var limit = documents.length;
    var length = documents.length;

    var batchSize = data.batchSize || 1000;

    var count = 0;

    var projectName = data.projectName;
    var collectionName = data.collectionName;

    async.whilst(
      function () { return length > 0; },
      function (callback) {

        var removed = documents.splice(0, batchSize);
        module.addDocumentsAsync({
          projectName: projectName,
          collectionName: collectionName,
          refresh: data.refresh,
          body: removed
        }).then(function(res) {
          return callback(null, res);
        }).catch(function(err) {
          return callback(err);
        })
        length -= removed.length;
      },
      function (err, res) {
        if (err) {
          console.log(err);
        }
        callback(null, limit + ' documents added');
      }
    );
  }
}(exports));
<|endoftext|>


<commit_before>
'use strict';

var EventEmitter = require( 'events' ).EventEmitter;
var util = require( './util' );

/**
 * Single user on the server.
 *
 * @param {Object} data - User data.
 * @param {MumbleClient} client - Mumble client that owns this user.
 */
var User = function( data, client ) {
    this.client = client;
    this._applyProperties( data );
};

User.prototype = Object.create( EventEmitter.prototype );

/**
 * @summary Moves the user to a channel
 *
 * @param {Channel|string} channel - Channel name or a channel object
 */
User.prototype.moveToChannel = function( channel ) {

    // Fetch the channel ID based on the parameter.
    var id;
    if( typeof channel === 'string' ) {
        id = this.client.channelByName( channel ).id;
    } else if( typeof channel === 'object' ) {
        id = channel.id;
    } else {
        return;
    }

    // Send the move message.
    this.client.connection.sendMessage( 'UserState', {
        session: this.session, actor: this.client.user.session, channel_id: id } );
};

/**
 * @summary Change a user's self deafened state. (Obviously) only works on yourself.
 *
 * @param {Boolean} isSelfDeaf - The new self deafened state
 */
User.prototype.setSelfDeaf = function( isSelfDeaf ) {
    this.client.connection.sendMessage( 'UserState', {
        session: this.session, actor: this.client.user.session, self_deaf: isSelfDeaf } );
};

/**
 * @summary Change a user's self muted state. (Obviously) only works on yourself.
 *
 * @param {Boolean} isSelfMute - The new self muted state
 */
User.prototype.setSelfMute = function( isSelfMute ) {
    this.client.connection.sendMessage( 'UserState', {
        session: this.session, actor: this.client.user.session, self_mute: isSelfMute } );
};

/**
 * @summary Attempts to kick the user.
 *
 * @param {string} [reason] - The reason to kick the user for.
 */
User.prototype.kick = function( reason ) {
    this._sendRemoveUser( reason || 'You have been kicked', false );
};

/**
 * @summary Attempts to ban the user.
 *
 * @param {string} [reason] - The reason to ban the user for.
 */
User.prototype.ban = function( reason ) {
    this._sendRemoveUser( reason || 'You have been banned', true );
};

/**
 * @summary Sends a message to the user.
 *
 * @param {string} message - The message to send.
 */
User.prototype.sendMessage = function( message ) {
    this.client.sendMessage( message, { session: [ this.session ] } );
};

/**
 * @summary Returns an output stream for listening to the user audio.
 *
 * @param {Boolean} [noEmptyFrames]
 *      True to cut the output stream during silence. If the output stream
 *      isn't cut it will keep emitting zero-values when the user isn't
 *      talking.
 *
 * @returns {MumbleOutputStream} Output stream.
 */
User.prototype.outputStream = function( noEmptyFrames ) {
    return this.client.connection.outputStream( this.session, noEmptyFrames );
};

/**
 * @summary Returns an input stream for sending audio to the user.
 *
 * @returns {MumbleInputStream} Input stream.
 */
User.prototype.inputStream = function() {
    return this.client.inputStreamForUser( this.session );
};

/**
 * @summary Checks whether the user can talk or not.
 *
 * @returns {Boolean} True if the user can talk.
 */
User.prototype.canTalk = function() {
    return !this.mute && !this.selfMute && !this.suppress;
};

/**
 * @summary Checks whether the user can hear other people.
 *
 * @returns {Boolean} True if the user can hear.
 */
User.prototype.canHear = function() {
    return !this.selfDeaf;
};

/**
 * @summary _true_ when the user is registered on the server.
 *
 * @returns {Boolean} _true_ if the user is registered.
 */
User.prototype.isRegistered = function() {
    return !!this.id;
};

User.prototype._applyProperties = function( data ) {

    /**
     * @summary Session ID
     *
     * @description
     * Session ID is present for all users. The ID specifies the current user
     * session and will change when the user reconnects.
     *
     * @see User#id
     *
     * @name User#session
     * @type Number
     */
    this.session = data.session;

    /**
     * @summary User name
     *
     * @name User#name
     * @type String
     */
    this.name = data.name;

    /**
     * @summary User ID
     *
     * @description
     * User ID is specified only for users who are registered on the server.
     * The user ID won't change when the user reconnects.
     *
     * @see User#session
     *
     * @name User#id
     * @type Number
     */
    this.id = data.user_id;

    /**
     * @summary _true_ when the user is muted by an admin.
     *
     * @name User#mute
     * @type Boolean
     */
    this.mute = data.mute;

    /**
     * @summary _true_ when the user is deafened by an admin.
     *
     * @name User#deaf
     * @type Boolean
     */
    this.deaf = data.deaf;

    /**
     * @summary _true_ when the user is suppressed due to lack of
     * permissions.
     *
     * @description
     * The user will be suppressed by the server if they don't have permissions
     * to speak on the current channel.
     *
     * @name User#suppress
     * @type Boolean
     */
    this.suppress = data.suppress;

    /**
     * @summary _true_ when the user has muted themselves.
     *
     * @name User#selfMute
     * @type Boolean
     */
    this.selfMute = data.self_mute;

    /**
     * @summary _true_ when the user has deafened themselves.
     *
     * @name User#selfDeaf
     * @type Boolean
     */
    this.selfDeaf = data.self_deaf;

    /**
     * @summary The hash of the user certificate
     *
     * @name User#hash
     * @type String
     */
    this.hash = data.hash;

    /**
     * @summary _true_ when the user is recording the conversation.
     *
     * @name User#recording
     * @type Boolean
     */
    this.recording = data.recording;

    /**
     * @summary _true_ when the user is a priority speaker.
     *
     * @name User#prioritySpeaker
     * @type Boolean
     */
    this.prioritySpeaker = data.priority_speaker;

    /**
     * @summary User's current channel.
     *
     * @name User#channel
     * @type Channel
     */
    if( data.channel_id !== null ) {
        this.channel = this.client.channelById( data.channel_id );
    } else { // New users always enter root
        this.channel = this.client.rootChannel;
    }
    this.channel._addUser( this );

    // TODO: Comments, textures
};

/**
 * @summary Emitted when the user disconnects
 *
 * @description
 * Also available through the client `user-disconnect` event.
 *
 * @event User#disconnect
 */

User.prototype._detach = function() {
    this.emit( 'disconnect' );
    this.channel._removeUser( this );
};

/**
 * @summary Emitted when the user moves between channels.
 *
 * @event User#move
 * @param {Channel} oldChannel - The channel where the user was moved from.
 * @param {Channel} newChannel - The channel where the user was moved to.
 * @param {User} actor - The user who moved the channel or undefined for server.
 */

/**
 * @summary Checks whether the user changed channel.
 *
 * @private
 *
 * @param {Object} data - New user data.
 */
User.prototype._checkChangeChannel = function( data ) {

    // Get the two channel instances.
    var newChannel = this.client.channelById( data.channel_id );
    var oldChannel = this.channel;

    // Make sure there is a change in the channel.
    if( newChannel === oldChannel )
        return;

    // Make the channel change and notify listeners.
    this.channel = newChannel;
    oldChannel._removeUser( this );
    newChannel._addUser( this );

    var actor = this.client.userBySession( data.actor );
    this.emit( 'move', oldChannel, newChannel, actor );
};

/**
 * @summary Emitted when the user is muted or unmuted by the server.
 *
 * @description
 * Also available through the client `user-mute` event.
 *
 * @event User#mute
 * @param {Boolean} status
 *      True when the user is muted, false when unmuted.
 */

/**
 * @summary Emitted when the user mutes or unmutes themselves.
 *
 * @description
 * Also available through the client `user-self-mute` event.
 *
 * @event User#self-mute
 * @param {Boolean} status
 *      True when the user mutes themselves. False when unmuting.
 */

/**
 * @summary Emitted when the user deafens or undeafens themselves.
 *
 * @description
 * Also available through the client `user-self-deaf` event.
 *
 * @event User#self-deaf
 * @param {Boolean} status
 *      True when the user deafens themselves. False when undeafening.
 */

/**
 * @summary Emitted when the user is suppressed or unsuppressed.
 *
 * @description
 * Also available through the client `user-suppress` event.
 *
 * @event User#suppress
 * @param {Boolean} status
 *      True when the user is suppressed. False when unsuppressed.
 */

/**
 * @summary Emitted when the user starts or stops recording.
 *
 * @description
 * Also available through the client `user-recording` event.
 *
 * @event User#recording
 * @param {Boolean} status
 *      True when the user starts recording. False when they stop.
 */

/**
 * @summary Emitted when the user gains or loses priority speaker status.
 *
 * @description
 * Also available through the client `user-priority-speaker` event.
 *
 * @event User#priority-speaker
 * @param {Boolean} status
 *      True when the user gains priority speaker status. False when they lose
 *      it.
 */

User.prototype.update = function( data ) {
    var self = this;

    // Check the simple fields.
    [
        'mute', 'selfMute', 'suppress',
        'selfDeaf',
        'recording', 'prioritySpeaker',
    ].forEach( function( f ) {
        self._checkField( data, f );
    } );

    // Channel check
    if( data.channel_id !== null ) {
        this._checkChangeChannel( data );
    }

};

User.prototype._sendRemoveUser = function( reason, ban ) {
    this.client.connection.sendMessage( 'UserRemove', {
        session: this.session,
        actor: this.client.user.session,
        reason: reason,
        ban: ban
    } );
};

User.prototype._checkField = function( data, field ) {

    // Make sure the field has a value.
    var newValue = data[ util.toFieldName( field ) ];
    if( newValue === undefined )
        return;

    // Make sure the new value differs.
    var oldValue = this[ field ];
    if( newValue === oldValue )
        return;

    // All checks succeeded. Store the new value and emit change event.
    this[ field ] = newValue;
    var actor = this.client.userBySession( data.actor );
    this.emit( util.toEventName( field ), newValue, actor );
};

module.exports = User;
<commit_msg>Allow to set the comment of a user.<commit_after>
'use strict';

var EventEmitter = require( 'events' ).EventEmitter;
var util = require( './util' );

/**
 * Single user on the server.
 *
 * @param {Object} data - User data.
 * @param {MumbleClient} client - Mumble client that owns this user.
 */
var User = function( data, client ) {
    this.client = client;
    this._applyProperties( data );
};

User.prototype = Object.create( EventEmitter.prototype );

/**
 * @summary Moves the user to a channel
 *
 * @param {Channel|string} channel - Channel name or a channel object
 */
User.prototype.moveToChannel = function( channel ) {

    // Fetch the channel ID based on the parameter.
    var id;
    if( typeof channel === 'string' ) {
        id = this.client.channelByName( channel ).id;
    } else if( typeof channel === 'object' ) {
        id = channel.id;
    } else {
        return;
    }

    // Send the move message.
    this.client.connection.sendMessage( 'UserState', {
        session: this.session, actor: this.client.user.session, channel_id: id } );
};

/**
 * @summary Set the user's comment. Displayed when user's name is hovered over in client.
 *
 * @param {String} comment - The new comment
 */
User.prototype.setComment = function( comment ) {
    this.client.connection.sendMessage( 'UserState', {
        session: this.session, actor: this.client.user.session, comment: comment } );
};

/**
 * @summary Change a user's self deafened state. (Obviously) only works on yourself.
 *
 * @param {Boolean} isSelfDeaf - The new self deafened state
 */
User.prototype.setSelfDeaf = function( isSelfDeaf ) {
    this.client.connection.sendMessage( 'UserState', {
        session: this.session, actor: this.client.user.session, self_deaf: isSelfDeaf } );
};

/**
 * @summary Change a user's self muted state. (Obviously) only works on yourself.
 *
 * @param {Boolean} isSelfMute - The new self muted state
 */
User.prototype.setSelfMute = function( isSelfMute ) {
    this.client.connection.sendMessage( 'UserState', {
        session: this.session, actor: this.client.user.session, self_mute: isSelfMute } );
};

/**
 * @summary Attempts to kick the user.
 *
 * @param {string} [reason] - The reason to kick the user for.
 */
User.prototype.kick = function( reason ) {
    this._sendRemoveUser( reason || 'You have been kicked', false );
};

/**
 * @summary Attempts to ban the user.
 *
 * @param {string} [reason] - The reason to ban the user for.
 */
User.prototype.ban = function( reason ) {
    this._sendRemoveUser( reason || 'You have been banned', true );
};

/**
 * @summary Sends a message to the user.
 *
 * @param {string} message - The message to send.
 */
User.prototype.sendMessage = function( message ) {
    this.client.sendMessage( message, { session: [ this.session ] } );
};

/**
 * @summary Returns an output stream for listening to the user audio.
 *
 * @param {Boolean} [noEmptyFrames]
 *      True to cut the output stream during silence. If the output stream
 *      isn't cut it will keep emitting zero-values when the user isn't
 *      talking.
 *
 * @returns {MumbleOutputStream} Output stream.
 */
User.prototype.outputStream = function( noEmptyFrames ) {
    return this.client.connection.outputStream( this.session, noEmptyFrames );
};

/**
 * @summary Returns an input stream for sending audio to the user.
 *
 * @returns {MumbleInputStream} Input stream.
 */
User.prototype.inputStream = function() {
    return this.client.inputStreamForUser( this.session );
};

/**
 * @summary Checks whether the user can talk or not.
 *
 * @returns {Boolean} True if the user can talk.
 */
User.prototype.canTalk = function() {
    return !this.mute && !this.selfMute && !this.suppress;
};

/**
 * @summary Checks whether the user can hear other people.
 *
 * @returns {Boolean} True if the user can hear.
 */
User.prototype.canHear = function() {
    return !this.selfDeaf;
};

/**
 * @summary _true_ when the user is registered on the server.
 *
 * @returns {Boolean} _true_ if the user is registered.
 */
User.prototype.isRegistered = function() {
    return !!this.id;
};

User.prototype._applyProperties = function( data ) {

    /**
     * @summary Session ID
     *
     * @description
     * Session ID is present for all users. The ID specifies the current user
     * session and will change when the user reconnects.
     *
     * @see User#id
     *
     * @name User#session
     * @type Number
     */
    this.session = data.session;

    /**
     * @summary User name
     *
     * @name User#name
     * @type String
     */
    this.name = data.name;

    /**
     * @summary User ID
     *
     * @description
     * User ID is specified only for users who are registered on the server.
     * The user ID won't change when the user reconnects.
     *
     * @see User#session
     *
     * @name User#id
     * @type Number
     */
    this.id = data.user_id;

    /**
     * @summary _true_ when the user is muted by an admin.
     *
     * @name User#mute
     * @type Boolean
     */
    this.mute = data.mute;

    /**
     * @summary _true_ when the user is deafened by an admin.
     *
     * @name User#deaf
     * @type Boolean
     */
    this.deaf = data.deaf;

    /**
     * @summary _true_ when the user is suppressed due to lack of
     * permissions.
     *
     * @description
     * The user will be suppressed by the server if they don't have permissions
     * to speak on the current channel.
     *
     * @name User#suppress
     * @type Boolean
     */
    this.suppress = data.suppress;

    /**
     * @summary _true_ when the user has muted themselves.
     *
     * @name User#selfMute
     * @type Boolean
     */
    this.selfMute = data.self_mute;

    /**
     * @summary _true_ when the user has deafened themselves.
     *
     * @name User#selfDeaf
     * @type Boolean
     */
    this.selfDeaf = data.self_deaf;

    /**
     * @summary The hash of the user certificate
     *
     * @name User#hash
     * @type String
     */
    this.hash = data.hash;

    /**
     * @summary _true_ when the user is recording the conversation.
     *
     * @name User#recording
     * @type Boolean
     */
    this.recording = data.recording;

    /**
     * @summary _true_ when the user is a priority speaker.
     *
     * @name User#prioritySpeaker
     * @type Boolean
     */
    this.prioritySpeaker = data.priority_speaker;

    /**
     * @summary User's current channel.
     *
     * @name User#channel
     * @type Channel
     */
    if( data.channel_id !== null ) {
        this.channel = this.client.channelById( data.channel_id );
    } else { // New users always enter root
        this.channel = this.client.rootChannel;
    }
    this.channel._addUser( this );

    // TODO: Comments, textures
};

/**
 * @summary Emitted when the user disconnects
 *
 * @description
 * Also available through the client `user-disconnect` event.
 *
 * @event User#disconnect
 */

User.prototype._detach = function() {
    this.emit( 'disconnect' );
    this.channel._removeUser( this );
};

/**
 * @summary Emitted when the user moves between channels.
 *
 * @event User#move
 * @param {Channel} oldChannel - The channel where the user was moved from.
 * @param {Channel} newChannel - The channel where the user was moved to.
 * @param {User} actor - The user who moved the channel or undefined for server.
 */

/**
 * @summary Checks whether the user changed channel.
 *
 * @private
 *
 * @param {Object} data - New user data.
 */
User.prototype._checkChangeChannel = function( data ) {

    // Get the two channel instances.
    var newChannel = this.client.channelById( data.channel_id );
    var oldChannel = this.channel;

    // Make sure there is a change in the channel.
    if( newChannel === oldChannel )
        return;

    // Make the channel change and notify listeners.
    this.channel = newChannel;
    oldChannel._removeUser( this );
    newChannel._addUser( this );

    var actor = this.client.userBySession( data.actor );
    this.emit( 'move', oldChannel, newChannel, actor );
};

/**
 * @summary Emitted when the user is muted or unmuted by the server.
 *
 * @description
 * Also available through the client `user-mute` event.
 *
 * @event User#mute
 * @param {Boolean} status
 *      True when the user is muted, false when unmuted.
 */

/**
 * @summary Emitted when the user mutes or unmutes themselves.
 *
 * @description
 * Also available through the client `user-self-mute` event.
 *
 * @event User#self-mute
 * @param {Boolean} status
 *      True when the user mutes themselves. False when unmuting.
 */

/**
 * @summary Emitted when the user deafens or undeafens themselves.
 *
 * @description
 * Also available through the client `user-self-deaf` event.
 *
 * @event User#self-deaf
 * @param {Boolean} status
 *      True when the user deafens themselves. False when undeafening.
 */

/**
 * @summary Emitted when the user is suppressed or unsuppressed.
 *
 * @description
 * Also available through the client `user-suppress` event.
 *
 * @event User#suppress
 * @param {Boolean} status
 *      True when the user is suppressed. False when unsuppressed.
 */

/**
 * @summary Emitted when the user starts or stops recording.
 *
 * @description
 * Also available through the client `user-recording` event.
 *
 * @event User#recording
 * @param {Boolean} status
 *      True when the user starts recording. False when they stop.
 */

/**
 * @summary Emitted when the user gains or loses priority speaker status.
 *
 * @description
 * Also available through the client `user-priority-speaker` event.
 *
 * @event User#priority-speaker
 * @param {Boolean} status
 *      True when the user gains priority speaker status. False when they lose
 *      it.
 */

User.prototype.update = function( data ) {
    var self = this;

    // Check the simple fields.
    [
        'mute', 'selfMute', 'suppress',
        'selfDeaf',
        'recording', 'prioritySpeaker',
    ].forEach( function( f ) {
        self._checkField( data, f );
    } );

    // Channel check
    if( data.channel_id !== null ) {
        this._checkChangeChannel( data );
    }

};

User.prototype._sendRemoveUser = function( reason, ban ) {
    this.client.connection.sendMessage( 'UserRemove', {
        session: this.session,
        actor: this.client.user.session,
        reason: reason,
        ban: ban
    } );
};

User.prototype._checkField = function( data, field ) {

    // Make sure the field has a value.
    var newValue = data[ util.toFieldName( field ) ];
    if( newValue === undefined )
        return;

    // Make sure the new value differs.
    var oldValue = this[ field ];
    if( newValue === oldValue )
        return;

    // All checks succeeded. Store the new value and emit change event.
    this[ field ] = newValue;
    var actor = this.client.userBySession( data.actor );
    this.emit( util.toEventName( field ), newValue, actor );
};

module.exports = User;
<|endoftext|>


<commit_before>/**
 * https://github.com/warpech/angular-ui-handsontable
 */
angular.module('ui.directives', [])
  .directive('uiDatagrid', function () {
    var directiveDefinitionObject = {
      restrict: 'A',
      compile: function compile(tElement, tAttrs, transclude) {

        var defaultSettings = {
          startRows: 0,
          startCols: 3,
          outsideClickDeselects: false,
          autoComplete: []
        };

        var $container = $('

');

        var expression = tAttrs.datarows;
        var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
          lhs, rhs;
        if (!match) {
          throw Error("Expected datarows in form of '_item_ in _collection_' but got '" +
            expression + "'.");
        }
        lhs = match[1];
        rhs = match[2];
        tElement.data("uiDatagrid", {
          lhs: lhs,
          rhs: rhs,
          colHeaders: [],
          columns: [],
          settings: angular.extend({}, defaultSettings),
          $container: $container
        });

        return function postLink(scope, element, attrs, controller) {
          var uiDatagrid = element.data("uiDatagrid");
          uiDatagrid.settings = angular.extend(uiDatagrid.settings, scope.$eval(attrs.uiDatagrid));

          $(element).append($container);

          if (typeof scope[rhs] !== 'undefined') {
            uiDatagrid.settings['data'] = scope[rhs];
            if (uiDatagrid.columns.length > 0) {
              uiDatagrid.settings['columns'] = uiDatagrid.columns;
              uiDatagrid.settings['startCols'] = uiDatagrid.columns.length;
            }
          }

          if (uiDatagrid.colHeaders.length > 0) {
            uiDatagrid.settings['colHeaders'] = uiDatagrid.colHeaders;
          }

          $container.handsontable(uiDatagrid.settings);

          $container.on('datachange.handsontable', function (event, changes, source) {
            if (!scope.$$phase) { //if digest is not in progress
              scope.$digest(); //programmatic change does not trigger digest in AnuglarJS so we need to trigger it automatically
            }
          });

          $container.on('selectionbyprop.handsontable', function (event, r, p, r2, p2) {
            scope.$emit('datagridSelection', $container, r, p, r2, p2);
          });

          scope.$watch(rhs, function (newVal) {
            if (scope[rhs] !== $container.handsontable('getData') && uiDatagrid.columns.length > 0) {
              $container.handsontable('updateSettings', {
                data: scope[rhs],
                columns: uiDatagrid.columns,
                startCols: uiDatagrid.columns.length
              });
            }
            else {
              $container.handsontable('loadData', scope[rhs]);
            }
          }, true);
        }
      }
    };
    return directiveDefinitionObject;
  })
  .directive('datacolumn', function () {
    var directiveDefinitionObject = {
      restrict: 'E',
      priority: 500,
      compile: function compile(tElement, tAttrs, transclude) {

        var keys = [];
        for (var i in tAttrs) {
          if (tAttrs.hasOwnProperty(i)) {
            keys.push(i);
          }
        }

        tElement.data("uiDatagridAutocomplete", {
          value: tAttrs.value,
          source: null,
          live: ($.inArray('live', keys) !== -1) //true if element has attribute 'live'
        });

        return function postLink(scope, element, attrs, controller) {
          var uiDatagrid = element.inheritedData("uiDatagrid");

          var pattern = new RegExp("^(" + uiDatagrid.lhs + "\\.)")
            , value = attrs.value.replace(pattern, '')
            , title = scope.$eval(attrs.title)
            , type = scope.$eval(attrs.type)
            , options = attrs.options
            , tmp;

          var childScope = scope.$new();

          var column = scope.$eval(options) || {};
          column.data = value;

          uiDatagrid.colHeaders.push(title);

          switch (type) {
            case 'autocomplete':
              column.type = Handsontable.AutocompleteCell;
              var uiDatagridAutocomplete = element.data("uiDatagridAutocomplete");
              for (var i in uiDatagridAutocomplete) {
                if (uiDatagridAutocomplete.hasOwnProperty(i)) {
                  column[i] = uiDatagridAutocomplete[i];
                }
              }
              break;

            case 'checkbox':
              column.type = Handsontable.CheckboxCell;
              tmp = attrs.checkedtemplate;
              if (typeof tmp !== 'undefined') {
                column.checkedTemplate = scope.$eval(tmp); //if undefined then defaults to Boolean true
              }
              tmp = attrs.uncheckedtemplate;
              if (typeof tmp !== 'undefined') {
                column.uncheckedTemplate = scope.$eval(tmp); //if undefined then defaults to Boolean true
              }
              break;

            default:
              if (typeof type === 'object') {
                column.type = type;
              }
          }

          if (typeof attrs.readonly !== 'undefined') {
            column.readOnly = true;
          }

          for (var i in attrs) {
            if (attrs.hasOwnProperty(i) && i.charAt(0) !== '$' && typeof column[i] === 'undefined') {
              column[i] = childScope.$eval(attrs[i]);
            }
          }

          uiDatagrid.columns.push(column);
        }
      }
    };
    return directiveDefinitionObject;
  })
  .directive('optionlist', ['$interpolate', function ($interpolate) {
  var directiveDefinitionObject = {
    restrict: 'E',
    compile: function compile(tElement, tAttrs, transclude, linker) {

      var tpl = $.trim(tElement.html());
      tElement.remove();

      return function postLink(scope, element, attrs, controller) {
        var uiDatagridAutocomplete = element.inheritedData("uiDatagridAutocomplete");
        var uiDatagrid = element.inheritedData("uiDatagrid");

        var expression = attrs.datarows;
        var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
          lhs, rhs;
        if (!match) {
          throw Error("Expected datarows in form of '_item_ in _collection_' but got '" +
            expression + "'.");
        }
        lhs = match[1];
        rhs = match[2];

        var deregister
          , deinterval;

        var childScope = scope.$new();

        var interpolateFn = $interpolate(tpl);

        var lastItems;
        var lastQuery;

        uiDatagridAutocomplete.source = function (query, process) {
          if ($.trim(query) === lastQuery) {
            return;
          }
          lastQuery = $.trim(query);

          if (deregister) {
            deregister();
            clearInterval(deinterval);
          }
          var row = uiDatagrid.$container.data('handsontable').getSelected()[0];
          childScope[uiDatagrid.lhs] = scope.$eval(uiDatagrid.rhs)[row];
          if (uiDatagridAutocomplete.live) {
            childScope.$eval(uiDatagridAutocomplete.value + ' = "' + $.trim(query).replace(/"/g, '\"') + '"'); //refresh value after each key stroke
            childScope.$digest();
          }
          deinterval = setInterval(function () {
            scope.currentItem = childScope.item = uiDatagrid.$container.data('handsontable').getData()[row];
            scope.$digest();
            childScope.$digest();
          }, 100);
          deregister = childScope.$watch(rhs, function (newVal, oldVal) {
            lastItems = newVal;
            if (process) {
              process(newVal);
            }
          }, true);
        };

        uiDatagridAutocomplete.sorter = function (items) {
          return items;
        };

        uiDatagridAutocomplete.highlighter = function (item) {
          childScope[lhs] = item;
          return interpolateFn(childScope);
        };

        uiDatagridAutocomplete.select = function () {
          if (this.$menu.find('.active').length) {
            var index = this.$menu.find('.active').index();
            childScope[lhs] = lastItems[index];
            var instance = uiDatagrid.$container.data('handsontable');
            instance.destroyEditor();
            childScope.$eval(attrs.clickrow);
          }

          lastQuery = void 0;
          return this.hide();
        };
      }
    }
  };
  return directiveDefinitionObject;
}]);<commit_msg>"strict" directive to allow only predefined values<commit_after>/**
 * https://github.com/warpech/angular-ui-handsontable
 */
angular.module('ui.directives', [])
  .directive('uiDatagrid', function () {
    var directiveDefinitionObject = {
      restrict: 'A',
      compile: function compile(tElement, tAttrs, transclude) {

        var defaultSettings = {
          startRows: 0,
          startCols: 3,
          outsideClickDeselects: false,
          autoComplete: []
        };

        var $container = $('

');

        var expression = tAttrs.datarows;
        var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
          lhs, rhs;
        if (!match) {
          throw Error("Expected datarows in form of '_item_ in _collection_' but got '" +
            expression + "'.");
        }
        lhs = match[1];
        rhs = match[2];
        tElement.data("uiDatagrid", {
          lhs: lhs,
          rhs: rhs,
          colHeaders: [],
          columns: [],
          settings: angular.extend({}, defaultSettings),
          $container: $container
        });

        return function postLink(scope, element, attrs, controller) {
          var uiDatagrid = element.data("uiDatagrid");
          uiDatagrid.settings = angular.extend(uiDatagrid.settings, scope.$eval(attrs.uiDatagrid));

          $(element).append($container);

          if (typeof scope[rhs] !== 'undefined') {
            uiDatagrid.settings['data'] = scope[rhs];
            if (uiDatagrid.columns.length > 0) {
              uiDatagrid.settings['columns'] = uiDatagrid.columns;
              uiDatagrid.settings['startCols'] = uiDatagrid.columns.length;
            }
          }

          if (uiDatagrid.colHeaders.length > 0) {
            uiDatagrid.settings['colHeaders'] = uiDatagrid.colHeaders;
          }

          $container.handsontable(uiDatagrid.settings);

          $container.on('datachange.handsontable', function (event, changes, source) {
            if (!scope.$$phase) { //if digest is not in progress
              scope.$digest(); //programmatic change does not trigger digest in AnuglarJS so we need to trigger it automatically
            }
          });

          $container.on('selectionbyprop.handsontable', function (event, r, p, r2, p2) {
            scope.$emit('datagridSelection', $container, r, p, r2, p2);
          });

          scope.$watch(rhs, function (newVal) {
            if (scope[rhs] !== $container.handsontable('getData') && uiDatagrid.columns.length > 0) {
              $container.handsontable('updateSettings', {
                data: scope[rhs],
                columns: uiDatagrid.columns,
                startCols: uiDatagrid.columns.length
              });
            }
            else {
              $container.handsontable('loadData', scope[rhs]);
            }
          }, true);
        }
      }
    };
    return directiveDefinitionObject;
  })
  .directive('datacolumn', function () {
    var directiveDefinitionObject = {
      restrict: 'E',
      priority: 500,
      compile: function compile(tElement, tAttrs, transclude) {

        var keys = [];
        for (var i in tAttrs) {
          if (tAttrs.hasOwnProperty(i)) {
            keys.push(i);
          }
        }

        tElement.data("uiDatagridAutocomplete", {
          value: tAttrs.value,
          source: null,
          live: ($.inArray('live', keys) !== -1), //true if element has attribute 'live'
          strict: ($.inArray('strict', keys) !== -1) //true if element has attribute 'strict'
        });

        return function postLink(scope, element, attrs, controller) {
          var i;
          var uiDatagrid = element.inheritedData("uiDatagrid");

          var pattern = new RegExp("^(" + uiDatagrid.lhs + "\\.)")
            , value = attrs.value.replace(pattern, '')
            , title = scope.$eval(attrs.title)
            , type = scope.$eval(attrs.type)
            , options = attrs.options
            , tmp;

          var childScope = scope.$new();

          var column = scope.$eval(options) || {};
          column.data = value;

          uiDatagrid.colHeaders.push(title);

          switch (type) {
            case 'autocomplete':
              column.type = Handsontable.AutocompleteCell;
              var uiDatagridAutocomplete = element.data("uiDatagridAutocomplete");
              for (i in uiDatagridAutocomplete) {
                if (uiDatagridAutocomplete.hasOwnProperty(i)) {
                  column[i] = uiDatagridAutocomplete[i];
                }
              }
              break;

            case 'checkbox':
              column.type = Handsontable.CheckboxCell;
              tmp = attrs.checkedtemplate;
              if (typeof tmp !== 'undefined') {
                column.checkedTemplate = scope.$eval(tmp); //if undefined then defaults to Boolean true
              }
              tmp = attrs.uncheckedtemplate;
              if (typeof tmp !== 'undefined') {
                column.uncheckedTemplate = scope.$eval(tmp); //if undefined then defaults to Boolean true
              }
              break;

            default:
              if (typeof type === 'object') {
                column.type = type;
              }
          }

          if (typeof attrs.readonly !== 'undefined') {
            column.readOnly = true;
          }

          for (i in attrs) {
            if (attrs.hasOwnProperty(i) && i.charAt(0) !== '$' && typeof column[i] === 'undefined') {
              column[i] = childScope.$eval(attrs[i]);
            }
          }

          uiDatagrid.columns.push(column);
        }
      }
    };
    return directiveDefinitionObject;
  })
  .directive('optionlist', ['$interpolate', function ($interpolate) {
  var directiveDefinitionObject = {
    restrict: 'E',
    compile: function compile(tElement, tAttrs, transclude, linker) {

      var tpl = $.trim(tElement.html());
      tElement.remove();

      return function postLink(scope, element, attrs, controller) {
        var uiDatagridAutocomplete = element.inheritedData("uiDatagridAutocomplete");
        var uiDatagrid = element.inheritedData("uiDatagrid");

        var expression = attrs.datarows;
        var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
          lhs, rhs;
        if (!match) {
          throw Error("Expected datarows in form of '_item_ in _collection_' but got '" +
            expression + "'.");
        }
        lhs = match[1];
        rhs = match[2];

        var deregister
          , deinterval;

        var childScope = scope.$new();

        var interpolateFn = $interpolate(tpl);

        var lastItems;
        var lastQuery;

        uiDatagridAutocomplete.source = function (query, process) {
          if ($.trim(query) === lastQuery) {
            return;
          }
          lastQuery = $.trim(query);

          if (deregister) {
            deregister();
            clearInterval(deinterval);
          }
          var row = uiDatagrid.$container.data('handsontable').getSelected()[0];
          childScope[uiDatagrid.lhs] = scope.$eval(uiDatagrid.rhs)[row];
          if (uiDatagridAutocomplete.live) {
            childScope.$eval(uiDatagridAutocomplete.value + ' = "' + $.trim(query).replace(/"/g, '\"') + '"'); //refresh value after each key stroke
            childScope.$digest();
          }
          deinterval = setInterval(function () {
            scope.currentItem = childScope.item = uiDatagrid.$container.data('handsontable').getData()[row];
            scope.$digest();
            childScope.$digest();
          }, 100);
          deregister = childScope.$watch(rhs, function (newVal) {
            lastItems = newVal;
            if (process) {
              process(newVal);
            }
          }, true);
        };

        uiDatagridAutocomplete.sorter = function (items) {
          return items;
        };

        uiDatagridAutocomplete.highlighter = function (item) {
          childScope[lhs] = item;
          return interpolateFn(childScope);
        };

        uiDatagridAutocomplete.select = function () {
          var instance = uiDatagrid.$container.data('handsontable');
          if (this.$menu.find('.active').length) {
            var index = this.$menu.find('.active').index();
            childScope[lhs] = lastItems[index];
            instance.destroyEditor();
            childScope.$eval(attrs.clickrow);
          }
          else if (!uiDatagridAutocomplete.strict) {
            instance.destroyEditor();
            childScope.$eval(uiDatagridAutocomplete.value + ' = "' + $.trim(this.query).replace(/"/g, '\"') + '"'); //assign current textarea value
          }

          lastQuery = void 0;
          return this.hide();
        };
      }
    }
  };
  return directiveDefinitionObject;
}]);<|endoftext|>


<commit_before>const nested = require('./nested');

function pushLine(lines, v, i) {
  let x;

  if (lines[lines.length - 1].length) {
    lines.push('');
  }

  x = lines.length - 1;

  while (v.substr(i + 1, 3) !== 'and' && v[i]) {
    lines[x] = lines[x].concat(v[i]);
    i++;
  }

  return i;
}

function getChunk(line, startChr, untilChr) {
  let i = line.indexOf(startChr) + 1;
  let n = line.length;
  let chunk = '';

  while (line[i] !== untilChr && i < n) {
    chunk += line[i];
    i++;
  }

  return chunk.trim();
}

function mediaQuery(buffer) {
  let c = nested(buffer);
  let m = c.arguments.split(' ');
  let name = m[0];
  let value = m.slice(1).join(' ').replace(/\s+|\n/g, ' ');

  value = splitByComma(value);

  value = value.map(function (v) {
    let lines = [''];
    let i = 0;
    let n = v.length;

    for (; i < n; i++) {
      if (v.substr(i, 4) === 'only' && [' ', '('].indexOf(v[i + 4]) !== -1) {
        i = pushLine(lines, v, i);
      } else if (v.substr(i, 3) === 'and' && [' ', '('].indexOf(v[i + 3]) !== -1) {
        i = pushLine(lines, v, i);
      } else {
        lines[lines.length - 1] = lines[lines.length - 1].concat(v[i]);
      }
    }

    return lines.map(function (line) {
      let property;
      let value;

      if (line.substr(0, 4) === 'only' || line.substr(0, 3) !== 'and') {
        return { mediaType : line };
      }

      return {
        operator : line.split(' ')[0],
        property : getChunk(line, '(', ':'),
        value : getChunk(line, ':', ')')
      };
    });
  });

  buffer.string = buffer.string.substr(c.length);

  return {
    content : c.content,
    name : name,
    value : value
  };
}

module.exports = mediaQuery;
<commit_msg>Added missing require for 'splitByComma'<commit_after>const nested = require('./nested');
const splitByComma = require('../../vendor/splitByComma');

function pushLine(lines, v, i) {
  let x;

  if (lines[lines.length - 1].length) {
    lines.push('');
  }

  x = lines.length - 1;

  while (v.substr(i + 1, 3) !== 'and' && v[i]) {
    lines[x] = lines[x].concat(v[i]);
    i++;
  }

  return i;
}

function getChunk(line, startChr, untilChr) {
  let i = line.indexOf(startChr) + 1;
  let n = line.length;
  let chunk = '';

  while (line[i] !== untilChr && i < n) {
    chunk += line[i];
    i++;
  }

  return chunk.trim();
}

function mediaQuery(buffer) {
  let c = nested(buffer);
  let m = c.arguments.split(' ');
  let name = m[0];
  let value = m.slice(1).join(' ').replace(/\s+|\n/g, ' ');

  value = splitByComma(value);

  value = value.map(function (v) {
    let lines = [''];
    let i = 0;
    let n = v.length;

    for (; i < n; i++) {
      if (v.substr(i, 4) === 'only' && [' ', '('].indexOf(v[i + 4]) !== -1) {
        i = pushLine(lines, v, i);
      } else if (v.substr(i, 3) === 'and' && [' ', '('].indexOf(v[i + 3]) !== -1) {
        i = pushLine(lines, v, i);
      } else {
        lines[lines.length - 1] = lines[lines.length - 1].concat(v[i]);
      }
    }

    return lines.map(function (line) {
      let property;
      let value;

      if (line.substr(0, 4) === 'only' || line.substr(0, 3) !== 'and') {
        return { mediaType : line };
      }

      return {
        operator : line.split(' ')[0],
        property : getChunk(line, '(', ':'),
        value : getChunk(line, ':', ')')
      };
    });
  });

  buffer.string = buffer.string.substr(c.length);

  return {
    content : c.content,
    name : name,
    value : value
  };
}

module.exports = mediaQuery;
<|endoftext|>