<commit_before>
// Constants
const NEXT_BLOCK_LABELS = ["next block", "2nd block", "3rd block"]

// State
var chart
var lastMempoolDataUpdate = 0
var processedMempool = null
var currentTx = null

function generateColorPattern(patternAreas) {

  const patternColors = [
    ['LightGreen', 'YellowGreen'],
    ['YellowGreen', 'Bisque'],
    ['Bisque', 'Salmon'],
    ['Salmon', 'HotPink'],
  ]

  var colorPattern = []
  var c_counter = 0
  for (area in patternAreas) {
    logLimits = chroma.limits(patternAreas[area], 'l', patternAreas[area].length);
    pattern = chroma.scale(patternColors[c_counter])
      .mode('lch').classes(logLimits)
      .colors(patternAreas[area].length);

    colorPattern = colorPattern.concat(pattern)
    c_counter++
  }

  return colorPattern
}

function processApiMempoolDataForChart(response) {

  console.log('Mempool data written to db @', response.timestamp)
  lastMempoolDataUpdate = response.timestamp

  const mempoolSize = +(response.mempoolSize / 1000000).toFixed(2)

  const patternAreas = {
    '0to10': [],
    '11to100': [],
    '101to1k': [],
    'from1001': []
  }

  const ticks = []
  const lines = []
  const blocks = []
  const rowData = [
    [],[]
  ]

  for (var feerate in response.mempoolData) {
    rowData[0].push(feerate.toString())
    rowData[1].push(response.mempoolData[feerate])

    // TODO: (0xb10c) Why do we do this and what does it?
    log1pOfCount = response.mempoolData[feerate]
    if (feerate <= 10) {
      patternAreas['0to10'].push(log1pOfCount)
    } else if (feerate <= 100) {
      patternAreas['11to100'].push(log1pOfCount)
    } else if (feerate <= 1000) {
      patternAreas['101to1k'].push(log1pOfCount)
    } else {
      patternAreas['from1001'].push(log1pOfCount)
    }
  }

  for (var position in response.positionsInGreedyBlocks) {
    if (position < 3) {
      blocks.push(response.positionsInGreedyBlocks[position])
      // add lines to show estimated next blocks on the mempool graph
      lines.push({
        value: response.positionsInGreedyBlocks[position]
      })
    }
  }

  let colorPattern = generateColorPattern(patternAreas)

  // Sum all txs to get the total number of tx in the mempool
  const sum = Object.values(rowData[1]).reduce((a, b) => a + b, 0)
  return {
    "mempoolSize": mempoolSize,
    "blocks": blocks,
    "colorPattern": colorPattern,
    "lines": lines,
    "rowData": rowData,
    "sum": sum
  }
}

window.onload = function () {
  // Add event listeners to the search bar
  document.getElementById('button-lookup-txid').addEventListener('click', handleTxSearch)

  // add scroll listener for icon
  $(window).scroll(function() {
    if ($(".navbar").offset().top > 60) {
        $(".navbar").addClass("scrolled");
    } else {
        $(".navbar").removeClass("scrolled");
    }
  });

  // Get the mempool data
  axios.get('https://mempool.observer/api/mempool')
    .then(function (response) {
      console.log(response.data)
      processedMempool = processApiMempoolDataForChart(response.data)
      draw(processedMempool)
      updateCurrentMempoolCard(processedMempool)
      redraw() // init redraw loop
    })
}

function draw(processed) {
  chart = c3.generate({
    data: {
      rows: processed.rowData,
      type: 'bar',
      groups: [processed.rowData[0]],
      order: null,
    },
    point: {
      show: false
    },
    legend: {
      show: false
    },
    tooltip: {
      grouped: false,
      format: {
        name: function (name) {return name + ' sat/vbyte'},
        value: function (value) {return value + ' transactions'},
      }
    },
    size: {
      height: 750
    },
    padding: {
      top: 20
    },
    color: {
      pattern: processed.colorPattern
    },
    grid: {
      y: {
        lines: processed.lines
      }
    },
    axis: {
      y: {
        padding: {top: 0},
        show: true,
        label: {
          text: 'unconfirmed transactions'
        },
      },
      y2: {
        outer: false,
        padding: {top: 0, bottom:0},
        default: [0, processed.sum],
        label: {
          text: 'estimated blocks'
        },
        show: true,
        tick: {
          format: function (d) {return NEXT_BLOCK_LABELS[processed.blocks.indexOf(d)]},
          values: processed.blocks
        }
      }
    }
  })

  if(currentTx != null){
    const feeRate = Math.floor(currentTx.fee/currentTx.size)
    drawTxIdInChartByFeeRate(currentTx.txid, feeRate)
  }

}

function redraw() {
  setTimeout(function () {
    axios.get('https://mempool.observer/api/mempool')
      .then(function (response) {
        processed = processApiMempoolDataForChart(response.data)
        draw(processed)
        updateCurrentMempoolCard(processed)
        redraw()
      });
  }, 60000);
}

function updateCurrentMempoolCard(processed) { //TODO: Change name of function

  const spanTxCount = document.getElementById('current-mempool-count')
  const spanMempoolSize = document.getElementById('current-mempool-size')

  const txCountInMempool = processed.sum
  const txSizeInMempool = processed.mempoolSize

  spanTxCount.innerHTML = txCountInMempool
  spanMempoolSize.innerHTML = txSizeInMempool

  timeSinceLastUpdate = 0
  updateCurrentMempoolCardLastUpdated()
}

function updateCurrentMempoolCardLastUpdated() {
  // format as milliseconds since 1.1.1970 UTC
  // * 1000 to convert from seconds to milliseconds
  const millislastMempoolDataUpdate = lastMempoolDataUpdate * 1000
  const minutes = Math.floor((Date.now() -  millislastMempoolDataUpdate) / 1000 / 60)

  document.getElementById('current-mempool-last-update').innerHTML = (minutes)
}

// Update the 'Time since last update' text
setInterval(function() {
  updateCurrentMempoolCardLastUpdated()
}, 10000);

async function handleTxSearch() {
  $('#input-lookup-txid').removeClass("is-invalid" ) // Clear alerts

  txId = document.getElementById('input-lookup-txid').value

  // Check if txid is invalid
  if (/^[a-fA-F0-9]{64}$/.test(txId) == false) {
    $('#invalid-feedback').html('Invalid Bitcoin transaction id.') // Set alert message
    $('#input-lookup-txid').addClass("is-invalid")  // Show alert
  } else {
    try {
      const tx = await getTxFromApi(txId) // in sat/vbyte
      currentTx = tx

      if (tx.status.confirmed){

        let minutes_since_confirmation = Math.floor((Date.now() -  tx.status.block_time * 1000) / 1000 / 60)
        let error = `The transaction is already confirmed and therefore not in the mempool. (block ${tx.status.block_height}, ${minutes_since_confirmation} minutes ago)`
        console.error(error)

        $('#invalid-feedback').html(error)
        $('#input-lookup-txid').addClass("is-invalid")
      } else {
        const feeRate =  Math.floor(tx.fee/tx.size)
        drawTxIdInChartByFeeRate(tx.txid, feeRate)
      }

    } catch (error) {
      console.error(error)
      $('#invalid-feedback').html(error)
      $('#input-lookup-txid').addClass("is-invalid")
    }
  }
}

function drawTxIdInChartByFeeRate(txid,feeRate){
  const position = getTxPostionInChartByFeeRate(feeRate)
  chart.ygrids.remove({class:'user-tx'});
  chart.ygrids.add([{value: position, text: txid.substring(0, 8) + "...", class:'user-tx', position: 'middle'}]);
  chart.tooltip.show({x: feeRate, index: 0, id: '1' })
}

function getTxPostionInChartByFeeRate(feeRate){
  let position = 0
  for(index in processedMempool.rowData[0]){
    if(processedMempool.rowData[0][index] <= feeRate){
      position += processedMempool.rowData[1][index]
    }else{
      break;
    }
  }
  return position
}

function getTxFromApi(txId) {
  return axios.get(`https://blockstream.info/api/tx/${txId}`)
    .then(res => res.data)
    .catch(e => {
      console.log('Error getting data from explorer:', e)
      throw new Error('Could not find your transaction in the bitcoin network. Wait a few minutes before trying again.')
    })
}

<commit_msg>Fix search bar bug<commit_after>
// Constants
const NEXT_BLOCK_LABELS = ["next block", "2nd block", "3rd block"]

// State
var chart
var lastMempoolDataUpdate = 0
var processedMempool = null
var currentTx = null

function generateColorPattern(patternAreas) {

  const patternColors = [
    ['LightGreen', 'YellowGreen'],
    ['YellowGreen', 'Bisque'],
    ['Bisque', 'Salmon'],
    ['Salmon', 'HotPink'],
  ]

  var colorPattern = []
  var c_counter = 0
  for (area in patternAreas) {
    logLimits = chroma.limits(patternAreas[area], 'l', patternAreas[area].length);
    pattern = chroma.scale(patternColors[c_counter])
      .mode('lch').classes(logLimits)
      .colors(patternAreas[area].length);

    colorPattern = colorPattern.concat(pattern)
    c_counter++
  }

  return colorPattern
}

