cannot read property of -1 in ReactJS multidimensional grid - reactjs

I'm at my witsend with this problem. I'm building Conway's game of life and right now I'm trying to find neighbors(cells that are on top, to the bottom, and on the sides of the target cell). When I console log my function (basically to find the neighbors), I get values for the bottom and middle rows and all columns but for the top row I get "Cannot read property '-1' of undefined". Sometimes it will return "cannot read property of 'r' of defined" as well. I'm confused because I get return values when i console log both row (r) and columns (c). I'm also curious as to why I don't see this same error message when dealing with my columns. The messages on developer tools are very cryptic. I've spent quite a bit of time trying to figure this out but I'm truly stuck. Any help would be much appreciated.
I tried to do a JSbins: https://jsbin.com/bapazem/edit?html,css,js,output but its not liking the construction of my react components. You can see these areas clearly on my codepen: http://codepen.io/tbraden30/pen/AXgVNQ. Just click the play button and dev tools
Thanks in advance for your help
function randomGrid() {
var gridHeight = 20,
gridWidth = 20,
gridContainer = [];
for (var r = 0; r < gridWidth; r++) {
var row = [];
for (var c = 0; c < gridHeight; c++) {
row.push({
alive: Math.random() < 0.2,
neighbors: 0,
r: r,
c: c,
coords: [r, c]
});
}
gridContainer.push(row)
}
return gridContainer;
}
function isWithinBounds(cell) {
if (cell.r >= 0 && cell.r < 20 && cell.c >= 0 && cell.c < 20) {
return true
}
}
var grid = randomGrid();
var Board = React.createClass({
getInitialState: function() {
return {
cellLocation: randomGrid()
};
},
updateCells: function(r, c) {
var grid = this.state.cellLocation;
for (var r = 0; r < 20; r++) {
for (var c = 0; c < 20; c++) {
var cell = grid[r][c];
//console.log('cell', cell)
cell.neighbors = 0;
var isAlive = cell.alive;
var bottomLeft = grid[r + 1][c - 1],
bottomMiddle = grid[r + 1][c],
bottomRight = grid[r + 1][c + 1],
middleLeft = grid[r][c - 1],
middleRight = grid[r][c + 1],
topLeft = grid[r - 1][c - 1], **//problematic**
topMiddle = grid[r - 1][c], **//problematic**
topRight = grid[r - 1][c + 1];**//problematic**
/* if (isWithinBounds(bottomLeft) && isAlive) {
cell.neighbors++;
}
if (isWithinBounds(bottomMiddle) && isAlive) {
cell.neighbors++;
}
if (isWithinBounds(bottomRight) && isAlive) {
cell.neighbors++;
}
if (isWithinBounds(middleLeft) && isAlive) {
cell.neighbors++;
}
if (isWithinBounds(middleRight) && isAlive) {
cell.neighbors++;
}
if (isWithinBounds(topLeft) && isAlive) {
cell.neighbors++;
}
if (isWithinBounds(topMiddle) && isAlive) {
cell.neighbors++;
}
if (isWithinBounds(topRight) && isAlive) {
cell.neighbors++;
}*/
console.log('TR', topRight)
}
}
return cell;
},
render: function() {
return (
<div className='container grid_body'>
<CellMatrix grid={this.state.cellLocation}/>
<button type='button' onClick={this.updateCells} className='play'>Play</button>
</div>
)
}
}, this)
var CellMatrix = React.createClass({ //cells conglomerated into a grid
render: function() {
var fillCells = [],
grid = this.props.grid,
grid = grid.reduce((a, b) => a.concat(b)) //flattens the array.
grid.map(function(cellObject, index) {
fillCells.push(<Cell alive={cellObject.alive} r={cellObject.r} c={cellObject.c} coords={cellObject.coords} index={index} neighbors={this.updateCells}/>)
// console.log('fillcells', fillCells)
}, this)
return (
<div>
{fillCells}
</div>
)
}
})
var Cell = React.createClass({ //individual cell
render: function() {
return (
<td className={this.props.alive}
index={this.props.index}
coords={this.props.coords}
r={this.props.r}
c={this.props.neighbors}
neighbors={this.props.neighbors}
>
{this.props.children}
</td>
)
}
})
ReactDOM.render(<Board/>, document.getElementById('content'))

