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

  var Type = {
    NONE:  0,
    STAR:  1,
    PRIME: 2
  };

  /**
   * Array minima mutation functions.
   */
  function subtractRowMinima( row ) {
    var min = Math.min.apply( Math, row );

    for ( var i = 0, il = row.length; i < il; i++ ) {
      row[i] -= min;
    }

    return row;
  }

  function subtractRowsMinima( array ) {
    var i, il;
    for ( i = 0, il = array.length; i < il; i++ ) {
      subtractRowMinima( array[i] );
    }
  }

  /**
   * Takes a two-dimensional n by m matrix.
   */
  function subtractColMinima( array, colIndex ) {
    var min = Number.POSITIVE_INFINITY;
    var value;
    var i, il;
    // Create col array and find min.
    for ( i = 0, il = array.length; i < il; i++ ) {
      value = array[i][ colIndex ];
      if ( value < min ) {
        min = value;
      }
    }

    for ( i = 0, il = array.length; i < il; i++ ) {
      array[i][ colIndex ] -= min;
    }

    return array;
  }

  function logMatrix( matrix, precision ) {
    precision = precision || 0;

    // Get number of integer digits.
    var maxDigits = 0;
    var digits;
    var i, il;
    var j, jl;
    for ( i = 0, il = matrix.length; i < il; i++ ) {
      for ( j = 0, jl = matrix[i].length; j < jl; j++ ) {
        digits = Math.floor( matrix[i][j] ).toString().length;
        if ( digits > maxDigits ) {
          maxDigits = digits;
        }
      }
    }

    /**
     * Pads an number with leading spaces.
     *
     * For example: pad( 3.24, 3 ) => '  3.24'.
     */
    function pad( number, digits ) {
      var count = digits - Math.floor( number ).toString().length;
      var padding = '';

      while ( count > 0 ) {
        padding += ' ';
        count--;
      }

      return padding + number;
    }

    // Construct string representation of matrix.
    var string = '';
    for ( i = 0, il = matrix.length; i < il; i++ ) {
      for ( j = 0, jl = matrix[i].length; j < jl; j++ ) {
        string += pad( matrix[i][j].toFixed( precision ), maxDigits );

        // Append a comma if not at end.
        if ( j < jl - 1) {
          string += ', ';
        }
      }

      if ( i < il - 1 ) {
        string += '\n';
      }
    }

    return string;
  }

  function logMarks( marks ) {
    var string = '';
    var i, il;
    var j, jl;
    for ( i = 0, il = marks.length; i < il; i++ ) {
      for ( j = 0, jl = marks[i].length; j < jl; j++ ) {
        switch ( marks[i][j] ) {
          case Type.STAR:
            string += '*';
            break;

          case Type.PRIME:
            string += 'P';
            break;

          default:
            string += ' ';
        }

        if ( j < jl - 1 ) {
          string += ', ';
        }
      }

      if ( i < il - 1 ) {
        string += '\n';
      }
    }

    return string;
  }

  function calculate( costMatrix ) {
    var coveredRows = [],
        coveredCols = [];

    var marks = [];

    function init() {
      var i, il;
      var j, jl;
      var array;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        array = [];

        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          array.push( 0 );
        }

        marks.push( array );
      }

      step1();
    }

    init();

    function step1() {
      subtractRowsMinima( costMatrix );
      step2();
    }

    function step2() {
      var i, il;
      var j, jl;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          // Mark uncovered zeros.
          if ( !costMatrix[i][j] && !coveredRows[i] && !coveredCols[j] ) {
            marks[i][j] = Type.STAR;
            coveredRows[i] = true;
            coveredCols[j] = true;
          }
        }
      }

      // Empty covers.
      coveredRows = [];
      coveredCols = [];

      step3();
    }

    function step3() {
      var i, il;
      var j, jl;
      // Cover each column containing a starred zero.
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          if ( marks[i][j] === Type.STAR ) {
            coveredCols[j] = true;
          }
        }
      }

      // Count covered columns.
      var count = 0;
      for ( i = 0, il = coveredCols.length; i < il; i++ ) {
        if ( coveredCols[i] ) {
          count++;
        }
      }

      if ( count >= costMatrix[0].length ) {
        // Finished.
        return;
      } else {
        step4();
      }
    }

    // Step 4 helper functions.
    function findUncoveredZero() {
      var i, il;
      var j, jl;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          if ( !costMatrix[i][j] && !coveredRows[i] && !coveredCols[j] ) {
            return {
              row: i,
              col: j
            };
          }
        }
      }

      return {
        row: -1,
        col: -1
      };
    }

    function indexOfStar( row ) {
      for ( var i = 0, il = row.length; i < il; i++ ) {
        if ( row[i] === Type.STAR ) {
          return i;
        }
      }

      return -1;
    }

    function step4() {
      var row;
      var col;
      var index;
      var starIndex;
      while ( true ) {
        index = findUncoveredZero();
        row = index.row;
        col = index.col;
        if ( row === -1 ) {
          return step6();
        } else {
          marks[ row ][ col ] = Type.PRIME;

          starIndex = indexOfStar( marks[ row ] );
          if ( starIndex !== -1 ) {
            col = starIndex;
            coveredRows[ row ] = true;
            coveredCols[ col ] = false;
          } else {
            return step5( row, col );
          }
        }
      }
    }

    function step5( row, col ) {
      function indexOfStarCol( col ) {
        for ( var i = 0, il = marks.length; i < il; i++ ) {
          if ( marks[i][ col ] === Type.STAR ) {
            return i;
          }
        }

        return -1;
      }

      function indexOfPrimeRow( rowIndex ) {
        var row = marks[ rowIndex ];
        for ( var i = 0, il = row.length; i < il; i++ ) {
          if ( row[i] === Type.PRIME ) {
            return i;
          }
        }

        return -1;
      }

      function augmentPath( path, pathCount ) {
        var row, col;
        for ( var i = 0; i < pathCount; i++ ) {
          row = path[i][0];
          col = path[i][1];
          if ( marks[ row ][ col ] === 1 ) {
            marks[ row ][ col ] = 0;
          } else {
            marks[ row ][ col ] = 1;
          }
        }
      }

      function clearCovers() {
        coveredRows = [];
        coveredCols = [];
      }

      function removePrimes() {
        var i, il;
        var j, jl;
        for ( i = 0, il = marks.length; i < il; i++ ) {
          for ( j = 0, jl = marks[i].length; j < jl; j++ ) {
            if ( marks[i][j] === Type.PRIME ) {
              marks[i][j] = 0;
            }
          }
        }
      }

      var pathIndex = 0;
      var path = [
        [ row, col ]
      ];
      var index;
      while ( true ) {
        index = indexOfStarCol( path[ pathIndex ][1] );
        if ( index > -1 ) {
          pathIndex++;
          path[ pathIndex ] = [
            index,
            path[ pathIndex - 1 ][1]
          ];

          index = indexOfPrimeRow( path[ pathIndex ][0] );
          pathIndex++;
          path[ pathIndex ] = [
            path[ pathIndex - 1 ][0],
            index
          ];
        } else {
          break;
        }
      }

      augmentPath( path, pathIndex + 1 );
      clearCovers();
      removePrimes();
      step3();
    }

    function uncoveredMinima() {
      var min = Number.POSITIVE_INFINITY;

      var value;
      var i, il;
      var j, jl;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          value = costMatrix[i][j];
          if ( value < min && !coveredRows[i] && !coveredCols[j] ) {
            min = value;
          }
        }
      }

      return min;
    }

    function step6() {
      var min = uncoveredMinima();
      if ( !isFinite( min ) ) {
        return;
      }

      var i, il;
      var j, jl;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          // Add min to every element of each covered row.
          if ( coveredRows[i] ) {
            costMatrix[i][j] += min;
          }

          // Subtract min from every element of each uncoverd column.
          if ( !coveredCols[j] ) {
            costMatrix[i][j] -= min;
          }
        }
      }

      step4();
    }

    return marks.map(function( row ) {
      return row.indexOf( Type.STAR );
    });
  }

  return {
    calculate: calculate,

    Matrix: {
      log: logMatrix
    }
  };

}) ();
<commit_msg>registration[points]: Add cloneMatrix function.<commit_after>/*exported Hungarian*/
var Hungarian = (function() {
  'use strict';

  var Type = {
    NONE:  0,
    STAR:  1,
    PRIME: 2
  };

  /**
   * Array minima mutation functions.
   */
  function subtractRowMinima( row ) {
    var min = Math.min.apply( Math, row );

    for ( var i = 0, il = row.length; i < il; i++ ) {
      row[i] -= min;
    }

    return row;
  }

  function subtractRowsMinima( array ) {
    var i, il;
    for ( i = 0, il = array.length; i < il; i++ ) {
      subtractRowMinima( array[i] );
    }
  }

  /**
   * Takes a two-dimensional n by m matrix.
   */
  function subtractColMinima( array, colIndex ) {
    var min = Number.POSITIVE_INFINITY;
    var value;
    var i, il;
    // Create col array and find min.
    for ( i = 0, il = array.length; i < il; i++ ) {
      value = array[i][ colIndex ];
      if ( value < min ) {
        min = value;
      }
    }

    for ( i = 0, il = array.length; i < il; i++ ) {
      array[i][ colIndex ] -= min;
    }

    return array;
  }

  function logMatrix( matrix, precision ) {
    precision = precision || 0;

    // Get number of integer digits.
    var maxDigits = 0;
    var digits;
    var i, il;
    var j, jl;
    for ( i = 0, il = matrix.length; i < il; i++ ) {
      for ( j = 0, jl = matrix[i].length; j < jl; j++ ) {
        digits = Math.floor( matrix[i][j] ).toString().length;
        if ( digits > maxDigits ) {
          maxDigits = digits;
        }
      }
    }

    /**
     * Pads an number with leading spaces.
     *
     * For example: pad( 3.24, 3 ) => '  3.24'.
     */
    function pad( number, digits ) {
      var count = digits - Math.floor( number ).toString().length;
      var padding = '';

      while ( count > 0 ) {
        padding += ' ';
        count--;
      }

      return padding + number;
    }

    // Construct string representation of matrix.
    var string = '';
    for ( i = 0, il = matrix.length; i < il; i++ ) {
      for ( j = 0, jl = matrix[i].length; j < jl; j++ ) {
        string += pad( matrix[i][j].toFixed( precision ), maxDigits );

        // Append a comma if not at end.
        if ( j < jl - 1) {
          string += ', ';
        }
      }

      if ( i < il - 1 ) {
        string += '\n';
      }
    }

    return string;
  }

  function logMarks( marks ) {
    var string = '';
    var i, il;
    var j, jl;
    for ( i = 0, il = marks.length; i < il; i++ ) {
      for ( j = 0, jl = marks[i].length; j < jl; j++ ) {
        switch ( marks[i][j] ) {
          case Type.STAR:
            string += '*';
            break;

          case Type.PRIME:
            string += 'P';
            break;

          default:
            string += ' ';
        }

        if ( j < jl - 1 ) {
          string += ', ';
        }
      }

      if ( i < il - 1 ) {
        string += '\n';
      }
    }

    return string;
  }

  function cloneMatrix( matrix ) {
    var clone = [];

    for ( var i = 0, il = matrix.length; i < il; i++ ) {
      clone.push( matrix[i].slice() );
    }

    return clone;
  }

  function calculate( costMatrix ) {
    var coveredRows = [],
        coveredCols = [];

    var marks = [];

    function init() {
      var i, il;
      var j, jl;
      var array;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        array = [];

        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          array.push( 0 );
        }

        marks.push( array );
      }

      step1();
    }

    init();

    function step1() {
      subtractRowsMinima( costMatrix );
      step2();
    }

    function step2() {
      var i, il;
      var j, jl;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          // Mark uncovered zeros.
          if ( !costMatrix[i][j] && !coveredRows[i] && !coveredCols[j] ) {
            marks[i][j] = Type.STAR;
            coveredRows[i] = true;
            coveredCols[j] = true;
          }
        }
      }

      // Empty covers.
      coveredRows = [];
      coveredCols = [];

      step3();
    }

    function step3() {
      var i, il;
      var j, jl;
      // Cover each column containing a starred zero.
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          if ( marks[i][j] === Type.STAR ) {
            coveredCols[j] = true;
          }
        }
      }

      // Count covered columns.
      var count = 0;
      for ( i = 0, il = coveredCols.length; i < il; i++ ) {
        if ( coveredCols[i] ) {
          count++;
        }
      }

      if ( count >= costMatrix[0].length ) {
        // Finished.
        return;
      } else {
        step4();
      }
    }

    // Step 4 helper functions.
    function findUncoveredZero() {
      var i, il;
      var j, jl;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          if ( !costMatrix[i][j] && !coveredRows[i] && !coveredCols[j] ) {
            return {
              row: i,
              col: j
            };
          }
        }
      }

      return {
        row: -1,
        col: -1
      };
    }

    function indexOfStar( row ) {
      for ( var i = 0, il = row.length; i < il; i++ ) {
        if ( row[i] === Type.STAR ) {
          return i;
        }
      }

      return -1;
    }

    function step4() {
      var row;
      var col;
      var index;
      var starIndex;
      while ( true ) {
        index = findUncoveredZero();
        row = index.row;
        col = index.col;
        if ( row === -1 ) {
          return step6();
        } else {
          marks[ row ][ col ] = Type.PRIME;

          starIndex = indexOfStar( marks[ row ] );
          if ( starIndex !== -1 ) {
            col = starIndex;
            coveredRows[ row ] = true;
            coveredCols[ col ] = false;
          } else {
            return step5( row, col );
          }
        }
      }
    }

    function step5( row, col ) {
      function indexOfStarCol( col ) {
        for ( var i = 0, il = marks.length; i < il; i++ ) {
          if ( marks[i][ col ] === Type.STAR ) {
            return i;
          }
        }

        return -1;
      }

      function indexOfPrimeRow( rowIndex ) {
        var row = marks[ rowIndex ];
        for ( var i = 0, il = row.length; i < il; i++ ) {
          if ( row[i] === Type.PRIME ) {
            return i;
          }
        }

        return -1;
      }

      function augmentPath( path, pathCount ) {
        var row, col;
        for ( var i = 0; i < pathCount; i++ ) {
          row = path[i][0];
          col = path[i][1];
          if ( marks[ row ][ col ] === 1 ) {
            marks[ row ][ col ] = 0;
          } else {
            marks[ row ][ col ] = 1;
          }
        }
      }

      function clearCovers() {
        coveredRows = [];
        coveredCols = [];
      }

      function removePrimes() {
        var i, il;
        var j, jl;
        for ( i = 0, il = marks.length; i < il; i++ ) {
          for ( j = 0, jl = marks[i].length; j < jl; j++ ) {
            if ( marks[i][j] === Type.PRIME ) {
              marks[i][j] = 0;
            }
          }
        }
      }

      var pathIndex = 0;
      var path = [
        [ row, col ]
      ];
      var index;
      while ( true ) {
        index = indexOfStarCol( path[ pathIndex ][1] );
        if ( index > -1 ) {
          pathIndex++;
          path[ pathIndex ] = [
            index,
            path[ pathIndex - 1 ][1]
          ];

          index = indexOfPrimeRow( path[ pathIndex ][0] );
          pathIndex++;
          path[ pathIndex ] = [
            path[ pathIndex - 1 ][0],
            index
          ];
        } else {
          break;
        }
      }

      augmentPath( path, pathIndex + 1 );
      clearCovers();
      removePrimes();
      step3();
    }

    function uncoveredMinima() {
      var min = Number.POSITIVE_INFINITY;

      var value;
      var i, il;
      var j, jl;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          value = costMatrix[i][j];
          if ( value < min && !coveredRows[i] && !coveredCols[j] ) {
            min = value;
          }
        }
      }

      return min;
    }

    function step6() {
      var min = uncoveredMinima();
      if ( !isFinite( min ) ) {
        return;
      }

      var i, il;
      var j, jl;
      for ( i = 0, il = costMatrix.length; i < il; i++ ) {
        for ( j = 0, jl = costMatrix[i].length; j < jl; j++ ) {
          // Add min to every element of each covered row.
          if ( coveredRows[i] ) {
            costMatrix[i][j] += min;
          }

          // Subtract min from every element of each uncoverd column.
          if ( !coveredCols[j] ) {
            costMatrix[i][j] -= min;
          }
        }
      }

      step4();
    }

    return marks.map(function( row ) {
      return row.indexOf( Type.STAR );
    });
  }

  return {
    calculate: calculate,

    Matrix: {
      clone: cloneMatrix,
      log: logMatrix
    }
  };

}) ();
<|endoftext|>