function processApiMempoolDataForChart(response) {

  console.log('Mempool data written to db @', response.timestamp)
  lastMempoolDataUpdate = response.timestamp

  const mempoolSize = +(response.mempoolSize / 1000000).toFixed(2)

  const patternAreas = {
    '0to10': [],
    '11to100': [],
    '101to1k': [],
    'from1001': []
  }

  const ticks = []
  const lines = []
  const blocks = []
  const rowData = [
    [],[]
  ]

  for (var feerate in response.mempoolData) {
    rowData[0].push(feerate.toString())
    rowData[1].push(response.mempoolData[feerate])

    // TODO: (0xb10c) Why do we do this and what does it?
    log1pOfCount = response.mempoolData[feerate]
    if (feerate <= 10) {
      patternAreas['0to10'].push(log1pOfCount)
    } else if (feerate <= 100) {
      patternAreas['11to100'].push(log1pOfCount)
    } else if (feerate <= 1000) {
      patternAreas['101to1k'].push(log1pOfCount)
    } else {
      patternAreas['from1001'].push(log1pOfCount)
    }
  }

  for (var position in response.positionsInGreedyBlocks) {
    if (position < 3) {
      blocks.push(response.positionsInGreedyBlocks[position])
      // add lines to show estimated next blocks on the mempool graph
      lines.push({
        value: response.positionsInGreedyBlocks[position]
      })
    }
  }

  let colorPattern = generateColorPattern(patternAreas)

  // Sum all txs to get the total number of tx in the mempool
  const sum = Object.values(rowData[1]).reduce((a, b) => a + b, 0)
  return {
    "mempoolSize": mempoolSize,
    "blocks": blocks,
    "colorPattern": colorPattern,
    "lines": lines,
    "rowData": rowData,
    "sum": sum
  }
}

window.onload = function () {
  // Add event listeners to the search bar
  document.getElementById('button-lookup-txid').addEventListener('click', handleTxSearch)

  // add scroll listener for icon
  $(window).scroll(function() {
    if ($(".navbar").offset().top > 60) {
        $(".navbar").addClass("scrolled");
    } else {
        $(".navbar").removeClass("scrolled");
    }
  });

  // Get the mempool data
  axios.get('https://mempool.observer/api/mempool')
    .then(function (response) {
      console.log(response.data)
      processedMempool = processApiMempoolDataForChart(response.data)
      draw(processedMempool)
      updateCurrentMempoolCard(processedMempool)
      redraw() // init redraw loop
    })
}

function draw(processed) {
  chart = c3.generate({
    data: {
      rows: processed.rowData,
      type: 'bar',
      groups: [processed.rowData[0]],
      order: null,
    },
    point: {
      show: false
    },
    legend: {
      show: false
    },
    tooltip: {
      grouped: false,
      format: {
        name: function (name) {return name + ' sat/vbyte'},
        value: function (value) {return value + ' transactions'},
      }
    },
    size: {
      height: 750
    },
    padding: {
      top: 20
    },
    color: {
      pattern: processed.colorPattern
    },
    grid: {
      y: {
        lines: processed.lines
      }
    },
    axis: {
      y: {
        padding: {top: 0},
        show: true,
        label: {
          text: 'unconfirmed transactions'
        },
      },
      y2: {
        outer: false,
        padding: {top: 0, bottom:0},
        default: [0, processed.sum],
        label: {
          text: 'estimated blocks'
        },
        show: true,
        tick: {
          format: function (d) {return NEXT_BLOCK_LABELS[processed.blocks.indexOf(d)]},
          values: processed.blocks
        }
      }
    }
  })

  if(currentTx != null){
    const feeRate = Math.floor(currentTx.fee/currentTx.size)
    drawTxIdInChartByFeeRate(currentTx.txid, feeRate)
  }

}

function redraw() {
  setTimeout(function () {
    axios.get('https://mempool.observer/api/mempool')
      .then(function (response) {
        processed = processApiMempoolDataForChart(response.data)
        draw(processed)
        updateCurrentMempoolCard(processed)
        redraw()
      });
  }, 60000);
}

function updateCurrentMempoolCard(processed) { //TODO: Change name of function

  const spanTxCount = document.getElementById('current-mempool-count')
  const spanMempoolSize = document.getElementById('current-mempool-size')

  const txCountInMempool = processed.sum
  const txSizeInMempool = processed.mempoolSize

  spanTxCount.innerHTML = txCountInMempool
  spanMempoolSize.innerHTML = txSizeInMempool

  timeSinceLastUpdate = 0
  updateCurrentMempoolCardLastUpdated()
}

function updateCurrentMempoolCardLastUpdated() {
  // format as milliseconds since 1.1.1970 UTC
  // * 1000 to convert from seconds to milliseconds
  const millislastMempoolDataUpdate = lastMempoolDataUpdate * 1000
  const minutes = Math.floor((Date.now() -  millislastMempoolDataUpdate) / 1000 / 60)

  document.getElementById('current-mempool-last-update').innerHTML = (minutes)
}

// Update the 'Time since last update' text
setInterval(function() {
  updateCurrentMempoolCardLastUpdated()
}, 10000);

async function handleTxSearch() {
  $('#input-lookup-txid').removeClass("is-invalid" ) // Clear alerts

  txId = document.getElementById('input-lookup-txid').value

  // Check if txid is invalid
  if (/^[a-fA-F0-9]{64}$/.test(txId) == false) {
    $('#invalid-feedback').html('Invalid Bitcoin transaction id.') // Set alert message
    $('#input-lookup-txid').addClass("is-invalid")  // Show alert
  } else {
    try {
      const tx = await getTxFromApi(txId) // in sat/vbyte
      currentTx = tx

      if (tx.status.confirmed){

        let minutes_since_confirmation = Math.floor((Date.now() -  tx.status.block_time * 1000) / 1000 / 60)
        let error = `The transaction is already confirmed and therefore not in the mempool. (block ${tx.status.block_height}, ${minutes_since_confirmation} minutes ago)`
        console.error(error)

        $('#invalid-feedback').html(error)
        $('#input-lookup-txid').addClass("is-invalid")
      } else {
        const feeRate =  Math.floor(tx.fee/tx.size)
        drawTxIdInChartByFeeRate(tx.txid, feeRate)
      }

    } catch (error) {
      console.error(error)
      $('#invalid-feedback').html(error)
      $('#input-lookup-txid').addClass("is-invalid")
    }
  }
}

function drawTxIdInChartByFeeRate(txid,feeRate){
  const position = getTxPostionInChartByFeeRate(feeRate)
  // Remove existing line in case
  chart.ygrids.remove({class:'user-tx'});
  // Draw a new line, but wait 500ms for c3.js to not bug out
  setTimeout(function(){ chart.ygrids.add([{value: position, text: txid.substring(0, 8) + "...", class:'user-tx', position: 'middle'}]); }, 500)
  chart.tooltip.show({x: feeRate, index: 0, id: '1' })
}

function getTxPostionInChartByFeeRate(feeRate){
  let position = 0
  for(index in processedMempool.rowData[0]){
    if(processedMempool.rowData[0][index] <= feeRate){
      position += processedMempool.rowData[1][index]
    }else{
      break;
    }
  }
  return position
}

function getTxFromApi(txId) {
  return axios.get(`https://blockstream.info/api/tx/${txId}`)
    .then(res => res.data)
    .catch(e => {
      console.log('Error getting data from explorer:', e)
      throw new Error('Could not find your transaction in the bitcoin network. Wait a few minutes before trying again.')
    })
}

<|endoftext|>


<commit_before>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Affero General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