it sounds like an off by 1 error. when you generate the grid, you go from index 0..19, yet in the updateCells function you have a loop that sets c = 0, then subtracting 1 from it, meaning youre trying to access grid[-1][-1] when r and/or c is 0 (probably not what youre trying to do). then the fatal error likely occurs when r is 19 and c is 0, and you add one to r and subtract 1 from c. you end up accessing grid[20][-1] and since grid[20] is undefined, you get the error Cannot read property '-1' of undefined

Related

ng-repeat how to use filetr with 2 parametrer

Here im using ng-repeat as
<tr ng-repeat="d in TranHistory">
<td>{{d.Quantity}}</td>
<td>{{d.Qty_Lock}}</td>
<td>{{d.Balancedcommodity |filter:GetBalance(d.Quantity,d.Qty_Lock)}}</td>
</tr>
im trying to do Sum Operation on filter:GetBalance
$scope.GetBalance = function (Quantity,lock) {
if (Quantity > 0 && lock > 0) {
var available = Quantity - lock;
return d.Balancedcommodity = available;
}
}
Here why im not able to update my value
Thank you
why don't you do this:
<td>{{GetBalance(d)}}</td>
and in your js:
$scope.GetBalance = function (d) {
if (d.Quantity > 0 && d.Qty_Lock > 0) {
var available = d.Quantity - d.Qty_Lock;
d.Balancedcommodity = available;
return d.Balancedcommodity;
}
return "";
}
And if you really wanna use filter: try custom filter like below
angular.filter('getBalance', function(){
return function(d){
if (d.Quantity > 0 && d.Qty_Lock > 0) {
var available = d.Quantity - d.Qty_Lock;
return available;
}
return "";
}
});
And in your html you can use it like this:
<td>{{d|getBalance}}</td>

Angularjs summary row filter

I am trying to build a custom filter which would add a summary (avg) row to the array.
I'd like to use it like this:
<tr ng-repeat="item in items | summaryRow : ['col1', 'col1'] | orderBy : 'name'"
Here's what my filter looks like:
summaryRow.$inject = ['$parse', 'sumFilter'];
function summaryRow($parse, sumFilter) {
return function(array, fields, nameLabel) {
var row = { id: 0 }, l = array.length, setter;
row[nameLabel || 'name'] = 'Summary';
for (var i = 0, j = fields.length; i < j; i++) {
setter = $parse(fields[i]).assign;
setter(row, sumFilter(array, fields[i]) / l);
}
return array.concat(row);
}
}
The thing is I want this extra row to be sortable and aware of changes within items. The problem is I'm running into the "10 $digest() iterations reached. Aborting!" issue. Any help would be appreciated, thanks!
Try using $filter in your controller. Here is a great post written by Todd Motto:
https://toddmotto.com/use-controller-filters-to-prevent-digest-performance-issues/
Found the solution, here's what I came up with:
summaryRow.$inject = ['$parse', 'sumFilter'];
function summaryRow($parse, sumFilter) {
var row = {};
return function(array, fields, nameLabel) {
clearObject(row);
var l = array.length, setter;
row.id = 0;
row[nameLabel || 'name'] = 'Summary';
for (var i = 0, j = fields.length; i < j; i++) {
setter = $parse(fields[i]).assign;
setter(row, sumFilter(array, fields[i]) / l);
}
return array.concat(row);
}
}
And here's the clearObject implementation:
function clearObject(obj) {
for (var k in obj)
delete obj[k];
}

Grouping variable number of elements with React / JSX