<commit_before>var select = document.querySelector('.rel-schema');

xm_gen.ajax.query({file: 'fetch-schema.php'}, showSchema);

function showSchema(data) {
  data = JSON.parse(data);
  // TODO: remove log
  console.log(data);

  data.forEach(function (el) {
    var option = document.createElement('option'),
        prop = 'Tables_in_aa2000';

    option.value = el[prop];
    option.innerHTML = el[prop];

    select.append(option);
  });

  xm_gen.ajax.query({file: 'desc-relation.php?relation=' + select.value}, generateForm);
}

var formObject;

select.addEventListener('change', function () {
  xm_gen.ajax.query({file: 'desc-relation.php?relation=' + this.value}, generateForm);
});

function generateForm(data) {
  data = JSON.parse(data);
  // TODO: remove log
  console.log(data);

  formObject = {};

  var container = document.querySelector('.rel-schema-form'),
      prop;

  container.innerHTML = "";

  data.forEach(function (el) {
    var prop = el['Field'],
        type = el['Type'],
        row = document.createElement('div'),
        label = document.createElement('label'),
        input = document.createElement('input'),
        labelAutoIncremento = document.createElement('label'),
        autoIncremento = document.createElement('input'),
        labelRandom = document.createElement('label'),
        random = document.createElement('input');

    row.classList.add('form-row');

    label.innerHTML = prop;
    input.classList.add('form-data');
    input.type = "text";
    input.setAttribute('data-type', type);
    input.name = prop;
    input.id = prop;

    labelAutoIncremento.innerHTML = "AutoIncremento: ";
    labelAutoIncremento.setAttribute('style', 'display:none');
    labelAutoIncremento.setAttribute('class', "displayNone");

    autoIncremento.type = "checkbox";
    autoIncremento.name = prop + 'Checkbox';
    autoIncremento.id = prop + 'Checkbox';
    autoIncremento.hidden = true;
    autoIncremento.setAttribute('class', "hidden");

    labelRandom.innerHTML = 'Random:';
    labelRandom.setAttribute('style', 'display:none');
    labelRandom.setAttribute('class', "displayNone");

    random.type = "checkbox";
    random.name = prop + 'Random';
    random.id = prop + 'Random';
    random.hidden = true;
    random.setAttribute('class', "hidden");

    row.append(label, input, labelAutoIncremento, autoIncremento, labelRandom, random);

    container.append(row);
    formObject[prop] = '';
  });

  // focus first input element
  document.querySelector('.form-data').focus();
}