(function() {

    'use strict';

    /**
     * @ngdoc directive
     * @restrict A
     * @name requisition-product-grid.productGridCell
     *
     * @description
     * Responsible for rendering the product grid cell based on the column source and requisitions type.
     * It also keeps track of the validation as well as changes made to the related cells.
     *
     * @example
     * Here we extend our product grid cell with the directive.
     * ```
     * <td ng-repeat="column in visibleColumns | orderBy : 'displayOrder'" product-grid-cell></td>
     * ```
     */
    angular
        .module('requisition-product-grid')
        .directive('productGridCell', productGridCell);

    productGridCell.$inject = [
        '$q', '$timeout', '$templateRequest', '$compile', 'requisitionValidator', 'authorizationService',
        'TEMPLATE_COLUMNS', 'COLUMN_TYPES', 'REQUISITION_RIGHTS'
    ];

    function productGridCell($q, $timeout, $templateRequest, $compile, requisitionValidator, authorizationService,
                            TEMPLATE_COLUMNS, COLUMN_TYPES, REQUISITION_RIGHTS) {

        return {
            restrict: 'A',
            link: link,
            scope: {
                requisition: '=',
                column: '=',
                lineItem: '='
            }
        };

        function link(scope, element) {
            var requisition = scope.requisition,
                column = scope.column,
                lineItem = scope.lineItem;

            scope.lineItem = lineItem;
            scope.column = column;
            scope.validate = validate;
            scope.update = update;
            scope.isReadOnly = isReadOnly();
            scope.canNotSkip = canNotSkip();

            if(!scope.isReadOnly){
                scope.$watch(function(){
                    return lineItem[column.name];
                }, function(newValue, oldValue) {
                    if (newValue !== oldValue) {
                        lineItem.updateDependentFields(column, requisition);
                    }
                });
            }

            scope.$watch(function(){
                if(lineItem.skipped){
                    return false;
                }
                return lineItem.$errors[column.name];
            }, function(error){
                if (lineItem.difference[column.name]) {
                    scope.invalidMessage = error ? displayError(error, lineItem.difference[column.name]) : undefined;
                } else {
                    scope.invalidMessage = error ? error : undefined;
                }
            });

            scope.$watch(function(){
                if(lineItem.skipped){
                    return false;
                }
                return lineItem.difference[column.name];
            }, function(difference){
                scope.invalidMessage = difference ? displayError(lineItem.$errors[column.name], difference) : undefined;
            });

            scope.$on('openlmisInvalid.update', validate);

            updateCellContents();

            function updateCellContents(){
                var templateUrl = '';
                if(column.name === TEMPLATE_COLUMNS.SKIPPED) {
                    templateUrl = 'requisition-product-grid/product-grid-cell-skip.html';
                } else if(column.name === TEMPLATE_COLUMNS.TOTAL_LOSSES_AND_ADJUSTMENTS) {
                    templateUrl = 'requisition-product-grid/product-grid-cell-total-losses-and-adjustments.html';
                } else if(column.$type === COLUMN_TYPES.NUMERIC && !scope.isReadOnly){
                    templateUrl = 'requisition-product-grid/product-grid-cell-input-numeric.html';
                } else if(!scope.isReadOnly) {
                    templateUrl = 'requisition-product-grid/product-grid-cell-input-text.html';
                } else if(column.$type === COLUMN_TYPES.CURRENCY) {
                    templateUrl = 'requisition-product-grid/product-grid-cell-currency.html';
                } else {
                    templateUrl = 'requisition-product-grid/product-grid-cell-text.html'
                }
                $templateRequest(templateUrl).then(replaceCell);
            }

            function replaceCell(newTemplate) {
                var cellWrapperPath = 'requisition-product-grid/product-grid-cell.html';
                $templateRequest(cellWrapperPath).then(function(template){
                    template = angular.element(template);
                    template.html(newTemplate);

                    var cell = $compile(template)(scope);
                    element.replaceWith(cell);

                    element = cell;

                    if(column.name === TEMPLATE_COLUMNS.PRODUCT_NAME) {
                        element.wrapInner('

');
                    }
                });
            }

            function update() {
                lineItem.updateDependentFields(column, requisition);
                validate();
            }

            function validate() {
                return requisitionValidator.validateLineItem(
                    scope.lineItem,
                    requisition.template.getColumns(!scope.lineItem.$program.fullSupply),
                    requisition
                );
            }

            function isReadOnly() {
                return lineItem.isReadOnly(requisition, column);
            }

            function canNotSkip() {
                return !requisition.$isInitiated() && !requisition.$isRejected() &&
                    !(hasAuthorizeRightForProgram() && requisition.$isSubmitted()) ||
                    !lineItem.canBeSkipped(scope.requisition);
            }

            function hasAuthorizeRightForProgram() {
                return authorizationService.hasRight(REQUISITION_RIGHTS.REQUISITION_AUTHORIZE, {
                    programCode: requisition.program.code
                });
            }

            function displayError(error, difference) {
                return difference == 0 ? undefined : error.concat('. The difference between the calculated and entered value is ', difference, '.');
            }
        }
    }

})();
<commit_msg>MALAWISUP-337: LMIS form does not highlight to show where the error is on the page, only the page number highlights<commit_after>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Affero General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

(function() {

    'use strict';

    /**
     * @ngdoc directive
     * @restrict A
     * @name requisition-product-grid.productGridCell
     *
     * @description
     * Responsible for rendering the product grid cell based on the column source and requisitions type.
     * It also keeps track of the validation as well as changes made to the related cells.
     *
     * @example
     * Here we extend our product grid cell with the directive.
     * ```
     * <td ng-repeat="column in visibleColumns | orderBy : 'displayOrder'" product-grid-cell></td>
     * ```
     */
    angular
        .module('requisition-product-grid')
        .directive('productGridCell', productGridCell);

    productGridCell.$inject = [
        '$templateRequest', '$compile', 'requisitionValidator', 'authorizationService',
        'TEMPLATE_COLUMNS', 'COLUMN_TYPES', 'REQUISITION_RIGHTS'
    ];

    function productGridCell($templateRequest, $compile, requisitionValidator, authorizationService,
                            TEMPLATE_COLUMNS, COLUMN_TYPES, REQUISITION_RIGHTS) {

        return {
            restrict: 'A',
            link: link,
            scope: {
                requisition: '=',
                column: '=',
                lineItem: '='
            }
        };

        function link(scope, element) {
            var requisition = scope.requisition,
                column = scope.column,
                lineItem = scope.lineItem;

            scope.lineItem = lineItem;
            scope.column = column;
            scope.validate = validate;
            scope.update = update;
            scope.isReadOnly = isReadOnly();
            scope.canNotSkip = canNotSkip();

            if(!scope.isReadOnly){
                scope.$watch(function(){
                    return lineItem[column.name];
                }, function(newValue, oldValue) {
                    if (newValue !== oldValue) {
                        lineItem.updateDependentFields(column, requisition);
                    }
                });
            }

            scope.$watch(function() {
                if(lineItem.skipped) {
                    return false;
                }
                return lineItem.$errors[column.name];
            }, function(error) {
                if (lineItem.difference[column.name]) {
                    scope.invalidMessage = error ? displayError(error, lineItem.difference[column.name]) : undefined;
                } else {
                    scope.invalidMessage = error ? error : undefined;
                }
            });

            scope.$watch(function() {
                if(lineItem.skipped) {
                    return false;
                }
                return lineItem.difference[column.name];
            }, function(difference) {
                scope.invalidMessage = displayError(lineItem.$errors[column.name], difference);
            });

            scope.$on('openlmisInvalid.update', validate);

            updateCellContents();

            function updateCellContents(){
                var templateUrl = '';
                if(column.name === TEMPLATE_COLUMNS.SKIPPED) {
                    templateUrl = 'requisition-product-grid/product-grid-cell-skip.html';
                } else if(column.name === TEMPLATE_COLUMNS.TOTAL_LOSSES_AND_ADJUSTMENTS) {
                    templateUrl = 'requisition-product-grid/product-grid-cell-total-losses-and-adjustments.html';
                } else if(column.$type === COLUMN_TYPES.NUMERIC && !scope.isReadOnly){
                    templateUrl = 'requisition-product-grid/product-grid-cell-input-numeric.html';
                } else if(!scope.isReadOnly) {
                    templateUrl = 'requisition-product-grid/product-grid-cell-input-text.html';
                } else if(column.$type === COLUMN_TYPES.CURRENCY) {
                    templateUrl = 'requisition-product-grid/product-grid-cell-currency.html';
                } else {
                    templateUrl = 'requisition-product-grid/product-grid-cell-text.html'
                }
                $templateRequest(templateUrl).then(replaceCell);
            }

            function replaceCell(newTemplate) {
                var cellWrapperPath = 'requisition-product-grid/product-grid-cell.html';
                $templateRequest(cellWrapperPath).then(function(template){
                    template = angular.element(template);
                    template.html(newTemplate);

                    var cell = $compile(template)(scope);
                    element.replaceWith(cell);

                    element = cell;

                    if(column.name === TEMPLATE_COLUMNS.PRODUCT_NAME) {
                        element.wrapInner('

');
                    }
                });
            }

            function update() {
                lineItem.updateDependentFields(column, requisition);
                validate();
            }

            function validate() {
                return requisitionValidator.validateLineItem(
                    scope.lineItem,
                    requisition.template.getColumns(!scope.lineItem.$program.fullSupply),
                    requisition
                );
            }

            function isReadOnly() {
                return lineItem.isReadOnly(requisition, column);
            }

            function canNotSkip() {
                return !requisition.$isInitiated() && !requisition.$isRejected() &&
                    !(hasAuthorizeRightForProgram() && requisition.$isSubmitted()) ||
                    !lineItem.canBeSkipped(scope.requisition);
            }

            function hasAuthorizeRightForProgram() {
                return authorizationService.hasRight(REQUISITION_RIGHTS.REQUISITION_AUTHORIZE, {
                    programCode: requisition.program.code
                });
            }

            function displayError(error, difference) {
                return !difference ? error : error.concat('. The difference between the calculated and entered value is ', difference, '.');
            }
        }
    }

})();
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Annict 放送予定表示 V3
// @description Annict の放送予定ページで放送前の表示色を変更します。また、放送予定の表示範囲を抑制します。
// @namespace   imatake.github.io
// @include     https://annict.com/programs
// @author      imatake
// @version     3.00.20170218
// @license     MIT License
// @grant       none
// ==/UserScript==
(function () {
  // 放送前のスタイルを変更する（スタイルシートを埋め込み）
  var css = '/* 放送前のスタイルを変更 */' + `
body.p-programs .c-program-list .is-unbroadcasted .c-program-progress__line {
  border-color: #228B22;
}
body.p-programs .c-program-list .is-unbroadcasted .c-program-progress__point {
  background-color: #228B22;
}
`;
  var head = document.querySelector('head');
  if (head) {
    var style = document.createElement('style');
    style.type = 'text/css';
    style.appendChild(document.createTextNode(css));
    head.appendChild(style);
  }
}) ();
// -----
(function () {
  // 放送予定の表示範囲設定
  document.body.addEventListener('load', showProgramEvent, true);
  //$('div.c-load-more-button') [0].firstChild.addEventListener('click', showProgramEvent, true);
  /*
  //var script = document.createElement('script');
  //script.type = 'text/javascript';
  //script.appendChild(document.createTextNode('showProgramEvent();'));
  //document.body.appendChild(script);
  //console.info(script);
  */
  function getHiddenMsecAndCreateTimeSelector() { // 放送予定の表示範囲セレクター
    var time_select = $('#a-time-selector') [0];
    //console.info(time_select);
    if (!time_select) {
      var selector_parent = $('select.custom-select') [0].parentNode;
      //console.info(selector_parent);
      if (selector_parent) {
        selector_parent.appendChild(document.createTextNode(' '));
        //
        time_select = document.createElement('select');
        time_select.id = 'a-time-selector';
        time_select.className = 'custom-select';
        time_select.addEventListener('change', showProgramEvent, true);
        selector_parent.appendChild(time_select);
        //
        time_select.appendChild(createOptionItem('放送済みのみ', 0, false));
        time_select.appendChild(createOptionItem('12時間以内まで', 12 * 60 * 60 * 1000, true));
        time_select.appendChild(createOptionItem('1日(24時間)まで', 24 * 60 * 60 * 1000, false));
        time_select.appendChild(createOptionItem('2日(48時間)まで', 48 * 60 * 60 * 1000, false));
        time_select.appendChild(createOptionItem('3日(72時間)まで', 72 * 60 * 60 * 1000, false));
        //time_select.appendChild(createOptionItem('1週間', 7 * 24 * 60 * 60 * 1000, false));
        time_select.appendChild(createOptionItem('すべて', - 1, false));
      }
    }
    return time_select.value;
  }
  function createOptionItem(name, value, selected) { // セレクターの選択肢作成
    var opt = document.createElement('option');
    opt.textContent = name;
    opt.setAttribute('value', value);
    if (selected) opt.setAttribute('selected', 'selected');
    return opt;
  }
  function showProgramEvent() { // 放送予定の表示設定
    var hidden_msec = getHiddenMsecAndCreateTimeSelector(); // 12 * 60 * 60 * 1000;
    //console.info(hidden_msec);
    var now_dt = new Date();
    // 非表示とする放送予定の時刻(通算ミリ秒, now_dt.getTime() + 12 * 60 * 60 * 1000 で現在時刻より12時間前)
    var hidden_dt = hidden_msec >= 0 ? new Date(now_dt.getTime() + hidden_msec * 1)  : new Date('2099/12/31 23:59:59.999');
    //console.info(hidden_dt);
    // 放送中と判断する時刻(通算ミリ秒, now_dt.getTime() - 30 * 60 * 1000 で現在時刻より30分前)
    var before_dt = new Date(now_dt.getTime() - 30 * 60 * 1000);
    // ---------- ----------
    $('div.c-card>div.row').each(function () {
      var started_at = $(this).find('span.mr-zp5') [0];
      //console.info(started_at);
      this.style.display = '';
      var started_at_obj = new Date('2017/' + started_at.textContent);
      // 指定範囲外の非表示
      if (started_at_obj > hidden_dt) this.style.display = 'none';
      // 放送中の表示
      if (now_dt > started_at_obj && started_at_obj > before_dt) {
        $(this).find('div.a-line') [0].style = 'border-color: #87CEEB';
        $(this).find('div.a-point') [0].style = 'background-color: #87CEEB';
      }
    });
  }
}) ();
<commit_msg>日付フォーマット変更対応と１年以上前も非表示対象に<commit_after>// ==UserScript==
// @name        Annict 放送予定表示 V3
// @description Annict の放送予定ページで放送前の表示色を変更します。また、放送予定の表示範囲を抑制します。
// @namespace   imatake.github.io
// @include     https://annict.com/programs
// @author      imatake
// @version     3.00.20170323
// @license     MIT License
// @grant       none
// ==/UserScript==
(function () {
  // 放送前のスタイルを変更する（スタイルシートを埋め込み）
  var css = '/* 放送前のスタイルを変更 */' + `
body.p-programs .c-program-list .is-unbroadcasted .c-program-progress__line {
  border-color: #228B22;
}
body.p-programs .c-program-list .is-unbroadcasted .c-program-progress__point {
  background-color: #228B22;
}
`;
  var head = document.querySelector('head');
  if (head) {
    var style = document.createElement('style');
    style.type = 'text/css';
    style.appendChild(document.createTextNode(css));
    head.appendChild(style);
  }
}) ();
// -----
(function () {
  // 放送予定の表示範囲設定
  document.body.addEventListener('load', showProgramEvent, true);
  //$('div.c-load-more-button') [0].firstChild.addEventListener('click', showProgramEvent, true);
  /*
  //var script = document.createElement('script');
  //script.type = 'text/javascript';
  //script.appendChild(document.createTextNode('showProgramEvent();'));
  //document.body.appendChild(script);
  //console.info(script);
  */
  function getHiddenMsecAndCreateTimeSelector() { // 放送予定の表示範囲セレクター
    var time_select = $('#a-time-selector') [0];
    //console.info(time_select);
    if (!time_select) {
      var selector_parent = $('select.custom-select') [0].parentNode;
      //console.info(selector_parent);
      if (selector_parent) {
        selector_parent.appendChild(document.createTextNode(' '));
        //
        time_select = document.createElement('select');
        time_select.id = 'a-time-selector';
        time_select.className = 'custom-select';
        time_select.addEventListener('change', showProgramEvent, true);
        selector_parent.appendChild(time_select);
        //
        time_select.appendChild(createOptionItem('放送済みのみ', 0, false));
        time_select.appendChild(createOptionItem('12時間以内まで', 12 * 60 * 60 * 1000, true));
        time_select.appendChild(createOptionItem('1日(24時間)まで', 24 * 60 * 60 * 1000, false));
        time_select.appendChild(createOptionItem('2日(48時間)まで', 48 * 60 * 60 * 1000, false));
        time_select.appendChild(createOptionItem('3日(72時間)まで', 72 * 60 * 60 * 1000, false));
        //time_select.appendChild(createOptionItem('1週間', 7 * 24 * 60 * 60 * 1000, false));
        time_select.appendChild(createOptionItem('すべて', - 1, false));
      }
    }
    return time_select.value;
  }
  function createOptionItem(name, value, selected) { // セレクターの選択肢作成
    var opt = document.createElement('option');
    opt.textContent = name;
    opt.setAttribute('value', value);
    if (selected) opt.setAttribute('selected', 'selected');
    return opt;
  }
  function showProgramEvent() { // 放送予定の表示設定
    var hidden_msec = getHiddenMsecAndCreateTimeSelector(); // 12 * 60 * 60 * 1000;
    //console.info(hidden_msec);
    var now_dt = new Date();
    // 非表示とする放送予定の時刻(通算ミリ秒, now_dt.getTime() + 12 * 60 * 60 * 1000 で現在時刻より12時間前)
    var hidden_dt = hidden_msec >= 0 ? new Date(now_dt.getTime() + hidden_msec * 1)  : new Date('2099/12/31 23:59:59.999');
    //console.info(hidden_dt);
    var past_dt = hidden_msec >= 0 ? new Date(now_dt.getTime() - 365 * 24 * 60 * 60 * 1000)  : new Date(0);
    // 放送中と判断する時刻(通算ミリ秒, now_dt.getTime() - 30 * 60 * 1000 で現在時刻より30分前)
    var before_dt = new Date(now_dt.getTime() - 30 * 60 * 1000);
    // ---------- ----------
    $('div.c-card>div.row').each(function () {
      var started_at = $(this).find('span.mr-zp5') [0];
      //console.info(started_at);
      this.style.display = '';
      var started_at_obj = new Date(started_at.textContent);
      // 指定範囲外の非表示
      if (started_at_obj > hidden_dt) this.style.display = 'none';
      if (started_at_obj < past_dt) this.style.display = 'none';
      // 放送中の表示
      if (now_dt > started_at_obj && started_at_obj > before_dt) {
        $(this).find('div.a-line') [0].style = 'border-color: #87CEEB';
        $(this).find('div.a-point') [0].style = 'background-color: #87CEEB';
      }
    });
  }
}) ();
<|endoftext|>


<commit_before>/**
 * The examples provided by Facebook are for non-commercial testing and
 * evaluation purposes only.
 *
 * Facebook reserves all rights not expressly granted.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
 * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
'use strict';

var React = require('react-native');
var {
  NavigatorIOS,
  StatusBarIOS,
  StyleSheet,
  Text,
  View
} = React;

var EmptyPage = React.createClass({

  render: function() {
    return (
      <View style={styles.emptyPage}>
        <Text style={styles.emptyPageText}>
          {this.props.text}
        </Text>
      </View>
    );
  },

});

var NavigatorIOSColors = React.createClass({

  statics: {
    title: '<NavigatorIOS> - Custom',
    description: 'iOS navigation with custom nav bar colors',
  },

  render: function() {
    // Set StatusBar with light contents to get better contrast
    StatusBarIOS.setStyle(StatusBarIOS.Style.lightContent);

    return (
      <NavigatorIOS
        style={styles.container}
        initialRoute={{
          component: EmptyPage,
          title: '<NavigatorIOS>',
          rightButtonTitle: 'Done',
          onRightButtonPress: () => {
            StatusBarIOS.setStyle(StatusBarIOS.Style['default']);
            this.props.onExampleExit();
          },
          passProps: {
            text: 'The nav bar has custom colors with tintColor, ' +
              'barTintColor and titleTextColor props.',
          },
        }}
        tintColor="#FFFFFF"
        barTintColor="#183E63"
        titleTextColor="#FFFFFF"
      />
    );
  },

});

var styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  emptyPage: {
    flex: 1,
    paddingTop: 64,
  },
  emptyPageText: {
    margin: 10,
  },
});

NavigatorIOSColors.external = true;

module.exports = NavigatorIOSColors;
<commit_msg>[ReactNative] fix navIOS example<commit_after>/**
 * The examples provided by Facebook are for non-commercial testing and
 * evaluation purposes only.
 *
 * Facebook reserves all rights not expressly granted.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
 * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
'use strict';

var React = require('react-native');
var {
  NavigatorIOS,
  StatusBarIOS,
  StyleSheet,
  Text,
  View
} = React;

var EmptyPage = React.createClass({

  render: function() {
    return (
      <View style={styles.emptyPage}>
        <Text style={styles.emptyPageText}>
          {this.props.text}
        </Text>
      </View>
    );
  },

});

var NavigatorIOSColors = React.createClass({

  statics: {
    title: '<NavigatorIOS> - Custom',
    description: 'iOS navigation with custom nav bar colors',
  },

  render: function() {
    // Set StatusBar with light contents to get better contrast
    StatusBarIOS.setStyle('light-content');

    return (
      <NavigatorIOS
        style={styles.container}
        initialRoute={{
          component: EmptyPage,
          title: '<NavigatorIOS>',
          rightButtonTitle: 'Done',
          onRightButtonPress: () => {
            StatusBarIOS.setStyle('default');
            this.props.onExampleExit();
          },
          passProps: {
            text: 'The nav bar has custom colors with tintColor, ' +
              'barTintColor and titleTextColor props.',
          },
        }}
        tintColor="#FFFFFF"
        barTintColor="#183E63"
        titleTextColor="#FFFFFF"
      />
    );
  },

});

var styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  emptyPage: {
    flex: 1,
    paddingTop: 64,
  },
  emptyPageText: {
    margin: 10,
  },
});

NavigatorIOSColors.external = true;

module.exports = NavigatorIOSColors;
<|endoftext|>


<commit_before>// This is a JavaScript file

var application_key = "YOUR_APPLICATION_KEY";
var client_key = "YOUR_CLIENT_KEY";

var ncmb = new NCMB(application_key, client_key);
var Places = ncmb.DataStore("Places");
var map = new OpenLayers.Map("map");
var mapnik = new OpenLayers.Layer.OSM();
map.addLayer(mapnik);

var projection3857 = new OpenLayers.Projection("EPSG:3857");
var projection4326 = new OpenLayers.Projection("EPSG:4326");
var popup = null;

var ncmbController = {
  run: function() {
    var self = this;
    // ݈ʒu擾܂
    navigator.geolocation.getCurrentPosition(function(location) {
      // ݈ʒu擾ƁAlocationƂϐ̈ʒuIuWFNg܂
      // ʒugāAOpenLayers̈ʒuIuWFNgɕϊ܂
      // ̍ہAEPSG:4326EPSG:3857ɕϊws܂
      var lonLat = new OpenLayers.LonLat(location.coords.longitude, location.coords.latitude)
       .transform(
         projection4326,
         projection3857
      );
      // 쐬ʒun}̒ɐݒ肵܂
      map.setCenter(lonLat, 15);

      // }[J[鏈ł
      ncmbController.findMarkers(location.coords.latitude, location.coords.longitude);

      // {^ǉ鏈ł
      ncmbController.addButton();
    });
  },

  findMarkers: function(latitude, longitude) {
    // mBaaS̈ʒuIuWFNg쐬

  },
  addMarker: function(areaName, latitude, longitude) {
    // }[J[\邽߂̃C[
    var markers = new OpenLayers.Layer.Markers("Markers");
    map.addLayer(markers);

    // }[J[쐬
    var marker = new OpenLayers.Marker(
      new OpenLayers.LonLat(longitude, latitude)
      .transform(
        projection4326,
        projection3857
      )
    );
    // }[J[̃^OƂăGAw
    marker.tag = areaName;

    // }[J[^bvۂɃ|bvAbv\܂
    marker.events.register("touchstart", marker, function(event) {
      // łɕʂȃ|bvAbvJĂ܂
      if (popup) map.removePopup(popup);
      // |bvAbv쐬
      popup = new OpenLayers.Popup("chicken",
           event.object.lonlat,
           new OpenLayers.Size(100,50),
           event.object.tag,
           true);
      // 쐬|bvAbvn}ɒǉ܂
      map.addPopup(popup);
    });

    // 쐬}[J[n}i}[J[C[jɒǉ܂
    markers.addMarker(marker);
  },

  addButton: function() {
    var custom_button = new OpenLayers.Control.Button({
      displayClass : 'olControlCustomButton',
      trigger : ncmbController.createPlace
    })
    var control_panel = new OpenLayers.Control.Panel({});
    control_panel.addControls([custom_button])
    map.addControl(control_panel);
  },

  createPlace: function() {
    // GA̓͂𑣂

  }
};

var event = typeof cordova === 'undefined' ? 'DOMContentLoaded' : 'deviceready';

document.addEventListener(event, ncmbController.run, false);<commit_msg>Fix encoding<commit_after>// This is a JavaScript file

var application_key = "YOUR_APPLICATION_KEY";
var client_key = "YOUR_CLIENT_KEY";

var ncmb = new NCMB(application_key, client_key);
var Places = ncmb.DataStore("Places");
var map = new OpenLayers.Map("map");
var mapnik = new OpenLayers.Layer.OSM();
map.addLayer(mapnik);

var projection3857 = new OpenLayers.Projection("EPSG:3857");
var projection4326 = new OpenLayers.Projection("EPSG:4326");
var popup = null;

var ncmbController = {
  run: function() {
    var self = this;
    // 現在位置を取得します
    navigator.geolocation.getCurrentPosition(function(location) {
      // 現在位置を取得すると、locationという変数の位置情報オブジェクトが入ります
      // 位置情報を使って、OpenLayersの位置情報オブジェクトに変換します
      // その際、EPSG:4326からEPSG:3857に変換する指定を行います
      var lonLat = new OpenLayers.LonLat(location.coords.longitude, location.coords.latitude)
       .transform(
         projection4326,
         projection3857
      );
      // 作成した位置情報を地図の中央に設定します
      map.setCenter(lonLat, 15);

      // マーカーを検索する処理です
      ncmbController.findMarkers(location.coords.latitude, location.coords.longitude);

      // ボタンを追加する処理です
      ncmbController.addButton();
    });
  },

  findMarkers: function(latitude, longitude) {
    // mBaaSの位置情報オブジェクトを作成

  },
  addMarker: function(areaName, latitude, longitude) {
    // マーカーを表示するためのレイヤーを準備
    var markers = new OpenLayers.Layer.Markers("Markers");
    map.addLayer(markers);

    // マーカーを作成
    var marker = new OpenLayers.Marker(
      new OpenLayers.LonLat(longitude, latitude)
      .transform(
        projection4326,
        projection3857
      )
    );
    // マーカーのタグとしてエリア名を指定
    marker.tag = areaName;

    // マーカーをタップした際にポップアップを表示します
    marker.events.register("touchstart", marker, function(event) {
      // すでに別なポップアップが開いていたら消します
      if (popup) map.removePopup(popup);
      // ポップアップを作成
      popup = new OpenLayers.Popup("chicken",
           event.object.lonlat,
           new OpenLayers.Size(100,50),
           event.object.tag,
           true);
      // 作成したポップアップを地図に追加します
      map.addPopup(popup);
    });

    // 作成したマーカーを地図（マーカーレイヤー）に追加します
    markers.addMarker(marker);
  },

  addButton: function() {
    var custom_button = new OpenLayers.Control.Button({
      displayClass : 'olControlCustomButton',
      trigger : ncmbController.createPlace
    })
    var control_panel = new OpenLayers.Control.Panel({});
    control_panel.addControls([custom_button])
    map.addControl(control_panel);
  },

  createPlace: function() {
    // エリア名の入力を促す

  }
};

var event = typeof cordova === 'undefined' ? 'DOMContentLoaded' : 'deviceready';

document.addEventListener(event, ncmbController.run, false);<|endoftext|>


<commit_before>define(["dojo/_base/declare", "ppwcode-util-contracts/_Mixin", "dojo/_base/lang", "dijit/registry", "dojo/dom-class",
        "dijit/_WidgetBase", "../../SemanticObject",
        "dijit/form/ValidationTextBox", "dojox/mvc/Output", "dojox/form/CheckedMultiSelect", "dijit/form/SimpleTextarea",
         "dijit/form/CheckBox",
         "xstyle/css!./_SemanticObjectPane.css"],
    function(declare, _ContractMixin, lang, registry, domClass,
             _WidgetBase, SemanticObject,
             ValidationTextBox, Output, CheckedMultiSelect, SimpleTextarea,
             CheckBox) {

      var presentationModes = [
        // presentationMode and stylePresentationMode for viewing the data. No interaction allowed.
        "VIEW",

        // presentationMode and stylePresentationMode for editing the data. Interaction allowed.
        "EDIT",

        // presentationMode and stylePresentationMode while the SemanticObject is busy. No interaction allowed.
        "BUSY",

        // presentationMode and stylePresentationMode representing unacceptable data (the object is
        // not civilized, or we have other means of knowing the data is unacceptable).
        // Interaction must be allowed to reset the object, or ammeliorate the data/
        // This means this is a sub-state of EDIT.
        "WILD",

        // presentationMode and stylePresentationMode representing an error occured. We cannot proceed. The widget
        // and its object must be destroyed.
        "ERROR"];

      var stylePresentationModes = presentationModes.slice(0); // copy
      // stylePresentationMode for when there is no target
      stylePresentationModes.push("NOTARGET");

      function recursiveChildWidgets(domNode) {
        return registry.findWidgets(domNode).reduce(
          function(acc, w) {
            acc.push(w);
            return acc.concat(recursiveChildWidgets(w.domNode));
          },
          []
        );
      }

      function setStylepresentationMode(sop, stylePresentationMode) {
        var domNode = sop.get("domNode");
        var innerWidgets = recursiveChildWidgets(domNode);
        var widgetState = null;
        //noinspection FallthroughInSwitchStatementJS
        switch (stylePresentationMode) {
          case _SemanticObjectPane.prototype.NOTARGET:
            widgetState = { readOnly:false, disabled:true };
            break;
          case _SemanticObjectPane.prototype.EDIT:
          case _SemanticObjectPane.prototype.WILD:
            widgetState = { readOnly:false, disabled:false };
            break;
          case _SemanticObjectPane.prototype.BUSY:
          case _SemanticObjectPane.prototype.ERROR:
            widgetState = { readOnly:false, disabled:true };
            break;
          default: // VIEW
            widgetState = { readOnly:true, disabled:false };
        }
        innerWidgets.forEach(function (w) {
          if (w.isInstanceOf(ValidationTextBox) || w.isInstanceOf(Output)
              || w.isInstanceOf(CheckedMultiSelect) || w.isInstanceOf(SimpleTextarea) || w.isInstanceOf(CheckBox)) {
            w.set("readOnly", widgetState.readOnly);
            w.set("disabled", widgetState.disabled);
          }
        });
        if (stylePresentationMode == _SemanticObjectPane.prototype.VIEW ||
            stylePresentationMode == _SemanticObjectPane.prototype.EDIT) {
          domClass.replace(domNode,
            "SemanticObjectPane_enabled",
            "SemanticObjectPane_busy SemanticObjectPane_wild SemanticObjectPane_disabled SemanticObjectPane_error SemanticObjectPane_notarget");
        }
        else if (stylePresentationMode == _SemanticObjectPane.prototype.BUSY) {
          domClass.replace(domNode,
            "SemanticObjectPane_busy",
            "SemanticObjectPane_enabled SemanticObjectPane_wild SemanticObjectPane_disabled SemanticObjectPane_error SemanticObjectPane_notarget");
        }
        else if (stylePresentationMode == _SemanticObjectPane.prototype.WILD) {
          domClass.replace(domNode,
            "SemanticObjectPane_wild",
            "SemanticObjectPane_enabled SemanticObjectPane_busy SemanticObjectPane_disabled SemanticObjectPane_error SemanticObjectPane_notarget");
        }
        else if (stylePresentationMode == _SemanticObjectPane.prototype.ERROR) {
          domClass.replace(domNode,
            "SemanticObjectPane_error",
            "SemanticObjectPane_enabled SemanticObjectPane_busy SemanticObjectPane_wild SemanticObjectPane_disabled SemanticObjectPane_notarget");
        }
        else { // NOTARGET
          domClass.replace(domNode,
            " SemanticObjectPane_notarget",
            "SemanticObjectPane_enabled SemanticObjectPane_busy SemanticObjectPane_wild SemanticObjectPane_error SemanticObjectPane_disabled");
        }
      }

      var _SemanticObjectPane = declare([_WidgetBase, _ContractMixin], {
        // summary:
        //   Widget that represents a SemanticObject in detail, and that gives the opportunity
        //   to the user the view the details, edit the details, and create a new object.
        //   Abstract class to be extended for particular subtypes of SemanticObject.
        // description:
        //   What is common to all subtypes is that the widget:
        //   - has a `target` that is a SemanticObject; getTargetType() returns the top-most supported
        //     Constructor of supported targets. All targets must be instances of this type.
        //   - has an `presentationMode` (view, edit, busy, wild, error)
        //   - has a `stylePresentationMode`
        //
        //   The representation should reflect the presentationMode in a clear visual way to the user.
        //   This is the `stylePresentationModel`. It is the `presentationMode` if `target !== null`,
        //   and `NOTARGET` otherwise.
        //   Even when the information shown is completely read-only, widgets should extend
        //   this class, to change the representation of the displayed information consistently
        //   with other information in different presentationModes, e.g., in "busy" mode.
        //   The available presentation modes are defined in _SemanticObjectPane.prototype.presentationModes.
        //
        //   Instances can be wrapped around zero or more _SemanticObjectPanes, recursively (no loops!).
        //   We take care of propagating `presentationMode`. Subclasses need to override
        //   getWrappedDetails to return the wrapped details. All instances need to be instances of this class.
        //   The default implementation returns an empty array.
        //   The target of a wrapped detail might be the same target as our target, but it might be
        //   another related object, of a different type. set("target", ...) will call _propagateTarget, which is
        //   chained, so that subclasses can add propagation of setting the target. Our implementation only sets our
        //   target.
        //
        //   This class is concerned with presentation. Other classes might add presentation
        //   and functionality to support interaction with the user (save and cancel buttons, etc. -
        //   see e.g. ppwcode persistence).

        _c_invar: [
          function() {return this.get("presentationMode");},
          function() {return this.presentationModes.indexOf(this.get("presentationMode")) >= 0;},
          function() {return this.getTargetType() != null;},
          // TODO getTargetType is a subtype of SemanticObject
          function() {return this.get("target") == null ||
                        (this.get("target").isInstanceOf && this.get("target").isInstanceOf(this.getTargetType()));},
          function() {return this.get("stylePresentationMode");},
          function() {return this.stylePresentationModes.indexOf(this.get("stylePresentationMode")) >= 0;},
          function() {return (this.get("stylePresentationMode") === this.NOTARGET) === (this.get("target") === null);},
          function() {return this.get("target") !== null ? this.get("stylePresentationMode") === this.get("presentationMode") : true;},
          function() {return this._wrappedDetails() != null;},
          function() {return lang.isArray(this._wrappedDetails());},
          {
            objectSelector: function() {
              return this._wrappedDetails();
            },
            invars: [
              function(wd) {
                return wd && wd != null &&
                  wd.isInstanceOf && wd.isInstanceOf(_SemanticObjectPane);
              },
              // wrapped details might contain other objects of other types as target,
              // or null, but the presentationMode needs to be in sync. The stylePresentationMode
              // can differ.
              function(wd) {
                return wd.get("presentationMode") === this.get("presentationMode");
              }
            ]
          }
        ],

        "-chains-": {
          _propagateTarget: "after",
          _propagateOpener: "after",
          _localPresentationModeChange: "after"
        },

        // target: SemanticObject
        //    SemanticObject that is represented.
        //    Access in declarative template with  ... data-dojo-props="value: at('rel:', BINDING_PROPERTY_NAME)
        target: null,

        // presentationMode: String
        //    The presentation mode is either view, edit, busy, wild, or error
        presentationMode: stylePresentationModes[0], // default value

        // opener: Function
        //   Function that attempts to open a new "window" or "pane" for another PersistentObject
        //   (which is mostly linked to the current one)
        opener: null,

        widgetSize: 0,

        postCreate: function() {
          this.inherited(arguments);
          domClass.add(this.get("domNode"), "SemanticObjectPane");
        },

        // stylePresentationMode: String
        //    The stylePresentationMode is the same as the presentationMode, except when there is no target.
        //    Then it is NOTARGET.
        _getStylePresentationModeAttr: function() {
          return this.target ? this.get("presentationMode") : this.NOTARGET;
        },

        isInEditMode: function() {
          // summary:
          //    This is in a `stylePresentationMode` that allows the user to change the values, if there is a target and
          //    it is editable.
          return this.target &&
            (this.presentationMode === this.EDIT || this.presentationMode === this.BUSY || this.presentationMode === this.WILD);
        },

        _wrappedDetails: function() {
          // summary:
          //   Array containing the wrapped details, a subclass wants the presentationMode propagated to.
          // tags:
          //   protected

          return []; // return Array
        },

        getTargetType: function() {
          // summary:
          //    The supported type of SemanticObjects. All targets must be an instance of this type.
          // description:
          //    Subclasses should overwrite to return the correct type.

          return SemanticObject;
        },

        _propagateTarget: function(/*SemanticObject*/ so) {
          // summary:
          //   Propagate the target as appropriate to wrapped details.
          //   Chained. Subtypes could add propagation to wrapped details.
          // tags:
          //   protected
          // description:
          //   Does nothing in _SemanticObjectPane.

          this._c_NOP(so);
        },

        _setTargetAttr: function(so) {
          // summary:
          //    Sets the target of this instance. Chained. Subtypes could add propagation to wrapped details.
          this._c_pre(function() {return so == null || (so.isInstanceOf && so.isInstanceOf(this.getTargetType()));});

          if (so !== this.get("target")) {
            this._set("target", so);
          }
          this._propagateTarget(so);
          this.set("presentationMode", so !== null && so.persistenceId === null ? this.EDIT : this.VIEW);
        },

        _propagateOpener: function(/*Function*/ opener) {
          // summary:
          //   Propagate the opener function as appropriate to wrapped details.
          //   Chained. Subtypes could add propagation to wrapped details.
          // tags:
          //   protected
          // description:
          //   Does nothing in _SemanticObjectPane.

          this._c_NOP(opener);
        },

        _setOpenerAttr: function(opener) {
          if (opener !== this.get("opener")) {
            this._set("opener", opener);
          }
          this._propagateOpener(opener);
        },

        _setPresentationModeAttr: function(value) {
          // summary:
          //    Set the presentationMode, and propagate to the wrapped details.
          //    Also makes all innerWidgets read-only in presentationMode VIEW,
          //    disabled in presentationMode BUSY, and not-read-only and enabled
          //    in the other presentationModes.

          // Called during create by _WidgetBase with default value automatically
          this._c_pre(function() {return value != null});
          this._c_pre(function() {return _SemanticObjectPane.prototype.presentationModes.indexOf(value) >= 0});

          this._set("presentationMode", value);
          this._wrappedDetails().forEach(function(wd) {
            wd.set("presentationMode", value);
          });
          var myDomNode = this.get("domNode");
          var stylePresentationMode = this.get("stylePresentationMode");
          setStylepresentationMode(this, stylePresentationMode);
          this._localPresentationModeChange(value);
        },

        _localPresentationModeChange: function(/*String*/ presentationMode) {
          // summary:
          //   Make changes to representation to represent the
          //   new edit mode. Chained. Overwrite when needed.
          // tags:
          //   protected
          // description:
          //   Does nothing in _SemanticObjectPane

          this._c_NOP(presentationMode);
        }

      });

      // All supported presentationModes
      _SemanticObjectPane.prototype.presentationModes = presentationModes;
      // All supported stylePresentationModes
      _SemanticObjectPane.prototype.stylePresentationModes = stylePresentationModes;
      stylePresentationModes.forEach(function(em) {
        _SemanticObjectPane.prototype[em] = em;
      });

      return _SemanticObjectPane; // return _SemanticObjectPane

    });