I have a situation where I have a variable number of objects in an array, and I need to create groups of three. I know that to iterate over an array I can do something like this (Fiddle - https://jsfiddle.net/o5pbttjq/):
render: function() {
var random = Array.apply(null, Array((Math.floor(Math.random() * (15 - 1) + 1) + 1))).map(Number.prototype.valueOf,0).map(function(d,i) {return i});
return (
<div>
{random.map(function(d) {
return <p>{d}</p>
})}
</div>
);
}
I can also group them like so, but it's quite ugly (Fiddle - https://jsfiddle.net/Lxqm3tzo/):
render: function() {
var random = Array.apply(null, Array((Math.floor(Math.random() * (15 - 1) + 1) + 1))).map(Number.prototype.valueOf,0).map(function(d,i) {return i});
var content = "<div>";
random.forEach(function(d, i) {
if (i % 3 === 0 && i !== 0) {
content += "</div><div>"
}
content += "<p>" + d + "</p>"
})
content += "</div>"
return (
<div dangerouslySetInnerHTML={{__html: content}}></div>
);
}
How can I create groups of a variable number of objects with JSX?
The following will work without having to set the innerHTML. See the fiddle here: https://jsfiddle.net/kuvvd513/8/
var Hello = React.createClass({
render: function() {
var random = Array.apply(null, Array((Math.floor(Math.random() * (15 - 1) + 1) + 1))).map(Number.prototype.valueOf,0).map(function(d,i) {return i});
var allGroups = [];
var group = [];
random.forEach(function(d, i) {
if (i % 3 === 0 && i !== 0) {
allGroups.push(<div> {group} </div>);
group = [];
}
group.push(<p> {d} </p>);
}.bind(this));
allGroups.push(<div> {group} </div>);
return (
<div>
{allGroups}
</div>
);
}
});
React.render(<Hello name="World" />, document.getElementById('container'));

Angular 3 filters (inputs and checkboxes)

I need some help.
screenshot: https://monosnap.com/file/VxAdq975FVT6QHkECfxlFyHgGd3sAn
I have 3 filters on top: rooms, size and price. How to filter table results, when something typing in the filter fields?
UPDATE
$scope.$watch( '[min_size, max_size]', function(val) {
$scope.filterBySizeRange();
});
$scope.filterBySizeRange = function() {
$scope.filteredSizes = [];
angular.forEach($scope.apps, function(items) {
if (items.size >= $scope.min_size
&& items.size <= $scope.max_size) {
$scope.filteredSizes.push(items);
}
if (!$scope.min_size
&& !$scope.max_size) {
$scope.filteredSizes.push(items);
};
});
};
UPDATE 3
Here is my solution, that works with single or multiple range input fields
fiddle
I think you want to use $watchGroup.
$scope.$watchGroup(['min_size', 'max_size'], function(val) {
$scope.filterBySizeRange();
});
$scope.filterBySizeRange = function() {
$scope.filteredSizes = [];
angular.forEach($scope.apps, function(items) {
if (items.size >= $scope.min_size
&& items.size <= $scope.max_size) {
$scope.filteredSizes.push(items);
}
if (!$scope.min_size
&& !$scope.max_size) {
$scope.filteredSizes.push(items);
};
});
};
Anyway I believe that it would be better to create your own filter function
// template
<div ng-repeat="item in apps|sizefilter:min_size:max_size">
// filter
app.filter('sizefilter', function() {
return function(collection, minSize, maxSize) {
var items = collection.slice(0, collection.length -1);
var i =0, len = items.length
for (; i < len;) {
if (items.size < minSize && items.size > maxSize) {
items.splice(i, 1);
} else {
i++;
}
}
return items;
});
};
});
// Min/max size filter
$scope.sizeFilter = function(app){
if (!$scope.min_size && !$scope.max_size) {
return app;
} else if(!$scope.max_size){
return (app.size >= $scope.min_size);
} else if(!$scope.min_size){
return (app.size <= $scope.max_size);
} else {
return (app.size >= $scope.min_size && app.size <= $scope.max_size);
}
}

Meaning of hyphen in Angular tags