var load = document.querySelector('.rel-schema-load');

load.addEventListener('click', sendData);

function sendData() {
  var formData = document.querySelectorAll('.form-data');
    var cantidadTuplas = document.getElementById("cantidadTuplas").value;
    if (cantidadTuplas == "") cantidadTuplas = "1";

  for (var i = 0; i < formData.length; i++) {
    formObject[formData[i].name] = {};
    formObject[formData[i].name].value = formData[i].value;
    formObject[formData[i].name].autoIncremento = document.getElementById(formData[i].getAttribute('id')+"Checkbox").checked;
    formObject[formData[i].name].type = formData[i].getAttribute('data-type');

    if (document.getElementById(formData[i].getAttribute('id')+"Random").checked) {
        //Tengo que hacer un select del valor del input
        var tabla = formData[i].value.split("-")[0];
        var primaryKey = formData[i].value.split("-")[1];
        var relacion = hacerConsulta(tabla, primaryKey);
        var primaryKeyRandom = getPrimaryKeyRandom(relacion, primaryKey);
        //Elegir random
    }
  }

  // TODO: remove log
  console.log(formObject);

  xm_gen.ajax.query({file: 'insert-tuple.php?data=' + JSON.stringify(formObject) + "&relation=" + select.value + "&cantidadTuplas=" + cantidadTuplas},
  function (data) {console.log(data);})
}

function cargaMasiva() {
  // Tomo el valor actual del hidden
  var isHidden = document.getElementById("cantidadTuplas").hidden;

  // Muestro / Oculto la cantidad de tuplas a guardar
  document.getElementById("cantidadTuplas").hidden = !isHidden;

  // Muestro / Oculto los checkbox
  var listHidden = document.getElementsByClassName("hidden");
  for (var i = 0; i < listHidden.length; i++) {
      listHidden[i].hidden = !isHidden;
  }

   // Muestro / Oculto los labels
  var listDisplayNone = document.getElementsByClassName("displayNone");
  for (var i = 0; i < listDisplayNone.length; i++) {
      listDisplayNone[i].setAttribute('style', isHidden ? '' : 'display:none');
  }
}

function hacerConsulta(tabla, primaryKey) {
    var data;
   data = xm_gen.ajax.query({file: 'fetch-relation.php?relation=' + tabla});
    return data;
}

function getRelacion(data) {
    data = JSON.parse(data);
    return data;
}

function getPrimaryKeyRandom(relacion, primaryKey) {

    for (var i = 0; i < relacion.length; i++) {
        i++;
    }
}<commit_msg>Se carga un elemento random que es foreign key de otra tabla<commit_after>var select = document.querySelector('.rel-schema');
var keyFK;
var fkMap = {};

xm_gen.ajax.query({file: 'fetch-schema.php'}, showSchema);

function showSchema(data) {
  data = JSON.parse(data);
  // TODO: remove log
  console.log(data);

  data.forEach(function (el) {
    var option = document.createElement('option'),
        prop = 'Tables_in_aa2000';

    option.value = el[prop];
    option.innerHTML = el[prop];

    select.append(option);
  });

  xm_gen.ajax.query({file: 'desc-relation.php?relation=' + select.value}, generateForm);
}

var formObject;

select.addEventListener('change', function () {
  xm_gen.ajax.query({file: 'desc-relation.php?relation=' + this.value}, generateForm);
});

function generateForm(data) {
  data = JSON.parse(data);
  // TODO: remove log
  console.log(data);

  formObject = {};

  var container = document.querySelector('.rel-schema-form'),
      prop;

  container.innerHTML = "";

  data.forEach(function (el) {
    var prop = el['Field'],
        type = el['Type'],
        row = document.createElement('div'),
        label = document.createElement('label'),
        input = document.createElement('input'),
        labelAutoIncremento = document.createElement('label'),
        autoIncremento = document.createElement('input'),
        labelRandom = document.createElement('label'),
        random = document.createElement('input');

    row.classList.add('form-row');

    label.innerHTML = prop;
    input.classList.add('form-data');
    input.type = "text";
    input.setAttribute('data-type', type);
    input.name = prop;
    input.id = prop;

    labelAutoIncremento.innerHTML = "AutoIncremento: ";
    labelAutoIncremento.setAttribute('style', 'display:none');
    labelAutoIncremento.setAttribute('class', "displayNone");

    autoIncremento.type = "checkbox";
    autoIncremento.name = prop + 'Checkbox';
    autoIncremento.id = prop + 'Checkbox';
    autoIncremento.hidden = true;
    autoIncremento.setAttribute('class', "hidden");

    labelRandom.innerHTML = 'Random:';
    labelRandom.setAttribute('style', 'display:none');
    labelRandom.setAttribute('class', "displayNone");

    random.type = "checkbox";
    random.name = prop + 'Random';
    random.id = prop + 'Random';
    random.hidden = true;
    random.setAttribute('class', "hidden");
    random.setAttribute('onchange', "cargarMapaForeignKeys(this);");

    row.append(label, input, labelAutoIncremento, autoIncremento, labelRandom, random);

    container.append(row);
    formObject[prop] = '';
  });

  // focus first input element
  document.querySelector('.form-data').focus();
}

var load = document.querySelector('.rel-schema-load');

load.addEventListener('click', sendData);

function sendData() {
  var formData = document.querySelectorAll('.form-data');
    var cantidadTuplas = document.getElementById("cantidadTuplas").value;
    if (cantidadTuplas == "") cantidadTuplas = "1";

  for (var i = 0; i < formData.length; i++) {
    formObject[formData[i].name] = {};
    formObject[formData[i].name].value = formData[i].value;
    formObject[formData[i].name].autoIncremento = document.getElementById(formData[i].getAttribute('id')+"Checkbox").checked;
    formObject[formData[i].name].type = formData[i].getAttribute('data-type');

    if (document.getElementById(formData[i].getAttribute('id')+"Random").checked) {
        //Recupero del mapa de foreign keys, todos las tuplas de dicha foreign key
        var relacion = formData[i].value;
        var fk = formData[i].getAttribute("id");
        var list = fkMap[fk];
        //Elijo una tupla de manera random (Si no se desea random, solo cambiar la siguiente funcion)
        var valueRandom = getElementRandomOfList(list);
    }
  }

  // TODO: remove log
  console.log(formObject);

  xm_gen.ajax.query({file: 'insert-tuple.php?data=' + JSON.stringify(formObject) + "&relation=" + select.value + "&cantidadTuplas=" + cantidadTuplas},
  function (data) {console.log(data);})
}

function cargaMasiva() {
  // Tomo el valor actual del hidden
  var isHidden = document.getElementById("cantidadTuplas").hidden;

  // Muestro / Oculto la cantidad de tuplas a guardar
  document.getElementById("cantidadTuplas").hidden = !isHidden;

  // Muestro / Oculto los checkbox
  var listHidden = document.getElementsByClassName("hidden");
  for (var i = 0; i < listHidden.length; i++) {
      listHidden[i].hidden = !isHidden;
  }

   // Muestro / Oculto los labels
  var listDisplayNone = document.getElementsByClassName("displayNone");
  for (var i = 0; i < listDisplayNone.length; i++) {
      listDisplayNone[i].setAttribute('style', isHidden ? '' : 'display:none');
  }
}

function getResultSelect(data) {

    if (data != "") {
         var valueFkMap = JSON.parse(data);
         fkMap[keyFK] = valueFkMap;
         keyFK = null;
    }

}

function cargarMapaForeignKeys(button) {

    if (button.checked) {
        var idButton = button.getAttribute("id");
        var idLabel = idButton.split("Random")[0];
        var relacion = document.getElementById(idLabel).value;

        keyFK = idLabel;
        xm_gen.ajax.query({file: 'fetch-relation.php?relation=' + relacion}, getResultSelect);
    }

}

