Array of Components with refs - javascript

For my four in a row game I create an Array of components. I need to call a method of one specific component from the parent component.
Here are the two methods that are building the Field. At renderRow I put the ref into an Array which is defined in the constructor.
renderRow(row){
var Buttons = new Array(this.props.w)
for (var i = 0; i < this.props.w; i++) {
thisButton=<FieldButton ref={(r) => { this.refField['row'+ row +'col'+ i] = r; }} handler={this.actionFunction} key={'row'+ row +'col'+ i}></FieldButton>
Buttons.push(thisButton)
}
return Buttons
}
renderField(){
var Field = new Array(this.props.h);
for (var i = 0; i < this.props.h; i++) {
var row = this.renderRow(i);
Field.push(<View key={i} style={styles.fieldWrap}>{row}</View>);
}
this.field = Field;
}
The actionFunction should simply print the current ref.
actionFunction(pos) {
console.log(this.refField['row'+ pos.row +'col'+ pos.col])
}
The problem: The output is undefined.
Edit:
If I console.log the reField variable this is the output:

The solution is that I had to use let instead of var to initialise the variables in the for loop.
Working code:
renderRow(row){
var Buttons = new Array(this.props.w)
for (let i = 0; i < this.props.w; i++) {
let thisButton=<FieldButton ref={(r) => { this.refField['row'+ row +'col'+ i] = r; }} handler={this.actionFunction} key={'row'+ row +'col'+ i}></FieldButton>
Buttons.push(thisButton)
}
return Buttons
}
renderField(){
var Field = new Array(this.props.h);
for (let i = 0; i < this.props.h; i++) {
let row = this.renderRow(i);
Field.push(<View key={i} style={styles.fieldWrap}>{row}</View>);
}
this.field = Field;
}

React actually has a special property just for this called refs!
https://reactjs.org/docs/refs-and-the-dom.html
You can actually just set a name for the reference to the object and then access that object anywhere in your class using this.refs.referenceName. Do note that when you set the reference on the object the property name is 'ref' (singular) and when you want to access it you use 'this.refs' (plural).
For instance:
renderRow(row){
var Buttons = new Array(this.props.w)
for (var i = 0; i < this.props.w; i++) {
const thisButton=<FieldButton ref={'row' + row + 'col' + i} handler={this.actionFunction} key={'row'+ row +'col'+ i}></FieldButton>
Buttons.push(thisButton)
}
return Buttons
}
Now, if you need to get that FieldButton from your actionFunction:
actionFunction(pos) {
console.log(this.refs['row'+ pos.row +'col'+ pos.col])
}
Note: also make sure your function is being bound to the class instance.

Related

JavaScript `this` in event listener when using classes

I'm trying to write a simple Game of Life app. Unfortunately, after wrapping my code with a class, I've encountered the following problem.
cell.addEventListener('click', this.cellClick.bind(this));
I've binded the cellClick() method, but when I try to use this.id.split() or this.className within the cellClick() method I get an uncaught exception TypeError: Cannot read property 'split' of undefined at GOL.cellClick.
I've tried to use an arrow function instead, but that didn't work too.
The code works when I don't use classes.
I'm fairly new to JS and I've tried to look for a solution online and couldn't find one.
class GOL {
constructor(rows, columns) {
this.rows = rows;
this.columns = columns;
this.currentGeneration = Array(this.rows).fill(false).map(() => Array(this.columns).fill(0));
this.nextGeneration = Array(this.rows).fill(false).map(() => Array(this.columns).fill(0));
}
createWorld() {
const world = document.getElementById('world');
const table = document.createElement('table');
table.setAttribute('id', 'grid');
for (let i = 0; i < this.rows; i++) {
const tr = document.createElement('tr');
for (let j = 0; j < this.columns; j++) {
const cell = document.createElement('td');
cell.setAttribute('id', i + ':' + j);
cell.setAttribute('class', 'dead');
cell.addEventListener('click', this.cellClick.bind(this));
tr.appendChild(cell);
}
table.appendChild(tr);
}
world.appendChild(table);
}
cellClick() {
const location = this.id.split(':');
const row = location.indexOf(0);
const column = location.indexOf(1);
this.className === 'alive' ? this.setAttribute('class', 'dead') : this.setAttribute('class', 'alive');
}
}