I found a bit of code on Plunker which I don't understand. It's a word cloud where the cloud is added to the page with:
<tang-cloud words="words" on-click="test(word)" width="500" height="500"></tang-cloud>
This is some how picked up by Angular. What I don't understand is I can find no references to "tang-cloud" in the rest of the code. Various "tangcloud" but nothing with a hyphen.
I'm totally new to Angular, I've stumbled across another case where this seems to happen, but all the tutorial cases I've seen would have used "tangcloud". If I remove the hyphen it stops working, so I must just be missing something simple.
Thank you
It's a directive. Since HTML is case-insensitive, angular converts the tangCloud directive to tang-cloud to be readable by HTML.
The tangCloud directive in tangCloud.js is where you'll find the code for that.
Edit: Just to follow up, you see the bit that says restrict: 'E'? That tells angular that you can use the directive as a custom element. When you make a directive camelcase, like tangCloud, angular will automatically convert it to tang-cloud.
.directive('tangCloud', ['$interpolate', '$compile', '$timeout', function ($interpolate, $compile, $timeout) {
var directive = {
restrict: 'E',
scope: {
width: '=',
height: '=',
words: '=',
onClick: '&',
spin: '='
},
template: function (tElement, tAttrs) {
var isClickable = angular.isDefined(tAttrs.onClick);
var clickAttr = isClickable ? 'ng-click="onClick({word : entry.word, id : entry.id})"' : '';
return "<div class='tangcloud'>" +
"<span ng-repeat='entry in words'" + clickAttr + ">{{entry.word}}</span>" +
"</div>";
},
compile: function (elem) {
elem.children().children()
.addClass('tangcloud-item-' + $interpolate.startSymbol() + 'entry.size' + $interpolate.endSymbol())
.addClass('tangcloud-item-hidden');
return function (scope, elem) {
var centerX = scope.width / 2;
var centerY = scope.height / 2;
var outOfBoundsCount = 0;
var takenSpots = [];
if (scope.words) {
scope.words = shuffleWords(scope.words);
determineWordPositions();
}
function shuffleWords(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function determineWordPositions() {
$timeout(function () {
var trendSpans = elem.children().eq(0).children();
var length = trendSpans.length;
for (var i = 0; i < length; i++) {
setWordSpanPosition(trendSpans.eq(i));
}
});
}
function setWordSpanPosition(span) {
var height = parseInt(window.getComputedStyle(span[0]).lineHeight, 10);
var width = span[0].offsetWidth;
var spot = setupDefaultSpot(width, height);
var angleMultiplier = 0;
while (spotNotUsable(spot) && outOfBoundsCount < 50) {
spot = moveSpotOnSpiral(spot, angleMultiplier);
angleMultiplier += 1;
}
if (outOfBoundsCount < 50) {
takenSpots.push(spot);
addSpanPositionStyling(span, spot.startX, spot.startY);
}
outOfBoundsCount = 0;
}
function setupDefaultSpot(width, height) {
return {
width: width,
height: height,
startX: centerX - width / 2,
startY: centerY - height / 2,
endX: centerX + width / 2,
endY: centerY + height / 2
};
}
function moveSpotOnSpiral(spot, angleMultiplier) {
var angle = angleMultiplier * 0.1;
spot.startX = centerX + (1.5 * angle) * Math.cos(angle) - (spot.width / 2);
spot.startY = centerY + angle * Math.sin(angle) - (spot.height / 2);
spot.endX = spot.startX + spot.width;
spot.endY = spot.startY + spot.height;
return spot;
}
function spotNotUsable(spot) {
var borders = {
left: centerX - scope.width / 2,
right: centerX + scope.width / 2,
bottom: centerY - scope.height / 2,
top: centerY + scope.height / 2
};
for (var i = 0; i < takenSpots.length; i++) {
if (spotOutOfBounds(spot, borders) || collisionDetected(spot, takenSpots[i])) return true;
}
return false;
}
function spotOutOfBounds(spot, borders) {
if (spot.startX < borders.left ||
spot.endX > borders.right ||
spot.startY < borders.bottom ||
spot.endY > borders.top) {
outOfBoundsCount++;
return true;
} else {
return false;
}
}
function collisionDetected(spot, takenSpot) {
if (spot.startX > takenSpot.endX || spot.endX < takenSpot.startX) {
return false;
}
return !(spot.startY > takenSpot.endY || spot.endY < takenSpot.startY);
}
function addSpanPositionStyling(span, startX, startY) {
var style = "position: absolute; left:" + startX + "px; top: " + startY + "px;";
span.attr("style", style);
span.removeClass("tangcloud-item-hidden");
}
};
}
};
return directive;
}]);
The tang-cloud directive is defined as tangCloud - take this example from the angular docs for directive
app.js
.directive('myCustomer', function() {
return {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
};
});
index.html
<div ng-controller="Controller">
<div my-customer></div>
</div>
See the Normalization section in this part of the docs. Try searching 'tangCloud'

Resources