function getElementRandomOfList(list) {

    if (list != null && list.length > 0) {

        var i = getRandomInt(0, list.length);
        return list[i];
    }
    return null;
}

// Retorna un número aleatorio entre min (incluido) y max (excluido)
function getRandomArbitrary(min, max) {
  return Math.random() * (max - min) + min;
}

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}
<|endoftext|>


<commit_before>var Promise = require("bluebird");
var rp = require('request-promise');
var fs = require("fs");
var promiseDoWhilst = require('promise-do-whilst');

// config encapsulates opensensors-api-key
// valid keys for config are: api-key (required)
module.exports = function(config) {
    var API_POST_OPTIONS = {
        headers: {
            Accept: "application/json",
            Authorization: "api-key " + config["api-key"]
        }
    };

    var requests_filename = "./current_requests.json";
    var MAX_CONCURRENT_REQUESTS_IN_FLIGHT = 5;

    // on launch delete ther requests file
    if(fs.existsSync(requests_filename)) {
        fs.unlinkSync(requests_filename);
        console.log("Removed existing requests file: " + requests_filename);
    }

    // create it blank
    fs.writeFileSync(requests_filename, JSON.stringify([]));

    var API_BASE_URL = "https://api.opensensors.io";

    var httpGet = function(url, options){
        var options = Object.assign(
          {},
          {
            uri: url,
            resolveWithFullResponse: true,
            json: true,
            simple: false
          },
          options);

        return rp(options);
    };

    // helper (actually workhorse) method that does a GET to a URL
    // it appends the augmented payloads in the response to the second argument that gets passed to it
    // if the response body JSON contains a next element it recursively calls itself
    var retireRequest = function(theUrl){
        try {
            var file_contents = fs.readFileSync(requests_filename, 'utf8');
            if (file_contents.trim() == "") {
                file_contents = "[]";
            }
            var current_requests = JSON.parse(file_contents);

            // remove url from the list
            var i = current_requests.indexOf(theUrl);
            if (i != -1) {
                current_requests.splice(i, 1);
            }
            fs.writeFileSync(requests_filename, JSON.stringify(current_requests));
        }
        catch (e) {
            console.log(requests_filename + ' is corrupt - JSON parse failed');
            // this is also a really bad situation, but I don't know what can be done about it
        }
    };

    var getUntilNot400 = function(url){
      var theUrl = url;
      var requestsInFlight = 0;
      var current_requests = [];
      var requestCompletedSuccessfully = false;
      var fatalError = null;
      var got400 = false;
      var gotSaturated = false;
      var theResponse = null;

      return promiseDoWhilst(() => {
        // do this promise
        return Promise.try(() => {
          try{
            var file_contents = fs.readFileSync(requests_filename, 'utf8');
            if(file_contents.trim() == ""){
              file_contents = "[]";
            }
            current_requests = JSON.parse(file_contents);
            requestsInFlight = current_requests.length;
          }
          catch(e){
            console.log(requests_filename + ' is corrupt - JSON parse failed -- deleting it');
            fs.unlinkSync(requests_filename);
            file_contents = "[]";
            current_requests = JSON.parse(file_contents);
            requestsInFlight = current_requests.length;
          }

          if(requestsInFlight < MAX_CONCURRENT_REQUESTS_IN_FLIGHT) {
            // if the server is not saturated, go ahead and make a request and increase the saturation level

            // add this request to the list of in flight requests
            // if it's not already in the list
            if(current_requests.indexOf(theUrl) == -1) {
              current_requests.push(theUrl);
              try {
                fs.writeFileSync(requests_filename, JSON.stringify(current_requests));
              }
              catch (e) {
                console.log("Failed to write to " + requests_filename);
                // this is very bad... we really shouldn't proceed at this point with this request
                // it should in fact be as though we were saturated
                // just as though we had gotten a 400 response, just try again sooner
                console.log("Deferring request " + theUrl + " for 5 seconds");
                return;
              }

              return Promise.try(function () {
                return httpGet(theUrl, API_POST_OPTIONS);
              }).then(function (response) {
                if (response.statusCode == 400) {
                  console.log(theUrl);
                  console.log(response.body);
                  console.log("Got 400, waiting 30 seconds before trying " + theUrl + " again");
                  got400 = true;
                }
                else {
                  // finally! we can retire this request from the list and pass the results on
                  retireRequest(theUrl);
                  requestCompletedSuccessfully = true;
                  return response;
                }
              }).catch(function(error){
                // kill this request status file
                retireRequest(theUrl);
                console.log("+++++++++++++++++++++++");
                console.log("Error: " + error.message + " " + error.stack);
                console.log("+++++++++++++++++++++++");
                // fatalError = error;
                gotSaturated = true; // treat it like you got a saturated result
              });
            }
          }
          else{
            console.log("Saturated - Deferring request " + theUrl + " for 5 seconds");
            gotSaturated = true;
          }
        }).then((response) => {
          if(got400){
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                got400 = false;
                gotSaturated = false;
                resolve();
              }, 30000);
            });
          }
          else if(gotSaturated){
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                got400 = false;
                gotSaturated = false;
                resolve();
              }, 5000);
            });
          }
          else{
            theResponse = response;
          }
        });
      }, () => {
        // until this function return false, i.e. don't continue
        return !requestCompletedSuccessfully && !fatalError;
      }).then(() => {
        if(!fatalError){
          return theResponse;
        }
        else{
          throw fatalError;
        }
      });
    };

    var recursiveGET = function(url, results, status, followNext){
        var theUrl = url;
        var theResults = results;
        var theStatus = Object.assign({}, {}, status);
        var theFollowNext = followNext;

        console.log(theStatus.serialNumber + " Current Num Results: " + theResults.length + " -> URL: " + theUrl);

        return Promise.try(function(){
            return getUntilNot400(theUrl);
        }).then(function(response){
            var theResponse = response;
            var augmentedPayloads = [];

            if(!theResponse || !theResponse.body){
              console.log("%%%%%%%%%%%%%%%%%%%%%%%%%");
              console.log("% Unexpected Response: ", JSON.stringify(theResponse,null,2));
              console.log("%%%%%%%%%%%%%%%%%%%%%%%%%");
            }
            else if(theResponse.body.messages){
                augmentedPayloads = theResponse.body.messages.map(function(msg){
                    // as it turns out nan is not valid JSON
                    var body;
                    try {
                        body = msg.payload.text.replace(/':nan/g, '":null');
                        body = body.replace(/nan/g, 'null');

                        // workaround for malformation of uknown origin resulting in ' where " should be
                        body = body.replace(/'/g, '"');

                        var datum = JSON.parse(body);
                        datum.timestamp = msg.date;
                        datum.topic = msg.topic;
                        return datum;
                    }
                    catch(exception){
                        console.log(exception);
                        console.log(body);
                        return {
                            timestamp: msg.date,
                            topic: msg.topic
                        };
                    }
                });
            }

            return Promise.try(function(){
                return theResults.concat(augmentedPayloads);
            }).then(function(results){
                var theseResults = results;
                // if there's a non-null status object provided
                // lets reach into the status.filename
                // and modify the entry for status.serialnumber
                if(theStatus && theStatus.filename) {
                    var content = fs.readFileSync(theStatus.filename, 'utf8');
                    if(content == ""){
                        content = "{}";
                    }

                        var json = null;

                        try {
                            json = JSON.parse(content);
                          if (!json[theStatus.serialNumber]) {
                            json[theStatus.serialNumber] = {};
                            }

                        if (theResponse.body.messages) {
                            json[theStatus.serialNumber].numResults = theseResults.length + theResponse.body.messages.length;
                            }
                            else {
                            json[theStatus.serialNumber].complete = true;
                            json[theStatus.serialNumber].error = true;
                            json[theStatus.serialNumber].errorMessage = "No messages found.";
                            }

                        if (theseResults.length > 0) {
                            json[theStatus.serialNumber].timestamp = theseResults[theseResults.length - 1].timestamp;
                            }

                        if (!theResponse.body.next) {
                            json[theStatus.serialNumber].complete = true;
                            }
                            else {
                            json[theStatus.serialNumber].complete = false;
                            }
                        }
                        catch(err){
                            console.log(err);
                            return null;
                        }

                        if(json) {
                        try {
                            fs.writeFileSync(theStatus.filename, JSON.stringify(json));
                        }
                        catch(error){
                            console.log(error.message);
                        }
                    }
                    console.log(theStatus.serialNumber + " Wrote "+ JSON.stringify(json) + " to " + theStatus.filename);
                    return theseResults;
                }
                else {
                    return theseResults; // pass it through
                }
            }).delay(1000).then(function(newResults){
                if(theFollowNext && theResponse.body.next){
                    console.log("Next Found on url " + theUrl);
                    console.log("Last timestamp: " + theResponse.body.messages[theResponse.body.messages.length - 1].date);
                    return recursiveGET(API_BASE_URL + theResponse.body.next, newResults, theStatus, theFollowNext);
                }
                else{
                    console.log(theStatus.serialNumber + " Next Not Found on url " + theUrl);
                    // console.log(response.body);
                    if(theResponse.body.messages && theResponse.body.messages.length > 0) {
                        console.log("Response contained messages field with " + theResponse.body.messages.length
                          + " messages, Last timestamp: " + theResponse.body.messages[theResponse.body.messages.length - 1].date);
                    }
                    else if(!theResponse.body.messages){
                       console.log("Response did not contain any messages field");
                    }
                    else if(theResponse.body.messages.length === 0){
                       console.log("Response contained messages field with zero messages");
                    }
                    else{
                       console.log("Unexpected response content: ");
                       console.log(theResponse.body);
                    }
                    console.log("Total Results: " + newResults.length);
                    return newResults;
                }
            });
        }).catch(function(error){
            console.log("***********************");
            console.log("Error: " + error.message + " " + error.stack);
            console.log("***********************");
            return [];
        });
    };

    // this function returns a string to append to a url path
    // to add the [flat] params object as a querystring
    function urlParams(params){
        var ret = "";
        if(Object.keys(params).length > 0){ // if there are any optional params
            ret += '?';

            var encodeParams = Object.keys(params).map(function(key){
                if(key != "status") { // special case, not an OpenSensors parameter
                    return key + '=' + encodeURIComponent(params[key]);
                }
            });

            ret += encodeParams.join('&');
        }
        return ret;
    }

    // this function returns a string to append to a url path
    // to add the [flat] params object as a querystring
    function collectMessagesBy(x, val, params){
        var API_MESSAGES_BY_PATH = "/v1/messages/" + x;
        var url = API_BASE_URL + API_MESSAGES_BY_PATH;
        if(!val){
            console.error(x + "is required");
            return Promise.resolve({});
        }

        url += "/" + val+ urlParams(params);

        var status = params ? Object.assign({}, {}, params.status) : null;

        return recursiveGET(url, [], status, true); // follow_next = true
    }

    // returns an array of message payloads from the API, augmented with timestamp
    // valid optional param keys are "start-date", "end-date", and "dur"
    function collectMessagesByDevice(device, params){
        return collectMessagesBy("device", device, params);
    }

    // returns an array of message payloads from the API, augmented with timestamp
    // valid optional param keys are "start-date", "end-date", and "dur"
    function collectMessagesByTopic(topic, params){
        return collectMessagesBy("topic", topic, params);
    }

    // returns an array of message payloads from the API, augmented with timestamp
    // valid optional param keys are "start-date", "end-date", and "dur"
    function collectMessagesByUser(user, params){
        return collectMessagesBy("user", user, params);
    }

    // this is what require(opensensors)(config) actually will return
    return {
        messages: {
            byDevice: collectMessagesByDevice,
            byTopic: collectMessagesByTopic,
            byUser: collectMessagesByUser
        }
    };
};<commit_msg>if you don't get 200 keep trying<commit_after>var Promise = require("bluebird");
var rp = require('request-promise');
var fs = require("fs");
var promiseDoWhilst = require('promise-do-whilst');

// config encapsulates opensensors-api-key
// valid keys for config are: api-key (required)
module.exports = function(config) {
    var API_POST_OPTIONS = {
        headers: {
            Accept: "application/json",
            Authorization: "api-key " + config["api-key"]
        }
    };

    var requests_filename = "./current_requests.json";
    var MAX_CONCURRENT_REQUESTS_IN_FLIGHT = 5;

    // on launch delete ther requests file
    if(fs.existsSync(requests_filename)) {
        fs.unlinkSync(requests_filename);
        console.log("Removed existing requests file: " + requests_filename);
    }

    // create it blank
    fs.writeFileSync(requests_filename, JSON.stringify([]));

    var API_BASE_URL = "https://api.opensensors.io";

    var httpGet = function(url, options){
        var options = Object.assign(
          {},
          {
            uri: url,
            resolveWithFullResponse: true,
            json: true,
            simple: false
          },
          options);

        return rp(options);
    };

    // helper (actually workhorse) method that does a GET to a URL
    // it appends the augmented payloads in the response to the second argument that gets passed to it
    // if the response body JSON contains a next element it recursively calls itself
    var retireRequest = function(theUrl){
        try {
            var file_contents = fs.readFileSync(requests_filename, 'utf8');
            if (file_contents.trim() == "") {
                file_contents = "[]";
            }
            var current_requests = JSON.parse(file_contents);

            // remove url from the list
            var i = current_requests.indexOf(theUrl);
            if (i != -1) {
                current_requests.splice(i, 1);
            }
            fs.writeFileSync(requests_filename, JSON.stringify(current_requests));
        }
        catch (e) {
            console.log(requests_filename + ' is corrupt - JSON parse failed');
            // this is also a really bad situation, but I don't know what can be done about it
        }
    };

    var getUntil200 = function(url){
      var theUrl = url;
      var requestsInFlight = 0;
      var current_requests = [];
      var requestCompletedSuccessfully = false;
      var fatalError = null;
      var gotNon200 = false;
      var gotSaturated = false;
      var theResponse = null;

      return promiseDoWhilst(() => {
        // do this promise
        return Promise.try(() => {
          try{
            var file_contents = fs.readFileSync(requests_filename, 'utf8');
            if(file_contents.trim() == ""){
              file_contents = "[]";
            }
            current_requests = JSON.parse(file_contents);
            requestsInFlight = current_requests.length;
          }
          catch(e){
            console.log(requests_filename + ' is corrupt - JSON parse failed -- deleting it');
            fs.unlinkSync(requests_filename);
            file_contents = "[]";
            current_requests = JSON.parse(file_contents);
            requestsInFlight = current_requests.length;
          }

          if(requestsInFlight < MAX_CONCURRENT_REQUESTS_IN_FLIGHT) {
            // if the server is not saturated, go ahead and make a request and increase the saturation level

            // add this request to the list of in flight requests
            // if it's not already in the list
            if(current_requests.indexOf(theUrl) == -1) {
              current_requests.push(theUrl);
              try {
                fs.writeFileSync(requests_filename, JSON.stringify(current_requests));
              }
              catch (e) {
                console.log("Failed to write to " + requests_filename);
                // this is very bad... we really shouldn't proceed at this point with this request
                // it should in fact be as though we were saturated
                // just as though we had gotten a 400 response, just try again sooner
                console.log("Deferring request " + theUrl + " for 5 seconds");
                return;
              }

              return Promise.try(function () {
                return httpGet(theUrl, API_POST_OPTIONS);
              }).then(function (response) {
                if (response.statusCode !== 200) {
                  console.log(theUrl);
                  console.log(response.body);
                  console.log("Got Status Code " + response.statusCode + ", waiting 30 seconds before trying " + theUrl + " again");
                  gotNon200 = true;
                }
                else {
                  // finally! we can retire this request from the list and pass the results on
                  retireRequest(theUrl);
                  requestCompletedSuccessfully = true;
                  return response;
                }
              }).catch(function(error){
                // kill this request status file
                retireRequest(theUrl);
                console.log("+++++++++++++++++++++++");
                console.log("Error: " + error.message + " " + error.stack);
                console.log("+++++++++++++++++++++++");
                // fatalError = error;
                gotSaturated = true; // treat it like you got a saturated result
              });
            }
          }
          else{
            console.log("Saturated - Deferring request " + theUrl + " for 5 seconds");
            gotSaturated = true;
          }
        }).then((response) => {
          if(gotNon200){
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                gotNon200 = false;
                gotSaturated = false;
                resolve();
              }, 30000);
            });
          }
          else if(gotSaturated){
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                gotNon200 = false;
                gotSaturated = false;
                resolve();
              }, 5000);
            });
          }
          else{
            theResponse = response;
          }
        });
      }, () => {
        // until this function return false, i.e. don't continue
        return !requestCompletedSuccessfully && !fatalError;
      }).then(() => {
        if(!fatalError){
          return theResponse;
        }
        else{
          throw fatalError;
        }
      });
    };

    var recursiveGET = function(url, results, status, followNext){
        var theUrl = url;
        var theResults = results;
        var theStatus = Object.assign({}, {}, status);
        var theFollowNext = followNext;

        console.log(theStatus.serialNumber + " Current Num Results: " + theResults.length + " -> URL: " + theUrl);

        return Promise.try(function(){
            return getUntil200(theUrl);
        }).then(function(response){
            var theResponse = response;
            var augmentedPayloads = [];

            if(!theResponse || !theResponse.body){
              console.log("%%%%%%%%%%%%%%%%%%%%%%%%%");
              console.log("% Unexpected Response: ", JSON.stringify(theResponse,null,2));
              console.log("%%%%%%%%%%%%%%%%%%%%%%%%%");
            }
            else if(theResponse.body.messages){
                augmentedPayloads = theResponse.body.messages.map(function(msg){
                    // as it turns out nan is not valid JSON
                    var body;
                    try {
                        body = msg.payload.text.replace(/':nan/g, '":null');
                        body = body.replace(/nan/g, 'null');

                        // workaround for malformation of uknown origin resulting in ' where " should be
                        body = body.replace(/'/g, '"');

                        var datum = JSON.parse(body);
                        datum.timestamp = msg.date;
                        datum.topic = msg.topic;
                        return datum;
                    }
                    catch(exception){
                        console.log(exception);
                        console.log(body);
                        return {
                            timestamp: msg.date,
                            topic: msg.topic
                        };
                    }
                });
            }

            return Promise.try(function(){
                return theResults.concat(augmentedPayloads);
            }).then(function(results){
                var theseResults = results;
                // if there's a non-null status object provided
                // lets reach into the status.filename
                // and modify the entry for status.serialnumber
                if(theStatus && theStatus.filename) {
                    var content = fs.readFileSync(theStatus.filename, 'utf8');
                    if(content == ""){
                        content = "{}";
                    }

                        var json = null;

                        try {
                            json = JSON.parse(content);
                          if (!json[theStatus.serialNumber]) {
                            json[theStatus.serialNumber] = {};
                            }

                        if (theResponse.body.messages) {
                            json[theStatus.serialNumber].numResults = theseResults.length + theResponse.body.messages.length;
                            }
                            else {
                            json[theStatus.serialNumber].complete = true;
                            json[theStatus.serialNumber].error = true;
                            json[theStatus.serialNumber].errorMessage = "No messages found.";
                            }

                        if (theseResults.length > 0) {
                            json[theStatus.serialNumber].timestamp = theseResults[theseResults.length - 1].timestamp;
                            }

                        if (!theResponse.body.next) {
                            json[theStatus.serialNumber].complete = true;
                            }
                            else {
                            json[theStatus.serialNumber].complete = false;
                            }
                        }
                        catch(err){
                            console.log(err);
                            return null;
                        }

                        if(json) {
                        try {
                            fs.writeFileSync(theStatus.filename, JSON.stringify(json));
                        }
                        catch(error){
                            console.log(error.message);
                        }
                    }
                    console.log(theStatus.serialNumber + " Wrote "+ JSON.stringify(json) + " to " + theStatus.filename);
                    return theseResults;
                }
                else {
                    return theseResults; // pass it through
                }
            }).delay(1000).then(function(newResults){
                if(theFollowNext && theResponse.body.next){
                    console.log("Next Found on url " + theUrl);
                    console.log("Last timestamp: " + theResponse.body.messages[theResponse.body.messages.length - 1].date);
                    return recursiveGET(API_BASE_URL + theResponse.body.next, newResults, theStatus, theFollowNext);
                }
                else{
                    console.log(theStatus.serialNumber + " Next Not Found on url " + theUrl);
                    // console.log(response.body);
                    if(theResponse.body.messages && theResponse.body.messages.length > 0) {
                        console.log("Response contained messages field with " + theResponse.body.messages.length
                          + " messages, Last timestamp: " + theResponse.body.messages[theResponse.body.messages.length - 1].date);
                    }
                    else if(!theResponse.body.messages){
                       console.log("Response did not contain any messages field");
                    }
                    else if(theResponse.body.messages.length === 0){
                       console.log("Response contained messages field with zero messages");
                    }
                    else{
                       console.log("Unexpected response content: ");
                       console.log(theResponse.body);
                    }
                    console.log("Total Results: " + newResults.length);
                    return newResults;
                }
            });
        }).catch(function(error){
            console.log("***********************");
            console.log("Error: " + error.message + " " + error.stack);
            console.log("***********************");
            return [];
        });
    };

    // this function returns a string to append to a url path
    // to add the [flat] params object as a querystring
    function urlParams(params){
        var ret = "";
        if(Object.keys(params).length > 0){ // if there are any optional params
            ret += '?';

            var encodeParams = Object.keys(params).map(function(key){
                if(key != "status") { // special case, not an OpenSensors parameter
                    return key + '=' + encodeURIComponent(params[key]);
                }
            });

            ret += encodeParams.join('&');
        }
        return ret;
    }

    // this function returns a string to append to a url path
    // to add the [flat] params object as a querystring
    function collectMessagesBy(x, val, params){
        var API_MESSAGES_BY_PATH = "/v1/messages/" + x;
        var url = API_BASE_URL + API_MESSAGES_BY_PATH;
        if(!val){
            console.error(x + "is required");
            return Promise.resolve({});
        }

        url += "/" + val+ urlParams(params);

        var status = params ? Object.assign({}, {}, params.status) : null;

        return recursiveGET(url, [], status, true); // follow_next = true
    }

    // returns an array of message payloads from the API, augmented with timestamp
    // valid optional param keys are "start-date", "end-date", and "dur"
    function collectMessagesByDevice(device, params){
        return collectMessagesBy("device", device, params);
    }

    // returns an array of message payloads from the API, augmented with timestamp
    // valid optional param keys are "start-date", "end-date", and "dur"
    function collectMessagesByTopic(topic, params){
        return collectMessagesBy("topic", topic, params);
    }

    // returns an array of message payloads from the API, augmented with timestamp
    // valid optional param keys are "start-date", "end-date", and "dur"
    function collectMessagesByUser(user, params){
        return collectMessagesBy("user", user, params);
    }

    // this is what require(opensensors)(config) actually will return
    return {
        messages: {
            byDevice: collectMessagesByDevice,
            byTopic: collectMessagesByTopic,
            byUser: collectMessagesByUser
        }
    };
};<|endoftext|>


<commit_before>/**
 * Adds a time selector to Leaflet based maps.
 **/
if (typeof console == "undefined") {
        this.console = { log: function (msg) { /* do nothing since it would otherwise break IE */} };
}

L.Control.Datetime = L.Control.extend({
	options: {
		  datetimes: new Array()
		, callback: null
		, title: null
                , language: null
		, position: 'topright'
                , visibility: 'visible'
		, vertical: false
                , localtime: false
		, initialDatetime: null
	},

	initialize: function(options) {
		L.Util.setOptions(this, options);
		this._container = L.DomUtil.create('div',
			'leaflet-control-layers leaflet-control-layers-expanded leaflet-control-datetime');
                if (this.options.visibility == 'hidden') {
                    $(this._container).css("visibility", this.options.visibility);
                }
                $(this._container).addClass("hide-on-print");

		L.DomEvent.disableClickPropagation(this._container);
		this._createDatetimeSelector(this._container);
	},

	onAdd: function(map) {
		this._map = map;
                this._map.on("overlayadd overlayremove", this._layersChanged, this);
		return this._container;
	},

	onRemove: function(map) {
		this._container.style.display = 'none';
                this._map.off("overlayadd overlayremove", this._layersChanged, this);
		this._map = null;
	},

	_createDatetimeSelector: function(container) {
		if (this.options.title) {
			var titleDiv = L.DomUtil.create('div', 'leaflet-control-datetime-title', container);
			titleDiv.innerHTML = this.options.title;
		}
                var selectList = L.DomUtil.create('select', 'leaflet-control-datetime-dateselect', container);
                selectList._instance = this;
                var select_index = 0;
		for (var i1=0; i1<this.options.datetimes.length; i1++) {
                    var option = document.createElement("option");
                    var date = this.options.datetimes[i1];
                    option.value = date.toISOString();
                    //option.text = date.toISOString().substr(0,16);
                    var locmoment = moment(date);
                    if (this.options.language != null) {
                        locmoment.locale(this.options.language);
                    }
                    locmoment = locmoment.format('LLLL');
                    locmoment = locmoment.charAt(0).toUpperCase() + locmoment.slice(1);
                    option.text = locmoment;
                    //option.text = date.toString().split(' (')[0];
                    selectList.appendChild(option);
                    if (this.options.initialDatetime != null) {
                        if (this.options.initialDatetime.getTime() == date.getTime()) {
                            select_index = i1;
                            if (this.options.callback != null) {
                                this.options.callback('datetime', option.value);
                            }
                        }
                    }
                }
                if (this.options.initialDatetime == null) {
                    select_index = this._getNowIndex();
                    if (this.options.callback != null) {
                        if (select_index != null) {
                            this.options.callback('datetime', selectList.options[select_index].value);
                        }
                    }
                }
                selectList.onchange = this._datetimeChanged;
                selectList.selectedIndex = select_index;
		//L.DomEvent.addListener(selectList, 'onchange', this._datetimeChanged, L.DomEvent.stopPropagation);

                // Add slider control (jquery-ui)
                var sliderDiv = $(L.DomUtil.create('div', 'leaflet-control-datetime-sliderdiv', container));
                sliderDiv.slider({
                    "class": "leaflet-control-datetime-slider",
                    min: 0,
                    max: this.options.datetimes.length-1,
                    value: select_index,
                    slide: this._sliderChanged
                });
                // Create time slider shade
                sliderRange = $('

');
                sliderDiv.append(sliderRange);

                // Add datetime button controls
                var buttonDiv = L.DomUtil.create('div', 'leaflet-control-datetime-buttondiv', container);
                var startButton = $('<button class="btn btn-default btn-lg"></button>');
                startButton.click(this._datetimeStart);
                startButton.appendTo(buttonDiv);

                var backButton = $('<button class="btn btn-default btn-lg"></button>');
                backButton.click(this._datetimeBack);
                backButton.appendTo(buttonDiv);

                var nowstr = this._('Now');
                nowstr = '<button class="btn btn-default btn-lg">' + nowstr + '</button>';
                var nowButton = $(nowstr);
                nowButton.click(this._datetimeNow);
                nowButton.appendTo(buttonDiv);

                var forwardButton = $('<button class="btn btn-default btn-lg"></button>');
                forwardButton.click(this._datetimeForward);
                forwardButton.appendTo(buttonDiv);

                var endButton = $('<button class="btn btn-default btn-lg"></button>');
                endButton.click(this._datetimeEnd);
                endButton.appendTo(buttonDiv);

                // Add local time checkbox
                var timecb = $('<input>', {
                                type: "checkbox",
                                checked: "checked",
                                "class": "leaflet-control-datetime-localtime-checkbox"
                             });
                var callback = this.options.callback;
                timecb.click(function(pEvent) {
                    var select = $('.leaflet-control-datetime-dateselect')[0];
                    var datetimes = select._instance.options.datetimes;
		    for (var i=0; i<datetimes.length; i++) {
                        var locmoment = moment(datetimes[i]);
                        if (select._instance.options.language != null) {
                            locmoment.locale(select._instance.options.language);
                        }
                        if (this.checked) {
                            //select.options[i].text = datetimes[i].toString().split(' (')[0];
                            locmoment = locmoment.format('LLLL');
                            locmoment = locmoment.charAt(0).toUpperCase() + locmoment.slice(1);
                            select.options[i].text = locmoment;
                        } else {
                            locmoment = locmoment.utcOffset('+0000').format('LLLL');
                            locmoment = locmoment.charAt(0).toUpperCase() + locmoment.slice(1);
                            select.options[i].text = locmoment + ' GMT';
                            //select.options[i].text = datetimes[i].toUTCString();
                        }
                    }
		    // callback
		    if (callback && typeof callback == 'function') {
                        callback('timezone', this.checked);
		    }
                });
                timecb._instance = this;
                var lbl = $('');
                var d = new Date();
                function pad(num) {
                    norm = Math.abs(Math.floor(num));
                    return (norm < 10 ? '0' : '') + norm;
                }
                function formatTimezone() {
                    var local = new Date();
                    var tzo = -local.getTimezoneOffset();
                    var sign = tzo >= 0 ? '+' : '-';
                    return sign + pad(tzo / 60) + ':' + pad(tzo % 60);
                }
                var txt = this._('Use local time') + ' (GMT' + formatTimezone() + ')';
                var spn = lbl.append(timecb).append($('' + txt + ''));
                var localdiv = $('
', {"class": "leaflet-control-datetime-localtime"}).append(spn);
                localdiv.appendTo(container);
	},

        _layersChanged: function(pEvent) {
                // Find min and max time for selector (reversed)
                var datetimes = this.options.datetimes;
                var tmin = datetimes[datetimes.length-1];
                var tmax = datetimes[0];
                // Find min and max time
                this._map.eachLayer(function (layer) {
                    if (layer._overlay !== undefined && layer._overlay === true) {
                        if (layer.timesteps !== undefined) {
                            var timesteps = layer.timesteps;
                            if (timesteps !== null && timesteps.length > 1) {
                                tmin = (timesteps[0] < tmin ? timesteps[0] : tmin);
                                tmax = (timesteps[timesteps.length-1] > tmax ? timesteps[timesteps.length-1] : tmax);
                            }
                        }
                    }
                });
                // Find indices for min and max
                var minDiff = 10000000000;
                var imin = null;
                for (i in datetimes) {
                    var m = Math.abs(tmin - datetimes[i]);
                    if (m < minDiff) {
                        minDiff = m;
                        imin = parseInt(i);
                    }
                }
                var minDiff = 10000000000;
                var imax = null;
                for (i in datetimes) {
                    var m = Math.abs(tmax - datetimes[i]);
                    if (m < minDiff) {
                        minDiff = m;
                        imax = parseInt(i);
                    }
                }
                var sliderRange = $("#leaflet-control-datetime-range");
                if (imax > imin) {
                    // Calculate slider percentages
                    ifull = datetimes.length-1;
                    var pmin = imin/ifull*100.0;
                    var pmax = imax/ifull*100.0;
                    var pwidth = pmax - pmin;
                    // Set slider range to span min to max
                    sliderRange.css({"margin-left": pmin + "%", "width": pwidth + "%"});
                } else {
                    // Unset slider range when no overlays are selected
                    sliderRange.css({"margin-left": "", "width": ""});
                }
        },

        _datetimeUpdate: function(select) {
                var date = select.options[select.selectedIndex].value;
                var container = select.parentElement;

		// callback
		if (this.options.callback && typeof this.options.callback == 'function') {
			this.options.callback('datetime', date);
		}
        },

	_datetimeChanged: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
		var inst = select._instance;
                var index = select.selectedIndex;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
                inst._datetimeUpdate(select);
	},

	_sliderChanged: function(pEvent, elem) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                var index = Math.max(Math.min(elem.value, select.length - 1), 0);
                select.selectedIndex = index;
                elem.value = index;
		var inst = select._instance;
                inst._datetimeUpdate(select);
	},

	_datetimeStart: function(pEvent) {
		var elem = pEvent.target;
                var index = 0;
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
		var inst = select._instance;
                inst._datetimeUpdate(select);
	},

	_datetimeBack: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                var index = Math.max(select.selectedIndex - 1, 0);
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
                var inst = select._instance;
                inst._datetimeUpdate(select);
	},

	_datetimeNow: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
		var inst = select._instance;
                var index = inst._getNowIndex();
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
                inst._datetimeUpdate(select);
	},

        _getNowIndex: function() {
                // Find nearest index
                var options = this.options.datetimes;
                var now = new Date();
                var minDiff = 10000000000;
                var index = null;
                for (i in options) {
                    var m = Math.abs(now - options[i]);
                    if (m < minDiff) {
                        minDiff = m;
                        index = i;
                    }
                }
                return index;
        },

	_datetimeForward: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                var index = Math.min(select.selectedIndex + 1, select.length - 1);
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
		var inst = select._instance;
                inst._datetimeUpdate(select);
	},

	_datetimeEnd: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                var index = select.length - 1;
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
		var inst = select._instance;
                inst._datetimeUpdate(select);
	},

        _: function(key) {
                var lang = this.options.language;
                var i18n = {
                        en: {
                                  'Use local time': 'Use local time',
                                  'Now': 'Now'
                        },
                        da: {
                                  'Use local time': 'Brug lokal tid',
                                  'Now': 'Nu'
                        }
                };

                if (typeof i18n[lang] != 'undefined'
                                && typeof i18n[lang][key] != 'undefined') {
                        return  i18n[lang][key];
                }
                return key;
        },

});

