Where to handle parent-child event React - javascript

I'm getting start with React basic concepts, and I'm not sure how to proceed...
I'm trying to build a simple Minesweeper game, my structure is a MinesweeperField wrapper, with height, width and minesNumber as props, and a MinesweeperTile, with value (is it a mine/number of close mines) and explored as props.
My problem is, I'm not sure how to handle clicking on the tiles. The MinesweeperField component must be notified when a tile is explored to handle the new game state (ex: in case of game over or win), and it must also be able to trigger explore in other tiles (for example, if you click on a tile with 0 close mines, all of the adjacent tiles must be explored automatically).
From what I understood there are two approaches:
Handle click on the Tile, Tile notifies Field, Field triggers explore also on adjacent Tiles - notifying Field from Tile is easy, but calling Tile from Field would require to use refs, plus a different click handler function is instantiated for each Tile
Handle click on Field, Field updates the prop that in render is used to assign the isExplored prop to the Tile - since I render the Tile based on an array variable in the Field's state, I have to use setState to trigger a redraw of child Tiles components.
this is a simplified sample of the render function, in the "Handle Click on Field" version:
return (
<div className="minesweeper-wrapper">
<div className="minesweeper-field" style={this.getDimensions()}>
{this.state.proximityMap.map(function(verticalRow, vIndex){
return verticalRow.map(function(tileValue, hIndex){
return <MinesweeperTile value={tileValue} hIndex={hIndex} vIndex={vIndex} onClick={this.exploreTile.bind(this, vIndex, hIndex, tileValue)} isExplored={this.state.explorationMap[vIndex][hIndex]} />
});
})}
</div>
</div>
);
and here's the exploreTile function:
exploreTile(vIndex, hIndex, tileValue) {
this.unveilTile(vIndex, hIndex);
if (tileValue < 0) {
this.gameOver();
} else {
if (tileValue === 0) {
this.propagateTileExplore(vIndex, hIndex);
}
}
}
this.state.proximityMap contains values indicating how many mines are close to this tile/is this tile a mine. this.state.explorationMap contains bools indicating which tiles have been explored.
My problem with this approach is that, from what I understood, if I want to redraw a single tile, I have to call setState on Field and update this.state.explorationMap array, which will redraw every tile!
Any ideas as to how I could limit the redraw to a single Tile? Should I keep exploring the "handle click on Field" way or go back to "handle click on Tile"?
I started with "handle click on Tile" but I stopped when I got to the "explore adjacent tiles" problem.
Also please no jQuery/js tricks or hacks to solve this in an unconventional way, it's not about finding a way to do this, but finding the most appropriate. It won't probably make a noticeable difference in the context of a Minesweeper game, but as I said, it's for training purposes :)

The most appropriate way would be to let the parent handle the changes. The children just need to know whom to call when they are clicked and how they should look like. I did the same thing in my Game of Life clone using React.
CodePen Link
changeTile(row, col) {
if (!this.state.isPlaying) {
const newTile = this.state.board[row][col] === 1 ? 0 : 1;
const newRow = [...this.state.board[row]];
newRow.splice(col, 1, newTile);
const newBoard = [...this.state.board];
newBoard.splice(row, 1, newRow);
this.setState({
board: newBoard
});
}
}

Related

How to create a snappy User Interface with lots of local data?

I have a JSON file with 15000+ items. My JSON file has about 7MB. I'd like to display all results in a table (without pagination) and add a few checkboxes to display or hide the items under certain conditions. To simplify as much as possible, let's say I have a checkbox to display items from Paris only:
The problem is that the UI is not smooth. Clicking on the checkbox takes about one second to refresh the table. It's unacceptable.
I'm using fetch() to download JSON data and then keep it locally. When user clicks on the checkbox, here's what I did:
document.getElementById('paris-only').addEventListener('click', updateTable);
function updateTable() {
const parisOnly = document.getElementById('paris-only').checked;
items.forEach(item => item.visible = !parisOnly || item.city === 'Paris');
refreshTable();
}
function refreshTable() {
[...document.getElementById('items-table').children].forEach((tr, index) => {
tr.classList.toggle('hidden', !items[index].visible);
});
}
.hidden {
display: none;
}
I tried to play with setTimeout(_, 0), but it doesn't help much. It makes the UI freeze less, but it still takes a lot of time to refresh the table.
Is there any strategy available to make such an interface snappy? One of my ideas was to consider <canvas>, which makes drawing many elements quick, but it doesn't seem very natural for the task.
Another option would be to load data with fetch() on scroll, but I'd like to have all data loaded just once if possible.
What else should I consider?
I'd recommend using classes to signify if a given node should be shown or hidden (e.g. every node with name !== 'Paris' is in the notparis class. You can use javascript to initialize these categories (How do I add a class to a given element?) when you fetch the JSON table, and then showing/hiding can use for (tr of document.getElementsByClassName('notparis')) tr.visible = !parisOnly. If these categories overlap (i.e. you want notparis and notcoldweather), you could probably record which classes are currently visible or hidden and use document.getElementsByClassName('notparis notcoldweather') (this may be a bit more complex, but should maintain performance).
Also, changing the items directly instead of using an intermediate list might be more performant.