<commit_msg>disable the select too<commit_after>define(["dojo/_base/declare", "ppwcode-util-contracts/_Mixin", "dojo/_base/lang", "dijit/registry", "dojo/dom-class",
        "dijit/_WidgetBase", "../../SemanticObject",
        "dijit/form/ValidationTextBox", "dojox/mvc/Output", "dojox/form/CheckedMultiSelect", "dijit/form/SimpleTextarea",
         "dijit/form/CheckBox", "dijit/form/Select",
         "xstyle/css!./_SemanticObjectPane.css"],
    function(declare, _ContractMixin, lang, registry, domClass,
             _WidgetBase, SemanticObject,
             ValidationTextBox, Output, CheckedMultiSelect, SimpleTextarea,
             CheckBox, Select) {

      var presentationModes = [
        // presentationMode and stylePresentationMode for viewing the data. No interaction allowed.
        "VIEW",

        // presentationMode and stylePresentationMode for editing the data. Interaction allowed.
        "EDIT",

        // presentationMode and stylePresentationMode while the SemanticObject is busy. No interaction allowed.
        "BUSY",

        // presentationMode and stylePresentationMode representing unacceptable data (the object is
        // not civilized, or we have other means of knowing the data is unacceptable).
        // Interaction must be allowed to reset the object, or ammeliorate the data/
        // This means this is a sub-state of EDIT.
        "WILD",

        // presentationMode and stylePresentationMode representing an error occured. We cannot proceed. The widget
        // and its object must be destroyed.
        "ERROR"];

      var stylePresentationModes = presentationModes.slice(0); // copy
      // stylePresentationMode for when there is no target
      stylePresentationModes.push("NOTARGET");

      function recursiveChildWidgets(domNode) {
        return registry.findWidgets(domNode).reduce(
          function(acc, w) {
            acc.push(w);
            return acc.concat(recursiveChildWidgets(w.domNode));
          },
          []
        );
      }

      function setStylepresentationMode(sop, stylePresentationMode) {
        var domNode = sop.get("domNode");
        var innerWidgets = recursiveChildWidgets(domNode);
        var widgetState = null;
        //noinspection FallthroughInSwitchStatementJS
        switch (stylePresentationMode) {
          case _SemanticObjectPane.prototype.NOTARGET:
            widgetState = { readOnly:false, disabled:true };
            break;
          case _SemanticObjectPane.prototype.EDIT:
          case _SemanticObjectPane.prototype.WILD:
            widgetState = { readOnly:false, disabled:false };
            break;
          case _SemanticObjectPane.prototype.BUSY:
          case _SemanticObjectPane.prototype.ERROR:
            widgetState = { readOnly:false, disabled:true };
            break;
          default: // VIEW
            widgetState = { readOnly:true, disabled:false };
        }
        innerWidgets.forEach(function (w) {
          if (w.isInstanceOf(ValidationTextBox) || w.isInstanceOf(Output)
              || w.isInstanceOf(CheckedMultiSelect) || w.isInstanceOf(SimpleTextarea) || w.isInstanceOf(CheckBox)
              || w.isInstanceOf(Select)) {
            w.set("readOnly", widgetState.readOnly);
            w.set("disabled", widgetState.disabled);
          }
        });
        if (stylePresentationMode == _SemanticObjectPane.prototype.VIEW ||
            stylePresentationMode == _SemanticObjectPane.prototype.EDIT) {
          domClass.replace(domNode,
            "SemanticObjectPane_enabled",
            "SemanticObjectPane_busy SemanticObjectPane_wild SemanticObjectPane_disabled SemanticObjectPane_error SemanticObjectPane_notarget");
        }
        else if (stylePresentationMode == _SemanticObjectPane.prototype.BUSY) {
          domClass.replace(domNode,
            "SemanticObjectPane_busy",
            "SemanticObjectPane_enabled SemanticObjectPane_wild SemanticObjectPane_disabled SemanticObjectPane_error SemanticObjectPane_notarget");
        }
        else if (stylePresentationMode == _SemanticObjectPane.prototype.WILD) {
          domClass.replace(domNode,
            "SemanticObjectPane_wild",
            "SemanticObjectPane_enabled SemanticObjectPane_busy SemanticObjectPane_disabled SemanticObjectPane_error SemanticObjectPane_notarget");
        }
        else if (stylePresentationMode == _SemanticObjectPane.prototype.ERROR) {
          domClass.replace(domNode,
            "SemanticObjectPane_error",
            "SemanticObjectPane_enabled SemanticObjectPane_busy SemanticObjectPane_wild SemanticObjectPane_disabled SemanticObjectPane_notarget");
        }
        else { // NOTARGET
          domClass.replace(domNode,
            " SemanticObjectPane_notarget",
            "SemanticObjectPane_enabled SemanticObjectPane_busy SemanticObjectPane_wild SemanticObjectPane_error SemanticObjectPane_disabled");
        }
      }

      var _SemanticObjectPane = declare([_WidgetBase, _ContractMixin], {
        // summary:
        //   Widget that represents a SemanticObject in detail, and that gives the opportunity
        //   to the user the view the details, edit the details, and create a new object.
        //   Abstract class to be extended for particular subtypes of SemanticObject.
        // description:
        //   What is common to all subtypes is that the widget:
        //   - has a `target` that is a SemanticObject; getTargetType() returns the top-most supported
        //     Constructor of supported targets. All targets must be instances of this type.
        //   - has an `presentationMode` (view, edit, busy, wild, error)
        //   - has a `stylePresentationMode`
        //
        //   The representation should reflect the presentationMode in a clear visual way to the user.
        //   This is the `stylePresentationModel`. It is the `presentationMode` if `target !== null`,
        //   and `NOTARGET` otherwise.
        //   Even when the information shown is completely read-only, widgets should extend
        //   this class, to change the representation of the displayed information consistently
        //   with other information in different presentationModes, e.g., in "busy" mode.
        //   The available presentation modes are defined in _SemanticObjectPane.prototype.presentationModes.
        //
        //   Instances can be wrapped around zero or more _SemanticObjectPanes, recursively (no loops!).
        //   We take care of propagating `presentationMode`. Subclasses need to override
        //   getWrappedDetails to return the wrapped details. All instances need to be instances of this class.
        //   The default implementation returns an empty array.
        //   The target of a wrapped detail might be the same target as our target, but it might be
        //   another related object, of a different type. set("target", ...) will call _propagateTarget, which is
        //   chained, so that subclasses can add propagation of setting the target. Our implementation only sets our
        //   target.
        //
        //   This class is concerned with presentation. Other classes might add presentation
        //   and functionality to support interaction with the user (save and cancel buttons, etc. -
        //   see e.g. ppwcode persistence).

        _c_invar: [
          function() {return this.get("presentationMode");},
          function() {return this.presentationModes.indexOf(this.get("presentationMode")) >= 0;},
          function() {return this.getTargetType() != null;},
          // TODO getTargetType is a subtype of SemanticObject
          function() {return this.get("target") == null ||
                        (this.get("target").isInstanceOf && this.get("target").isInstanceOf(this.getTargetType()));},
          function() {return this.get("stylePresentationMode");},
          function() {return this.stylePresentationModes.indexOf(this.get("stylePresentationMode")) >= 0;},
          function() {return (this.get("stylePresentationMode") === this.NOTARGET) === (this.get("target") === null);},
          function() {return this.get("target") !== null ? this.get("stylePresentationMode") === this.get("presentationMode") : true;},
          function() {return this._wrappedDetails() != null;},
          function() {return lang.isArray(this._wrappedDetails());},
          {
            objectSelector: function() {
              return this._wrappedDetails();
            },
            invars: [
              function(wd) {
                return wd && wd != null &&
                  wd.isInstanceOf && wd.isInstanceOf(_SemanticObjectPane);
              },
              // wrapped details might contain other objects of other types as target,
              // or null, but the presentationMode needs to be in sync. The stylePresentationMode
              // can differ.
              function(wd) {
                return wd.get("presentationMode") === this.get("presentationMode");
              }
            ]
          }
        ],

        "-chains-": {
          _propagateTarget: "after",
          _propagateOpener: "after",
          _localPresentationModeChange: "after"
        },

        // target: SemanticObject
        //    SemanticObject that is represented.
        //    Access in declarative template with  ... data-dojo-props="value: at('rel:', BINDING_PROPERTY_NAME)
        target: null,

        // presentationMode: String
        //    The presentation mode is either view, edit, busy, wild, or error
        presentationMode: stylePresentationModes[0], // default value

        // opener: Function
        //   Function that attempts to open a new "window" or "pane" for another PersistentObject
        //   (which is mostly linked to the current one)
        opener: null,

        postCreate: function() {
          this.inherited(arguments);
          domClass.add(this.get("domNode"), "SemanticObjectPane");
        },

        // stylePresentationMode: String
        //    The stylePresentationMode is the same as the presentationMode, except when there is no target.
        //    Then it is NOTARGET.
        _getStylePresentationModeAttr: function() {
          return this.target ? this.get("presentationMode") : this.NOTARGET;
        },

        isInEditMode: function() {
          // summary:
          //    This is in a `stylePresentationMode` that allows the user to change the values, if there is a target and
          //    it is editable.
          return this.target &&
            (this.presentationMode === this.EDIT || this.presentationMode === this.BUSY || this.presentationMode === this.WILD);
        },

        _wrappedDetails: function() {
          // summary:
          //   Array containing the wrapped details, a subclass wants the presentationMode propagated to.
          // tags:
          //   protected

          return []; // return Array
        },

        getTargetType: function() {
          // summary:
          //    The supported type of SemanticObjects. All targets must be an instance of this type.
          // description:
          //    Subclasses should overwrite to return the correct type.

          return SemanticObject;
        },

        _propagateTarget: function(/*SemanticObject*/ so) {
          // summary:
          //   Propagate the target as appropriate to wrapped details.
          //   Chained. Subtypes could add propagation to wrapped details.
          // tags:
          //   protected
          // description:
          //   Does nothing in _SemanticObjectPane.

          this._c_NOP(so);
        },

        _setTargetAttr: function(so) {
          // summary:
          //    Sets the target of this instance. Chained. Subtypes could add propagation to wrapped details.
          this._c_pre(function() {return so == null || (so.isInstanceOf && so.isInstanceOf(this.getTargetType()));});

          if (so !== this.get("target")) {
            this._set("target", so);
          }
          this._propagateTarget(so);
          this.set("presentationMode", so !== null && so.persistenceId === null ? this.EDIT : this.VIEW);
        },

        _propagateOpener: function(/*Function*/ opener) {
          // summary:
          //   Propagate the opener function as appropriate to wrapped details.
          //   Chained. Subtypes could add propagation to wrapped details.
          // tags:
          //   protected
          // description:
          //   Does nothing in _SemanticObjectPane.

          this._c_NOP(opener);
        },

        _setOpenerAttr: function(opener) {
          if (opener !== this.get("opener")) {
            this._set("opener", opener);
          }
          this._propagateOpener(opener);
        },

        _setPresentationModeAttr: function(value) {
          // summary:
          //    Set the presentationMode, and propagate to the wrapped details.
          //    Also makes all innerWidgets read-only in presentationMode VIEW,
          //    disabled in presentationMode BUSY, and not-read-only and enabled
          //    in the other presentationModes.

          // Called during create by _WidgetBase with default value automatically
          this._c_pre(function() {return value != null});
          this._c_pre(function() {return _SemanticObjectPane.prototype.presentationModes.indexOf(value) >= 0});

          this._set("presentationMode", value);
          this._wrappedDetails().forEach(function(wd) {
            wd.set("presentationMode", value);
          });
          var myDomNode = this.get("domNode");
          var stylePresentationMode = this.get("stylePresentationMode");
          setStylepresentationMode(this, stylePresentationMode);
          this._localPresentationModeChange(value);
        },

        _localPresentationModeChange: function(/*String*/ presentationMode) {
          // summary:
          //   Make changes to representation to represent the
          //   new edit mode. Chained. Overwrite when needed.
          // tags:
          //   protected
          // description:
          //   Does nothing in _SemanticObjectPane

          this._c_NOP(presentationMode);
        }

      });

      // All supported presentationModes
      _SemanticObjectPane.prototype.presentationModes = presentationModes;
      // All supported stylePresentationModes
      _SemanticObjectPane.prototype.stylePresentationModes = stylePresentationModes;
      stylePresentationModes.forEach(function(em) {
        _SemanticObjectPane.prototype[em] = em;
      });

      return _SemanticObjectPane; // return _SemanticObjectPane

    });