L.Control.datetime = function(options) { return new L.Control.Datetime(options); };
<commit_msg>added comment<commit_after>/**
 * Adds a time selector to Leaflet based maps.
 **/
if (typeof console == "undefined") {
        this.console = { log: function (msg) { /* do nothing since it would otherwise break IE */} };
}

L.Control.Datetime = L.Control.extend({
	options: {
		  datetimes: new Array()
		, callback: null
		, title: null
                , language: null
		, position: 'topright'
                , visibility: 'visible'
		, vertical: false
                , localtime: false
		, initialDatetime: null
	},

	initialize: function(options) {
		L.Util.setOptions(this, options);
		this._container = L.DomUtil.create('div',
			'leaflet-control-layers leaflet-control-layers-expanded leaflet-control-datetime');
                if (this.options.visibility == 'hidden') {
                    $(this._container).css("visibility", this.options.visibility);
                }
                $(this._container).addClass("hide-on-print");

		L.DomEvent.disableClickPropagation(this._container);
		this._createDatetimeSelector(this._container);
	},

	onAdd: function(map) {
		this._map = map;
                this._map.on("overlayadd overlayremove", this._layersChanged, this);
		return this._container;
	},

	onRemove: function(map) {
		this._container.style.display = 'none';
                this._map.off("overlayadd overlayremove", this._layersChanged, this);
		this._map = null;
	},

	_createDatetimeSelector: function(container) {
		if (this.options.title) {
			var titleDiv = L.DomUtil.create('div', 'leaflet-control-datetime-title', container);
			titleDiv.innerHTML = this.options.title;
		}
                var selectList = L.DomUtil.create('select', 'leaflet-control-datetime-dateselect', container);
                selectList._instance = this;
                var select_index = 0;
		for (var i1=0; i1<this.options.datetimes.length; i1++) {
                    var option = document.createElement("option");
                    var date = this.options.datetimes[i1];
                    option.value = date.toISOString();
                    //option.text = date.toISOString().substr(0,16);
                    var locmoment = moment(date);
                    if (this.options.language != null) {
                        locmoment.locale(this.options.language);
                    }
                    locmoment = locmoment.format('LLLL');
                    locmoment = locmoment.charAt(0).toUpperCase() + locmoment.slice(1);
                    option.text = locmoment;
                    //option.text = date.toString().split(' (')[0];
                    selectList.appendChild(option);
                    if (this.options.initialDatetime != null) {
                        if (this.options.initialDatetime.getTime() == date.getTime()) {
                            select_index = i1;
                            if (this.options.callback != null) {
                                this.options.callback('datetime', option.value);
                            }
                        }
                    }
                }
                if (this.options.initialDatetime == null) {
                    select_index = this._getNowIndex();
                    if (this.options.callback != null) {
                        if (select_index != null) {
                            this.options.callback('datetime', selectList.options[select_index].value);
                        }
                    }
                }
                selectList.onchange = this._datetimeChanged;
                selectList.selectedIndex = select_index;
		//L.DomEvent.addListener(selectList, 'onchange', this._datetimeChanged, L.DomEvent.stopPropagation);

                // Add slider control (jquery-ui)
                var sliderDiv = $(L.DomUtil.create('div', 'leaflet-control-datetime-sliderdiv', container));
                sliderDiv.slider({
                    "class": "leaflet-control-datetime-slider",
                    min: 0,
                    max: this.options.datetimes.length-1,
                    value: select_index,
                    slide: this._sliderChanged
                });
                // Create time slider shade
                sliderRange = $('

');
                sliderDiv.append(sliderRange);

                // Add datetime button controls
                var buttonDiv = L.DomUtil.create('div', 'leaflet-control-datetime-buttondiv', container);
                var startButton = $('<button class="btn btn-default btn-lg"></button>');
                startButton.click(this._datetimeStart);
                startButton.appendTo(buttonDiv);

                var backButton = $('<button class="btn btn-default btn-lg"></button>');
                backButton.click(this._datetimeBack);
                backButton.appendTo(buttonDiv);

                var nowstr = this._('Now');
                nowstr = '<button class="btn btn-default btn-lg">' + nowstr + '</button>';
                var nowButton = $(nowstr);
                nowButton.click(this._datetimeNow);
                nowButton.appendTo(buttonDiv);

                var forwardButton = $('<button class="btn btn-default btn-lg"></button>');
                forwardButton.click(this._datetimeForward);
                forwardButton.appendTo(buttonDiv);

                var endButton = $('<button class="btn btn-default btn-lg"></button>');
                endButton.click(this._datetimeEnd);
                endButton.appendTo(buttonDiv);

                // Add local time checkbox
                var timecb = $('<input>', {
                                type: "checkbox",
                                checked: "checked",
                                "class": "leaflet-control-datetime-localtime-checkbox"
                             });
                var callback = this.options.callback;
                timecb.click(function(pEvent) {
                    var select = $('.leaflet-control-datetime-dateselect')[0];
                    var datetimes = select._instance.options.datetimes;
		    for (var i=0; i<datetimes.length; i++) {
                        var locmoment = moment(datetimes[i]);
                        if (select._instance.options.language != null) {
                            locmoment.locale(select._instance.options.language);
                        }
                        if (this.checked) {
                            //select.options[i].text = datetimes[i].toString().split(' (')[0];
                            locmoment = locmoment.format('LLLL');
                            locmoment = locmoment.charAt(0).toUpperCase() + locmoment.slice(1);
                            select.options[i].text = locmoment;
                        } else {
                            locmoment = locmoment.utcOffset('+0000').format('LLLL');
                            locmoment = locmoment.charAt(0).toUpperCase() + locmoment.slice(1);
                            select.options[i].text = locmoment + ' GMT';
                            //select.options[i].text = datetimes[i].toUTCString();
                        }
                    }
		    // callback
		    if (callback && typeof callback == 'function') {
                        callback('timezone', this.checked);
		    }
                });
                timecb._instance = this;
                var lbl = $('');
                var d = new Date();
                function pad(num) {
                    norm = Math.abs(Math.floor(num));
                    return (norm < 10 ? '0' : '') + norm;
                }
                function formatTimezone() {
                    var local = new Date();
                    var tzo = -local.getTimezoneOffset();
                    var sign = tzo >= 0 ? '+' : '-';
                    return sign + pad(tzo / 60) + ':' + pad(tzo % 60);
                }
                var txt = this._('Use local time') + ' (GMT' + formatTimezone() + ')';
                var spn = lbl.append(timecb).append($('' + txt + ''));
                var localdiv = $('
', {"class": "leaflet-control-datetime-localtime"}).append(spn);
                localdiv.appendTo(container);
	},

        _layersChanged: function(pEvent) {
                // Find min and max time for selector (reversed)
                var datetimes = this.options.datetimes;
                var tmin = datetimes[datetimes.length-1];
                var tmax = datetimes[0];
                // Find min and max time
                this._map.eachLayer(function (layer) {
                    if (layer._overlay !== undefined && layer._overlay === true) {
                        if (layer.timesteps !== undefined) {
                            var timesteps = layer.timesteps;
                            if (timesteps !== null && timesteps.length > 1) {
                                tmin = (timesteps[0] < tmin ? timesteps[0] : tmin);
                                tmax = (timesteps[timesteps.length-1] > tmax ? timesteps[timesteps.length-1] : tmax);
                            }
                        }
                    }
                });
                // Find indices for min and max
                var minDiff = 10000000000;
                var imin = null;
                for (i in datetimes) {
                    var m = Math.abs(tmin - datetimes[i]);
                    if (m < minDiff) {
                        minDiff = m;
                        imin = parseInt(i);
                    }
                }
                var minDiff = 10000000000;
                var imax = null;
                for (i in datetimes) {
                    var m = Math.abs(tmax - datetimes[i]);
                    if (m < minDiff) {
                        minDiff = m;
                        imax = parseInt(i);
                    }
                }
                // FIXME: Do not access the DOM
                var sliderRange = $("#leaflet-control-datetime-range");
                if (imax > imin) {
                    // Calculate slider percentages
                    ifull = datetimes.length-1;
                    var pmin = imin/ifull*100.0;
                    var pmax = imax/ifull*100.0;
                    var pwidth = pmax - pmin;
                    // Set slider range to span min to max
                    sliderRange.css({"margin-left": pmin + "%", "width": pwidth + "%"});
                } else {
                    // Unset slider range when no overlays are selected
                    sliderRange.css({"margin-left": "", "width": ""});
                }
        },

        _datetimeUpdate: function(select) {
                var date = select.options[select.selectedIndex].value;
                var container = select.parentElement;

		// callback
		if (this.options.callback && typeof this.options.callback == 'function') {
			this.options.callback('datetime', date);
		}
        },

	_datetimeChanged: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
		var inst = select._instance;
                var index = select.selectedIndex;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
                inst._datetimeUpdate(select);
	},

	_sliderChanged: function(pEvent, elem) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                var index = Math.max(Math.min(elem.value, select.length - 1), 0);
                select.selectedIndex = index;
                elem.value = index;
		var inst = select._instance;
                inst._datetimeUpdate(select);
	},

	_datetimeStart: function(pEvent) {
		var elem = pEvent.target;
                var index = 0;
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
		var inst = select._instance;
                inst._datetimeUpdate(select);
	},

	_datetimeBack: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                var index = Math.max(select.selectedIndex - 1, 0);
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
                var inst = select._instance;
                inst._datetimeUpdate(select);
	},

	_datetimeNow: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
		var inst = select._instance;
                var index = inst._getNowIndex();
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
                inst._datetimeUpdate(select);
	},

        _getNowIndex: function() {
                // Find nearest index
                var options = this.options.datetimes;
                var now = new Date();
                var minDiff = 10000000000;
                var index = null;
                for (i in options) {
                    var m = Math.abs(now - options[i]);
                    if (m < minDiff) {
                        minDiff = m;
                        index = i;
                    }
                }
                return index;
        },

	_datetimeForward: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                var index = Math.min(select.selectedIndex + 1, select.length - 1);
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
		var inst = select._instance;
                inst._datetimeUpdate(select);
	},

	_datetimeEnd: function(pEvent) {
                var select = $('select.leaflet-control-datetime-dateselect')[0];
                var index = select.length - 1;
                select.selectedIndex = index;
                $('.leaflet-control-datetime-sliderdiv').slider("value", index);
		var inst = select._instance;
                inst._datetimeUpdate(select);
	},

        _: function(key) {
                var lang = this.options.language;
                var i18n = {
                        en: {
                                  'Use local time': 'Use local time',
                                  'Now': 'Now'
                        },
                        da: {
                                  'Use local time': 'Brug lokal tid',
                                  'Now': 'Nu'
                        }
                };

                if (typeof i18n[lang] != 'undefined'
                                && typeof i18n[lang][key] != 'undefined') {
                        return  i18n[lang][key];
                }
                return key;
        },

});

L.Control.datetime = function(options) { return new L.Control.Datetime(options); };
<|endoftext|>