Angular 2 change template input values on click

Working with a set of column components with id input in my template:
<div class="panel-body" *ngIf="columns">
<div class="col-md-4">
<column [id]=columns[current_left_column].Id></column>
</div>
<div class="col-md-4">
<column [id]=columns[current_middle_column].Id></column>
</div>
<div class="col-md-4">
<column [id]=columns[current_right_column].Id></column>
</div>
</div>
I have buttons that increment and decrement the columns by changing the values of current_left_column, current_middle_column, and current_right_column. When I click those buttons I log the values of the three column id's and they're representative of where they should be however the template doesn't reload.
I did attempt to use ApplicationRef.tick() to trigger change detection, but the fact that it didn't change makes me think it's a binding issue, but I'm thus far unable to find anything that matches my case as 2-way binding seems to necessitate a more traditional input element and it currently is one way bound.
I think you need to change the name of the input. I have seen it several times that it collides with the id property every HTML element has.
I really appreciate the comments and info given, it helped me get a better handle on what I was actually trying to do.
I ended up taking an example from a different answer and using ngModel with an input to show the individual column id's. Then since I could prove my id's were switching but it wasn't updating even with the tick() I realized I was working on one component level too high and had to be initiating the change from the Column level. So I took the ngOnInit method that populates the columns with data and put it in a ngOnChanges() method, cleared the OnInit() because it was doubling up, and it started moving fine.
Then I had to deal with the information from the columns being added on top of the prior column, but that was relatively minor.
ngOnChanges(){
this._columnService.GetSingleColumn(this.id).subscribe(column => { this.columnData = column; });
this._cardService.GetCardsByColumnId(this.id).subscribe(cards => { this._cardColumnService.LoadCards(cards); console.log(this.cards); });
if (this.columnData == undefined) {
this.LoadDummyData();
}
}

React/redux + bootstrap, make modal show unique for component