<|endoftext|>


<commit_before>function Board(grid) {
	var Events = typeof window !== 'undefined' ? window.Events : {emit:function(){}};

	this.score = 0
	this.isGameOver = false

	this.resetGrid = function() {
		this.grid = _.map(Array(4), function() {
			return _.map(Array(4), function() {
				return 0
			})
		})
	}

	if(grid)
		this.grid = grid
	else
		this.resetGrid()

	this.newGame = function() {
		this.resetGrid()
		this.spawn()
		this.spawn()
		this.score = 0
		this.isGameOver = false
	}

	this.lastVisited = []

	this.hasAnotherMove = function() {
		for(var r=0;r<this.grid.length;r++) {
			for(var c=0;c<this.grid[0].length;c++) {
				if (!this.grid[r][c]) {
					return true
				}
				var dirs = [
					[-1, 0],
					[1, 0],
					[0, -1],
					[0, 1]
				]
				var self = this
				if (_.some(dirs, function(dir) {
					return self.grid[r+dir[0]] &&
						self.grid[r][c] === self.grid[r+dir[0]][c+dir[1]]
				})) return true
			}
		}
		return false
	}

	this.sampleWithout = function(arr, exclude) {
		var result = []

		for(var i=0;i<arr.length;i++) {
			var cell = arr[i];
			for(var j=0;j<exclude.length;j++) {
				var exCell = exclude[j];
				if (_.isEqual(cell, exCell)) break
			}

			if (j === exclude.length) {
				result.push(cell)
			}
		}

		return _.sample(result)
	}

	this.spawn = function() {

		if (!this.hasAnotherMove()) {
			return this.endGame()
		}

		// get random empty grid cell that didn't have any blocks last time
		var emptyCells = []
		for(var r=0;r<this.grid.length;r++) {
			for(var c=0;c<this.grid[0].length;c++) {
				if(!this.grid[r][c]) {
					emptyCells.push([r,c])
				}
			}
		}

		if (!emptyCells.length) {
			return;
		}

		var target = this.sampleWithout(emptyCells, this.lastVisited)

		if (!target) {
			target = _.sample(emptyCells)
		}

		var row = target[0]
		var col = target[1]

		// fill with either the first or second colors. 90% first color, 10% second
		var color = Math.random() < 0.1 ? 2 : 1
		this.grid[row][col] = color

		// add element to DOM
		Events.emit('spawn', {row: row, col: col, color: color})

		return [row, col] // used for tutorial
	}

	this.endGame = function() {
		// show end game screen
		this.isGameOver = true
		Events.emit('gameOver')
	}

	this.move = function(dir) {
		if (this.isGameOver) return;
		if (GAME.tutorial)
			GAME.tutorial.nextStep()
		this._move(dir)
		this.spawn()
	}

	this._move = function(dir) {
		var keymap = {
			up: [-1, 0],
			down: [1, 0],
			left: [0, -1],
			right: [0, 1]
		}

		var grid = this.grid
		var diff = keymap[dir]
		var rows = _.range(0, 4)
		var cols = _.range(0, 4)

		if(dir === 'down') {
			rows.reverse()
		}
		if(dir === 'right') {
			cols.reverse()
		}

		this.lastVisited = []

		// Hack, is something has been combined, it has 0.1 added to it temporarily
		for(var r=0;r<rows.length;r++) {
			for(var c=0;c<cols.length;c++) {
				var row = rows[r]
				var col = cols[c]

				var fromRow = row
				var fromCol = col

				if (grid[row] && grid[row][col]) {
					var combine = grid[row][col]

					while (grid[row + diff[0]] && (grid[row + diff[0]][col + diff[1]] === 0 ||
								grid[row][col] === grid[row + diff[0]][col + diff[1]])) {

						combine = grid[row][col]
						if (grid[row][col] === grid[row + diff[0]][col + diff[1]]) {
							combine = grid[row][col] + 1 + Math.random() / 2
							this.score += Math.pow(grid[row][col] * 2, 2)
						}

						this.lastVisited.push([row, col])
						grid[row][col] = 0
						row += diff[0]
						col += diff[1]
						grid[row][col] = combine

					}

					Events.emit('score', this.score);
					Events.emit('move', {fromRow: fromRow, toRow: row, fromCol: fromCol, toCol: col});
					Events.emit('setColor', {row:row, col: col, color: Math.floor(combine)});
				}
			}
		}

		this.grid = _.map(grid, function(row) {
			return _.map(row, Math.floor.bind(Math))
		})
	}
}