nodeList gives only last item with onClick

I have a JSON file which gives me every country, and I place these within <li> tags.
I want to loop through this list, and put a click event on every <li>, which it does. But no matter what I click on, I always get the last item in the list returned to me. What am I missing?
I want the item I'm clicking returned.
function lists() {
var items = document.querySelectorAll('#Countries>ul>li');
for (i = 0; i < items.length; i++) {
var item = items[i];
item.onclick = function() {
console.log(item);
}
}
}
You need to pass the item in the onclick function as a parameter. This is because the function inside the for loop will consider only the last item.
function lists() {
var items = document.querySelectorAll('#Countries>ul>li');
for (i = 0; i < items.length; i++) {
var item = items[i];
item.onclick = function(item) {
console.log(item);
}
}
}
You can also use let to isolate the scope:
function lists() {
let items = document.querySelectorAll('#Countries>ul>li');
for (let i = 0; i < items.length; i++) {
let item = items[i];
item.onclick = function() {
console.log(item);
}
}
}
Declare the variable with let in the for loop that will declare a block scope local variable. Try
for (let i = 0; i < items.length; i++) {
its a scoping issue, var is function scope.
you can use let from es6 to declare the variable, or another way is to use bind.
function lists() {
var items = document.querySelectorAll('#Countries>ul>li');
for (i = 0; i < items.length; i++) {
var item = items[i];
item.onclick = function() {
console.log(this);
}.bind(item);
}
}
lists();
<div id="Countries">
<ul>
<li>country1</li>
<li>country2</li>
</ul>
</div>

Javascript object property returns as undefined only when called from inside another function

I created 3 objects that nest arrays of one another - we'll call them Table, Row and Column so I can show you what's wrong. Table has an array of Rows, and Row has an array of Columns. When I call properties of the Rows from Table, no problem. When I call properties of the Columns from Row, it says undefined, but in the debugger and the console it recognizes the object and it's properties. Maybe I've been staring at it too long but I can't see a fundamental difference.
I stripped the Table layer to be sure it wasn't an issue with nested objects. Here's the code, not working:
function Column()
{
this.sampleProp = "testprop";
this.content = "<td>sometext</td>";
}
function Row(columns)
{
this.columns = [];
this.columns = columns;
this.outputRows = function()
{
var temp = "<tr>";
for(var i = 0; i < this.columns.length; i++)
{
//this is the line that doesn't work and comes out as undefined:
temp += this.columns[i].content;
console.log("Problem: " + this.columns[i].content);
//yet the object exists, and has the correct properties:
console.log(this.columns[i]);
}
temp += "</tr>";
return temp;
};
}
function test()
{
var col = new Column();
console.log("Printing out the value from here works fine: " + col.content);
var cols = [col];
console.log("It's also fine when it's called from an array: " + cols[0].content);
var row = new Row([cols]);
console.log(row.outputRows());
}
Here is the interaction between the parent layer and the rows, working fine:
function Table(rows)
{
this.rows = [];
this.rows = rows;
this.outputTable = function()
{
var temp = "<table>";
for(var i = 0; i < this.rows.length; i++)
{
temp += this.rows[i].outputRows();
}
temp += "</table>";
return temp;
};
}
and the updated test function:
function test()
{
var column = new Column();
var cols = [column];
var row = new Row([cols]);
console.log(row.outputRows());
var rs = [row, row];
var table = new Table(rs);
console.log(table.outputTable());
}
Two rows print out as expected this way, with undefined inside each. I originally had column.content written as a function, it doesn't make a difference.
Please tell me what stupid mistake I'm missing here!
Change that line :
var row = new Row([cols])
into
var row = new Row(cols)
since cols is already an array, you don't need to put it in an array again.
function Column() {
this.sampleProp = "testprop";
this.content = "<td>sometext</td>";
}
function Row(columns) {
// removed this.columns = [] since you are assigning it in the next line
this.columns = columns;
this.outputRows = function() {
var temp = "<tr>";
for (var i = 0; i < this.columns.length; i++) {
//this is the line that doesn't work and comes out as undefined:
temp += this.columns[i].content;
}
temp += "</tr>";
return temp;
};
}
function test() {
var col = new Column()
console.log("Printing out the value from here works fine: " + col.content);
var cols = [col];
console.log("It's also fine when it's called from an array: " + cols[0].content);
var row = new Row(cols); // the problem was here
console.log(row.outputRows());
}
test()

create unique nested objects dynamically

I need to create unique objects(Route) for the my variable's routes property. And this has to be done in a loop.
Please check out my code blow or http://jsfiddle.net/2gk36mvo/ to have a more clear image about my problem.
html
<input type="button" value="ss" onclick="initialize();">
javascript
var my={
routes:{}
};
function Route(points)
{
this.points = points;
return this;
};
function getRoutes(routes){
var result = [];
for (var prop in my.routes) {
result.push(prop);
}
return result.toString();
}
function initialize()
{
// create and add objects manually
my.routes.r0 = new Route("blabla0");
my.routes.r1 = new Route("blabla1");
alert(getRoutes(my.routes)); // gives 'r0,r1'
// clear the routes for the dynamic test
my.routes = {};
// create and add objects dynamically
for (i = 0; i < 2; i++) {
//???????????? create and and add the new Route objects
}
alert(getRoutes(my.routes)); // must give the same result as above 'r0,r1'
}
As cackharot states in his comment, you need to have code similar to this in your for loop:
for (i = 0; i < 2; i++)
{
my.routes["r"+i] = new Route("blahbla"+i);
console.log(my.routes);
}

Kendo grid change multiple selected cells value

Let's say I have few rows of data populated with numbers. I want to select multiple cells and then on click on a button outside the grid change their values to some other number, let's say '8'. See the sample.
The guys at Telerik gave me this solution:
$(".change").click(function () {
var grid = $("#Grid").data("kendoGrid");
var cellsToChange = grid.select();
for (var i = 0; i < cellsToChange.length; i++) {
var item = grid.dataItem($(cellsToChange[i]).closest("tr"));
item.ProductName = "new value";
}
grid.refresh();
});
But the problem is that I don't know which cells will be selected, so I can't work with item.ProductName, for example. Is there a way to set the value of all selected cells directly, something like cellsToChange[i].value?
You can either get the column name from grid.columns or from the corresponding th element. use the grid.cellIndex method to select the correct column:
$("#change").click(function() {
var selected = grid.select();
var header = grid.thead;
for (var i = 0, max = selected.length ; i < max ; i++) {
var index = grid.cellIndex(selected[i]);
var th = $(header).find("th").eq(index);
// could also use grid.columns[index].field
// (not sure if this gets reordered on column reorder though)
var field = $(th).data("field");
var item = grid.dataItem($(selected[i]).closest("tr"));
item[field] = "new value";
}
grid.refresh();
});
Regarding your comment:
dataItem.set() causes the <tr> elements to get removed from their context (because grid.refresh() will create new rows for the view), and because of that, grid.dataItem() won't give you the expected result with the old DOM elements you still have a reference to.
If you want to use dataItem.set(), you can try something like this as a work-around:
$("#change").click(function () {
var selected = grid.select(),
header = grid.thead,
dataItem,
index,
field,
value,
th;
for (var i = 0, max = selected.length; i < max; i++) {
dataItem = grid.dataItem($(selected[i]).closest("tr"));
index = $(selected[i]).index();
th = $(header).find("th").eq(index);
field = $(th).data("field");
value = "new value " + i;
setTimeout(function (dataItem, field, value) {
return function () {
dataItem.set(field, value);
}
}(dataItem, field, value), 5);
}
});
(demo)
You have to retrieve the ColumnList of your grid first and then loop through it
$(".change").click(function () {
var grid = $("#Grid").data("kendoGrid");
var columnsListOfView = grid.columns;
var cellsToChange = grid.select();
for (var j = 0; i < cellsToChange.length; i++) {
var item = grid.dataItem($(cellsToChange[i]).closest("tr"));
for (var j = 0; j < columnsListOfView.length; j++) {
//Here columnsListOfView[j].field will give you the different names that you need
var field=columnsListOfView[j].field;
item[field] = "new value";
}
}
grid.refresh();
});

Categories

Resources