I have a component that shows a modal to pop up some content in my map. I have a pretty straight forward set up :
The JSX looks like this :
<Modal show={this.props.results.showPreviewModal} >
{myPreviewContent}
</Modal>
2 action-creators to open, close, and set the current item :
export function previewAsset(result) {
return {
currentResult: result,
type: actions.PREVIEW_ASSET
};
}
export function closePreviewModal() {
return {
type: actions.CLOSE_PREVIEW_MODAL
};
}
And their reducers :
case actions.PREVIEW_ASSET:
return state.set('currentPreview', action.currentResult).set('showPreviewModal', true);
case actions.CLOSE_PREVIEW_MODAL:
return state.set('showPreviewModal', false);
Now, this seems to work fine. However, the issue is that the component that has the modal inside of it is inside a map, as it is a singular search result (each result component has a some functionality so it is it's own component that is mapped over with results). The issue is that if I have 10 results, this modal opens 10 times when I click the button that fires the previewAsset action.
This makes sense, because the showPreviewModal is accessible by all components, but what I am wondering is if there is a way to make then unique for each component individually, so only the 1 modal opens, not all 10. Unsure how to approach this within react/redux, would very much appreciate any advice, thanks!
An approach I've used successfully is to pull the Modal component out of the loop (or map() in this case), and have a reducer for currentItem or something similar, which gets set when an item is selected (you could also use currentItemIndex, and then select the current item based on that in your connect() call).
In the parent component, you'd have the Modal as a child, and only display it if that currentItem is not null.
Here's a quick JSBin example to show you what I mean:
http://jsbin.com/fefoxoy/edit?html,js,console,output

How to deal with dynamic properties in Backbone and sync to database

I have been struggling with Backbone the last few days in trying how to best approach dealing with some dynamic elements added by a user and sync those successfully with the database. I have one model and one view.
The model created is fairly straightforward, it represents a product(t-shirt) in a database and has the attributes: id, price, size, brand, colors.
The problem I am faced with is the colors attribute. The colors cannot be pre-populated by design (unfortunate as it may be) to allow for the user to enter any custom color and name it as freely as they want. In addition to the name, the user has to specify if the color is available. Clicking the Add Text button/link will have an input field and dropdown appended to the div below.
My question: What is the best way to add these multiple color properties as ONE attribute of the model?
I need to have all the colors/availability values as one property when it attempts to insert or update itself with the API as the colors property and goes into one row in the db (mysql). I believe the backend programmer has this row configured as a type of TEXT.
e.g.
{"colors": [{"blue":true},{"orange":false},{"white":false}]}
My thinking is that I need to obviously have some sort of nested JSON within the model but I can't figure out how to write this properly. Any help or something to point me in the right direction would be much appreciated.
Ok, this solution involves jQuery maybe a bit too much, but should work fine. Basically, listen to both changes of your color textboxes and select:
events: {
'change .colorText': 'setColor',
'change .colorSelect': 'setColor'
},
setColor: function() {
// here make your `color` attribute's array
var colors = [];
this.$('.colorText').each(function() {
var val, color;
// adapt the next to navigate to the corresponding select...
(val = $(this).val()) && (((color = {})[val] = $(this).next().val()) || 1) && colors.push(color);
});
this.model.set('colors', colors);
}

How to update ZK Grid values from jQuery

I have three Tabs and in each tab, I have a Grid.
The data for each Grid is coming from a database, so I am using rowRenderer to populate the Grids. The following code is common for all three Grids:
<grid id="myGrid1" width="950px" sizedByContent="true" rowRenderer="com.example.renderer.MyRowRenderer">
The rows are constructed from Doublebox objects. The data is populated successfully.
The Problem:
I need to handle multiple-cell editing on the client side. The editing is done via mouse-clicking on a particular cell and entering a value.
As example let's say that the user edits first cell on the first row and the value should be
propagated to all other cells on the same row and in all three Grids (so also the two Grids which the user currently does not see, because they are in tabpanes).
I am using jQuery to do this value propagation and it works OK.
I am passing the jQuery as follows:
doublebox.setWidgetListener(Events.ON_CHANGING, jQuerySelectors);
doublebox.setWidgetListener(Events.ON_CHANGE, jQuerySelectors);
This makes it possible to change the value in 1 cell and the change is instantly (visually) seen in all other cells filtered by jQuery selectors.
The problem is that the value is visually distributed to all the cells, but when I try to save the Grid data back to the database, the background values are the old ones.
I am assuming that ZK-Grid component is not aware that jQuery changed all the cell values. Nevertheless if I manually click on a cell that already has the NEW value (enter/leave/change focus) when I save the grid the NEW value is correct in that particular cell. Maybe that's a hint how can I resolve this.
Code of how I extract the Grid values:
Grid tGrid = (Grid) event.getTarget().getFellow("myGrid1");
ListModel model = tGrid.getModel();
MyCustomRow tRow = (MyCustomRow)model.getElementAt(i);
The model for my Grid is a List of MyCustomRow:
myGrid1.setModel(new ListModelList(List<MyCustomRow> populatedList));
I have a couple of assumptions, but whatever I have tried, hasn't worked. I have in mind that jQuery events and ZK-Events are different and probably isolated in different contexts. (Although I have tried to fire events from jQuery and so on..)
Do you have any suggestions? As a whole is my approach correct or there's another way to do this? Thanks for your time in advance!
Your problem is exactly what you are expecting.
Zk has it's own event system and do not care about your jq,
cos it's jq and zk don't observ the DOM.
The ways to solve your problem.
Use the "ZK-Way":
Simply listen at server-side and chage things there.
I am not sure if not selected Tabs
are updateable, but I am sure you could update the Grid
components on the select event of the Tab.
Fire an zk-event your self:
All you need to know, is written in the zk doc.
Basically, you collect your data at client side, send
an Event to the server via zAu.send() extract the
data from the json object at serverside and update your Grids
I would prefer the first one, cos it's less work and there should not be
a notable difference in traffic.
I post the solution we came up with:
This is the javascript attached to each Doublebox in the Z-Grid
//getting the value of the clicked cell
var currVal = jq(this).val();
//getting the next cell (on the right of the clicked cell)
objCells = jq(this).parents('td').next().find('.z-doublebox');
// if there's a next cell (returned array has length) - set the value and
// fire ZK onChange Event
if (objCells.length) {
zk.Widget.$(jq(objCells).attr('id')).setValue(currVal);
zk.Widget.$(jq(objCells).attr('id')).fireOnChange();
} else { //otherwise we assume this is the last cell of the current tab
//So we get the current row, because we want to edit the cells in the same row in the next tabs
var currRow = jq(this).parents('tr').prevAll().length;
//finding the next cell, on the same row in the hidden tab and applying the same logic
objCellsHiddenTabs = jq(this).parents('.z-tabpanel').next().find('.z-row:eq(' + currRow + ')').find('.z-doublebox');
if (objCellsHiddenTabs.length) {
zk.Widget.$(jq(objCellsHiddenTabs).attr('id')).setValue(currVal);
zk.Widget.$(jq(objCellsHiddenTabs).attr('id')).fireOnChange();
}
}
The java code in the RowRenderer class looks something like this:
...
if (someBean != null) {
binder.bindBean("tBean", someBean);
Doublebox box = new Doublebox();
setDefaultStyle(box);
row.appendChild(box);
binder.addBinding(box, "value", "tBean.someSetter");
...
private void setDefaultStyle(Doublebox box) {
box.setFormat("#.00");
box.setConstraint("no negative,no empty");
box.setWidth("50px");
String customJS = ""; //the JS above
//this is used to visually see that you're editing multiple cells at once
String customJSNoFireOnChange = "jq(this).parents('td').nextAll().find('.z-doublebox').val(jq(this).val());";
box.setWidgetListener(Events.ON_CHANGING, customJSNoFireOnChange);
box.setWidgetListener(Events.ON_CHANGE, customJS);
}
What is interesting to notice is that ZK optimizes this fireOnChange Events and send only 1 ajax request to the server containing the updates to the necessary cells.

Categories

Resources