if(typeof module !== 'undefined') {
	module.exports = Board
}<commit_msg>dont spawn if cant move<commit_after>function Board(grid) {
	var Events = typeof window !== 'undefined' ? window.Events : {emit:function(){}};

	this.score = 0
	this.isGameOver = false

	this.resetGrid = function() {
		this.grid = _.map(Array(4), function() {
			return _.map(Array(4), function() {
				return 0
			})
		})
	}

	if(grid)
		this.grid = grid
	else
		this.resetGrid()

	this.newGame = function() {
		this.resetGrid()
		this.spawn()
		this.spawn()
		this.score = 0
		this.isGameOver = false
	}

	this.lastVisited = []

	this.hasAnotherMove = function() {
		for(var r=0;r<this.grid.length;r++) {
			for(var c=0;c<this.grid[0].length;c++) {
				if (!this.grid[r][c]) {
					return true
				}
				var dirs = [
					[-1, 0],
					[1, 0],
					[0, -1],
					[0, 1]
				]
				var self = this
				if (_.some(dirs, function(dir) {
					return self.grid[r+dir[0]] &&
						self.grid[r][c] === self.grid[r+dir[0]][c+dir[1]]
				})) return true
			}
		}
		return false
	}

	this.sampleWithout = function(arr, exclude) {
		var result = []

		for(var i=0;i<arr.length;i++) {
			var cell = arr[i];
			for(var j=0;j<exclude.length;j++) {
				var exCell = exclude[j];
				if (_.isEqual(cell, exCell)) break
			}

			if (j === exclude.length) {
				result.push(cell)
			}
		}

		return _.sample(result)
	}

	this.spawn = function() {

		if (!this.hasAnotherMove()) {
			return this.endGame()
		}

		// get random empty grid cell that didn't have any blocks last time
		var emptyCells = []
		for(var r=0;r<this.grid.length;r++) {
			for(var c=0;c<this.grid[0].length;c++) {
				if(!this.grid[r][c]) {
					emptyCells.push([r,c])
				}
			}
		}

		if (!emptyCells.length) {
			return;
		}

		var target = this.sampleWithout(emptyCells, this.lastVisited)

		if (!target) {
			target = _.sample(emptyCells)
		}

		var row = target[0]
		var col = target[1]

		// fill with either the first or second colors. 90% first color, 10% second
		var color = Math.random() < 0.1 ? 2 : 1
		this.lastVisited.push([row, col])
		this.grid[row][col] = color

		// add element to DOM
		Events.emit('spawn', {row: row, col: col, color: color})

		return [row, col] // used for tutorial
	}

	this.endGame = function() {
		// show end game screen
		this.isGameOver = true
		Events.emit('gameOver')
	}

	this.move = function(dir) {
		if (this.isGameOver) return;
		if (GAME.tutorial)
			GAME.tutorial.nextStep()
		this._move(dir)
		if (this.lastVisited.length !== 0) {
			this.spawn()
		}
	}

	this._move = function(dir) {
		var keymap = {
			up: [-1, 0],
			down: [1, 0],
			left: [0, -1],
			right: [0, 1]
		}

		var grid = this.grid
		var diff = keymap[dir]
		var rows = _.range(0, 4)
		var cols = _.range(0, 4)

		if(dir === 'down') {
			rows.reverse()
		}
		if(dir === 'right') {
			cols.reverse()
		}

		this.lastVisited = []

		// Hack, is something has been combined, it has 0.1 added to it temporarily
		for(var r=0;r<rows.length;r++) {
			for(var c=0;c<cols.length;c++) {
				var row = rows[r]
				var col = cols[c]

				var fromRow = row
				var fromCol = col

				if (grid[row] && grid[row][col]) {
					var combine = grid[row][col]

					while (grid[row + diff[0]] && (grid[row + diff[0]][col + diff[1]] === 0 ||
								grid[row][col] === grid[row + diff[0]][col + diff[1]])) {

						combine = grid[row][col]
						if (grid[row][col] === grid[row + diff[0]][col + diff[1]]) {
							combine = grid[row][col] + 1 + Math.random() / 2
							this.score += Math.pow(grid[row][col] * 2, 2)
						}

						this.lastVisited.push([row, col])
						grid[row][col] = 0
						row += diff[0]
						col += diff[1]
						grid[row][col] = combine

					}

					Events.emit('score', this.score);
					Events.emit('move', {fromRow: fromRow, toRow: row, fromCol: fromCol, toCol: col});
					Events.emit('setColor', {row:row, col: col, color: Math.floor(combine)});
				}
			}
		}

		this.grid = _.map(grid, function(row) {
			return _.map(row, Math.floor.bind(Math))
		})
	}
}

if(typeof module !== 'undefined') {
	module.exports = Board
}<|endoftext|>