React JS Rendering parent does not render children again - javascript

I am having some issues with React JS rendering children when rendering the parent.
Here I am trying to implement the "Game of Life" (a project from Freecodecamp class).
I am stuck in this situation. I click on a dead cell and it becomes alive (blue). Then, suppose I want to clear the grid, that is, make all cells dead, but it doesn't work. It seems that even re-rendering the parent will not re-render the children.
Any idea?
var board = [];
var width = 80;
var length = 50;
var cells = width * length;
var states = ["alive", "dead"];
class BoardGrid extends React.Component {
constructor(props) {
super(props);
//this.initBoardArray = this.initBoardArray.bind(this);
}
render() {
//this.initBoardArray();
let boardDOM = this.props.board.map(function(cell) {
return <BoardGridCell status={cell.status} id={cell.id} />;
});
return (
<div id="game-grid">
{boardDOM}
</div>
);
}
}
class BoardGridCell extends React.Component {
render() {
return (
<div
id={this.props.id}
className={`cell ${this.props.status}`}
data-status={this.props.status}
/>
);
}
}
function initBoard() {
for (let cellIndex = 0; cellIndex < cells; cellIndex++) {
let cell = { id: cellIndex, status: "dead" };
board[cellIndex] = cell;
}
}
function drawBoard() {
ReactDOM.render(
<BoardGrid board={board} />,
document.getElementById("game-grid-wrapper")
);
}
function clearBoard() {
for (let cellIndex = 0; cellIndex < cells; cellIndex++) {
let cell = { id: cellIndex, status: "dead" };
board[cellIndex] = cell;
}
}
$("#game-grid-wrapper").on("click", ".cell", function() {
let currentState = $(this).attr("data-status");
let currentStateIndex = states.indexOf(currentState);
let newState = states[(currentStateIndex + 1) % 2];
$(this).attr("class", `cell ${newState}`);
$(this).attr("data-status", newState);
});
$("#stop").on("click", function() {
alert("clearing");
clearBoard();
drawBoard();
});
initBoard();
drawBoard();
html,
body {
height: 100%;
text-align: center;
font-family: 'Open Sans', sans-serif;
}
h1,
h2 {
font-family: 'Press Start 2P', cursive;
}
.button {
width: 100px;
border: 1px solid #555;
padding: 5px;
margin: 5px;
cursor: pointer;
border-radius: 4px;
}
.button:hover {
opacity: 0.9;
}
#main {
margin: 10px;
}
#game-grid {
background-color: #000;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
width: 800px;
height: 500px;
overflow: hidden;
}
#game-grid .cell {
border: 1px solid #767676;
width: 10px;
height: 10px;
color: #fff;
font-size: 9px;
box-sizing: border-box;
}
.alive {
background-color: #2185d0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
<div id="game-actions">
<div id="start" class="button"><i class="fa fa-play"></i> Start</div>
<div id="pause" class="button"><i class="fa fa-pause"></i> Pause</div>
<div id="stop" class="button"><i class="fa fa-stop"></i> Stop</div>
</div>
<div id='game-grid-wrapper'></div>
</div>

You should not use jQuery together with React if you don't have to. Both manipulate the DOM but based on different information which can make them interfere in an unexpected way.
For your board state you should use the state of your BoardGrid component. Initialize your state in the constructor and add an onClick() callback to each cell when rendering it.
When the cell is clicked call the callback function given by the board component and pass it's id with it. Use that id to update the board state using setState() in your BoardGrid component.
I can add some example code later, if you struggle with anything.

Related

Function to Open and Close the Overlay using single button element

I would like to open and close overlay using single button, so when the button is clicked an additional class is added, when closed the class is removed and overlay is closed.
So far I wrote the code that opens overlay and add/remove the class to the button.
Also I've created the method to close the overlay but I'm struggling to create a proper event to actually close it, so I would be happy if anyone can guide me a bit.
I think there should be an 'if' statement within the events() checking if the button have added class, if so, the overlay will be closed using this function element.classList.contains("active");
Also the button is animated, so when class is added 3 bars (hamburger icon) becomes X and this is the main reason I don't want to have separate buttons to open and close, I already achieved that but this is not what I'm looking for.
class OverlayNav {
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayOpen())
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x")
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
injectHTML() {
document.body.insertAdjacentHTML('beforeend', `
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`)
}
}
export default OverlayNav
You can make a function with a if statement handle Opening and closing the overlay
Here is your code edited
class OverlayNav {
constructor() {
this.injectHTML();
this.hamburgerIcon = document.querySelector(".menu-icon");
this.events();
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayHandle());
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x");
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
overlayHandle() {
if (element.classList.contains("active")) {
this.overlayClose();
} else {
this.overlayOpen();
}
}
injectHTML() {
document.body.insertAdjacentHTML(
"beforeend",
`
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`
);
}
}
export default OverlayNav;
You can add a property that keeps track of the state of the nav bar.
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
this.overlayVisible=true;
}
Then add a method that toggles the state and calls the right open/close-method:
toggleOverlay() {
if (this.overlayVisible)
this.overlayOpen();
else
this.overlayClose();
this.overlayVisible=!this.overlayVisible;
}
Finally make the events method call toggleOverlay() instead of overlayOpen().
events() {
this.hamburgerIcon.addEventListener("click", () => this.toggleOverlay())
}
Alternativly, a pure HTML + CSS solution, using only the details element and the [open] CSS attribute selector.
.overlay > p {
padding: 1rem;
margin: 0;
display: flex;
flex-direction: column;
width: 25vw
}
.overlay summary {
padding: 1rem 0.5rem;
cursor: pointer;
max-height: 90vh;
overflow: auto;
font-size: 4em;
list-style: none;
}
.overlay[open] summary {
background: black;
color: white;
padding: 0.5rem;
font-size: 1em;
}
.overlay[open] {
position: fixed;
/* top: calc(50% - 25vw); */
left: calc(50% - 15vw);
outline: 5000px #00000090 solid;
border: 5px red solid;
border-radius: 0.5rem;
font-size: 1em
}
.overlay[open] summary::after {
content: '❌';
float: right;
}
<details class="overlay">
<summary>☰</summary>
<p>
Hello world!
</p>
</details>

Get modified element in HTML slot

I am trying to create a custom HTML component which creates tabs for every child element that it has. Here is the code for that custom component:
const code_window = document.getElementById("window");
class CodeWindow extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
});
this.shadowRoot.appendChild(code_window.content.cloneNode(true));
this.code = this.shadowRoot.getElementById("code");
this.tabbar = this.shadowRoot.getElementById("tabbar");
this.code.addEventListener("slotchange", e => {
var elems = e.target.assignedNodes();
for (let i = 0; i < elems.length; i++) {
var tab = document.createElement("div");
tab.classList.add("tab");
var name = elems[i].getAttribute("name");
tab.innerHTML = name;
tab.setAttribute("name", name);
this.tabbar.appendChild(tab);
if (elems[i].hasAttribute("active")) this.activateTab(name);
tab.addEventListener("click", e => this.activateTab(e.target.getAttribute("name")));
}
if (!this.previous_tab && this.tabbar.children.length > 0) this.activateTab(this.tabbar.children[0].getAttribute("name"));
});
}
activateTab(name) {
if (this.previous_tab) {
this.previous_frame.removeAttribute("active");
this.previous_tab.removeAttribute("active");
}
var tabs = this.tabbar.children,
frames = this.code.assignedElements();
for (let i = 0; i < tabs.length; i++) {
if (tabs[i].getAttribute("name") == name) {
tabs[i].setAttribute("active", "");
frames[i].setAttribute("active", "");
this.previous_frame = frames[i];
this.previous_tab = tabs[i];
break;
}
}
}
}
customElements.define("c-window", CodeWindow);
<template id="window">
<style>
:host {
display: flex;
flex-direction: column;
}
#titlebar {
display: flex;
background-color: green;
}
#tabbar {
flex-grow: 1;
display: flex;
}
#tabbar .tab {
display: flex;
align-items: center;
padding: 5px 10px;
background-color: #bbb;
}
#tabbar .tab[active] {
background-color: #fff;
}
#code {
flex-grow: 1;
display: block;
position: relative;
}
#code::slotted(*) {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 5px;
display: none;
}
#code::slotted(*[active]) {
display: block;
}
</style>
<div id="titlebar">
<div id="tabbar"></div>
</div>
<slot id="code" name="frames"></slot>
</template>
<c-window style="border: 2px solid green; height: 300px;">
<div slot="frames" name="Content 1">Contents of tab 1</div>
<div slot="frames" name="Content 2">Contents of tab 2</div>
</c-window>
There is a slot inside the custom component and I want that every time a child element is added, a tab should be added, if a child element is removed, the tab associated with that child should be removed.
But the slotchange event carries no information about which element has been added/removed, so currently, I have to loop through the slot.assignedElements() every time slotchange event is called and create tabs for each and every element. It means that for a particular child, duplicate tabs will be created, which can also be quite CPU intensive.
So, I was thinking if there is some way to get the information about the modified element so that the action can be performed on only the modified element. Is there any way of getting only the modified element? If not, what method can I apply for achieving this?
You are doing the oldskool Switch-DIVs approach for Tabs
You can do it all with one shadowDOM <slot ="active">
and one click handler
Needs some more work; but you get the slot="active" concept
<template id="TABS-MEISTER">
<style>
#bar { display:flex }
#bar div { width: 100px; background: lightgreen ; margin-right:1em ; cursor:pointer}
</style>
<div id="bar"></div>
<div style="clear:both"><slot name="active"></slot></div>
</template>
<tabs-meister>
<div title="Tab1">Tab #1</div>
<div slot="active" title="Tab2">Tab #2</div>
<div title="Tab3">Tab #3</div>
</tabs-meister>
<script>
customElements.define('tabs-meister', class extends HTMLElement {
constructor() {
let template = (id) => document.getElementById(id).content.cloneNode(true);
super()
.attachShadow({mode: 'open'})
.append(template(this.nodeName));
this.onclick = (evt) => {
if (this.activetab) this.activetab.removeAttribute("slot");
let tab = evt.composedPath()[0];
this.activetab = this.querySelector(`[title="${tab.title}"]`);
this.activetab.slot = 'active';
}
}
connectedCallback() {
let tabs = [...this.children].map(node => {
return `<div title=${node.title}>${node.title}</div>`;
}).join ``;
this.shadowRoot.querySelector("#bar").innerHTML = tabs;
}
});
</script>

addEventListenner on js : the bubble does not want to be canceled

I coded a game for 2 people:
Initialization is done with player1 ()
with the displayScore () function I can reiterate the score as much as I want (here everything is fine)
but when I switch to the other player with the changePlayer (player) function, the event goes well with player2 I can also reiterate but the concern is that the first player continues to play while my functions and variables are well partitioned
the problem comes from the event of the first which continues despite my stopPropagation () which I did not forget to report.
Where's the problem I've been on for a day and a half.
I join the two files html and js and also css
Thank you in advance,
cordially.
jeu de dés
<body>
<div id="launchGame">
<h1>NEW GAME</h1>
</div>
<div id="players">
<div id="player_1"><h2>PLAYER 1</h2><span id="score_player_1">Score :</span></div>
<picture><img id="dice">img</picture>
<div id="player_2"><h2>PLAYER 2</h2><span id="score_player_2">Score :</span></div>
</div>
<div id="current_score">
<div id="current_score_1"><p>current<span id="round_player_1"></span></p></div>
<div id="controls_game">
<h3 id="roll_dice"><img src="images/re_game.png">ROLL DICE</h3>
<h3 id="hold"><img src="images/charge_score.png">HOLD</h3>
</div>
<div id="current_score_2"><p>current<span id="round_player_2"></span></p></div>
</div>
</body>
(function(){
window.addEventListener("DOMContentLoaded", function() {
const launch = document.getElementById("launchGame");
const rollDice = document.getElementById("roll_dice");
function currentPlayer(player) {
const hold = document.getElementById("hold");
if (player.getElementsByTagName("img").length < 1) {
player.firstChild.style.position = "relative";
var currentPlayer = new Image();
currentPlayer.src = "images/current_player.png";
currentPlayer.setAttribute("class", player.getAttribute("id"));
currentPlayer.style.position = "absolute";
player.firstChild.appendChild(currentPlayer);
} else {
player.getElementsByTagName("img")[0].style.visibility = "visible";
}
changePlayer(player);
rollDice.addEventListener("click", function(e) {
e.stopPropagation();
displayScore(player);
}, {capture:false, once: false, useCapture: true});
}
function player1() {
let player1 = document.getElementById("player_1");
currentPlayer(player1);
}
function player2() {
let player2 = document.getElementById("player_2");
currentPlayer(player2);
}
function changePlayer(player) {
hold.addEventListener("click", function(e) {
e.stopPropagation();
player.getElementsByTagName("img")[0].style.visibility = "hidden";
if (player.getAttribute("id") === "player_1") {
console.log(player.getAttribute("id"));
player2();
} else if (player.getAttribute("id") === "player_2") {
console.log(player.getAttribute("id"));
player1();
}
}, {capture:false, once:true, useCapture: true});
}
function displayScore(player) {
let scoreDice = getScoreDice();
let scorePlayer = document.getElementById("round_" +
player.getAttribute("id"));
scorePlayer.textContent = scoreDice();
}
function getScoreDice() {
var result = 0;
var faceDice = Math.floor(Math.random()*7);
if ((faceDice > 0) && (faceDice < 7)) {
result = faceDice;
} else {
result = faceDice+1;
}
function innerGetScoreDice() {
return result;
}
return innerGetScoreDice;
}
launch.addEventListener("click", player1(), {capture:false, once:true});
});
})()
body {
margin: 0;
padding: 0;
background-color: silver;
background: linear-gradient(to left, white, white 50%, rgb(228, 227, 227) 50%, rgb(228, 227, 227));
text-align: center;
color: gray;
}
h1 {
width: 40%;
margin: 1% auto 5%;
font-size: 1.5rem;
}
h1::before {
content: "⊕ ";
font-size: 2rem;
align-items: center;
color: tomato;
}
h2 {
height: 30px;
line-height: 30px;
}
h3 {
margin: 7% 0;
}
span {
display: block;
margin-top: 50%;
}
img {
max-width: 10%;
vertical-align: middle;
margin-right: 3%;
}
#players, #final_score, #current_score, #current_score_1, #current_score_2, #controls_game {
display: flex;
}
#players, #final_score, #current_score {
flex-direction: row;
justify-content:space-evenly;
align-items: center;
}
#players {
justify-content:space-around;
margin: 1% 1% 10%;
}
#current_score_1, #current_score_2 {
border-radius: 20%;
padding: 2% 4%;
background-color: tomato;
color: white;
}
#controls_game {
flex-direction: column;
max-width: 33%;
}
In function changePlayer(player), isn't variable 'hold' undefined ?
No
The variable hold is defined and ok
All variables are ok
Problems is in event of first players
Bubble or capture
I don't now whats this

onclick change border colour of a html header permanently until another is clicked

Currently I have a number of clickable boxes that when I hover over them, they change colour. When I click and hold on a specific box, it changes colour.(by using :active in css.
Is there anyway I can make the colour of the border change permanently until a different box is clicked? E.G the same as the :active property except I don't have to keep the mouse held in?
My Code:
flight-viewer.html
<h3>Flights </h3>
<div>
<ul class= "grid grid-pad">
<a *ngFor="let flight of flights" class="col-1-4">
<li class ="module flight" (click)="selectFlight(flight)">
<h4>{{flight.number}}</h4>
</li>
</a>
</ul>
</div>
<div class="box" *ngIf="flightClicked">
<!--<flight-selected [flight]="selectedFlight"></flight-selected>-->
You have selected flight: {{selectedFlight.number}}<br>
From: {{selectedFlight.origin}}<br>
Leaving at: {{selectedFlight.departure || date }}<br>
Going to: {{selectedFlight.destination}}<br>
Arriving at: {{selectedFlight.arrival || date}}
</div>
flight-viewer.css:
h3 {
text-align: center;
margin-bottom: 0;
}
h4 {
position: relative;
}
ul {
width: 1600px;
overflow-x: scroll;
background: #ccc;
white-space: nowrap;
vertical-align: middle;
}
div
{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
}
li {
display: inline-block;
/* if you need ie7 support */
*display: inline;
zoom: 1
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607D8B;
border-radius: 2px;
}
.module:hover {
background-color: #EEE;
cursor: pointer;
color: #607d8b;
}
.module:active {
border: 5px solid #73AD21;
}
.box {
text-align: center;
margin-bottom: 0;
margin: auto;
width: 600px;
position:absolute;
top: 180px;
right: 0;
height: 100px;
border: 5px solid #73AD21;
text-align: center;
display: inline-block;
}
flight-viewer-component.ts:
import { Component } from '#angular/core';
import { FlightService } from './flight.service';
import { Flight } from './flight.model'
#Component({
selector: 'flight-viewer',
templateUrl: 'app/flight-viewer.html',
styleUrls: ['app/flight-viewer.css']
})
export class FlightViewerComponent {
name = 'FlightViewerComponent';
errorMessage = "";
stateValid = true;
flights: Flight[];
selectedFlight: Flight;
flightToUpdate: Flight;
flightClicked = false;
constructor(private service: FlightService) {
this.selectedFlight = null;
this.flightToUpdate = null;
this.fetchFlights();
}
flightSelected(selected: Flight) {
console.log("Setting selected flight to: ", selected.number);
this.selectedFlight = selected;
}
flightUpdating(selected: Flight) {
console.log("Setting updateable flight to: ", selected.number);
this.flightToUpdate = selected;
}
updateFlight(flight: Flight) {
let errorMsg = `Could not update flight ${flight.number}`;
this.service
.updateFlight(flight)
.subscribe(() => this.fetchFlights(),
() => this.raiseError(errorMsg));
}
selectFlight(selected: Flight) {
console.log("Just click on this flight ", selected.number, " for display");
this.flightClicked = true;
this.selectedFlight = selected;
}
private fetchFlights() {
this.selectedFlight = null;
this.flightToUpdate = null;
this.service
.fetchFlights()
.subscribe(flights => this.flights = flights,
() => this.raiseError("No flights found!"));
}
private raiseError(text: string): void {
this.stateValid = false;
this.errorMessage = text;
}
}
Thanks!
I'm quite sure that this has already been answered.
Make your DIVs focusable, by adding tabIndex:
<div tabindex="1">
Section 1
</div>
<div tabindex="2">
Section 2
</div>
<div tabindex="3">
Section 3
</div>
Then you can simple use :focus pseudo-class
div:focus {
background-color:red;
}
Demo: http://jsfiddle.net/mwbbcyja/
You can use the [ngClass] directive provided by angular to solve your problem.
<a *ngFor="let flight of flights" class="col-1-4">
<li [ngClass]="{'permanent-border': flight.id === selectedFlight?.id}" class ="module flight" (click)="selectFlight(flight)">
<h4>{{flight.number}}</h4>
</li>
</a>
This will add the css class permantent-border to the <li> element, if the id of the flight matches the id with the selectedFlight (Assuming you have an id proberty specified, or just use another proberty which is unique for the flight)

JavaScript Accordion Menu with querySelectorAll()

I'm attempting to build an accordion menu using querySelectorAll() but unsure what the best method would be to check if the clicked list item's children (.toggleContent and .toggleIcon) belong to it's clicked parent toggle_li[i].
Correct me if I am wrong, but I assume that controlling this within the onclick function will be more flexible than impacting the toggleDataAttr function?
I'm still new to querySelector so any guidance is appreciated.
codepen: http://codepen.io/seejaeger/pen/qdqxGy
// data attribute toggle
var toggleDataAttr = function (toggleElem, opt1, opt2, dataAttr) {
//
// ? belongs to clicked element (parent toggle_li[i])?
//
var toggleElem = document.querySelector(toggleElem);
toggleElem.setAttribute(dataAttr,
toggleElem.getAttribute(dataAttr) === opt1 ? opt2 : opt1);
};
// declare toggle onclick element
var toggle_li = document.querySelectorAll('li');
// iterate query and listen for clicks
for (var i = 0; i < toggle_li.length; i++) {
toggle_li[i].onclick = function() {
//
// ? belongs to clicked element (parent toggle_li[i])?
//
toggleDataAttr('.toggleContent', 'closed', 'open', 'data-state');
toggleDataAttr('.toggleIcon', 'plus', 'minus', 'data-icon');
};
}
Here is what I think you should do:
Update your toggleDataAttr function to receive one more parameter parentElem.
Use this new parentElem for querySelector instead of document inside toggleDataAttr.
And then in your loop, pass this as parameter to be used as parentElem.
Snippet:
var toggleDataAttr = function(parentElem, toggleElem, opt1, opt2, dataAttr) {
var toggleElem = parentElem.querySelector(toggleElem);
toggleElem.setAttribute(dataAttr, toggleElem.getAttribute(dataAttr) === opt1 ? opt2 : opt1);
};
var toggle_li = document.querySelectorAll('li');
for (var i = 0; i < toggle_li.length; i++) {
toggle_li[i].onclick = function() {
toggleDataAttr(this, '.toggleContent', 'closed', 'open', 'data-state');
toggleDataAttr(this, '.toggleIcon', 'plus', 'minus', 'data-icon');
};
}
body {
background: #034;
opacity: 0.9;
font-family: sans-serif;
font-size: 24px;
font-weight: 300;
letter-spacing: 2px;
}
ul {
list-style: none;
padding: 0 24px;
width: 30%;
overflow: hidden;
color: #333;
}
li {
background: #eee;
padding: 0px;
border-bottom: 2px solid #aaa;
}
i {
font-style: normal;
}
.li-label {
padding: 18px;
}
.toggleContent {
padding: 18px 14px;
border-top: 2px solid #bac;
background: #334;
color: #eee;
}
.toggleContent[data-state=closed] {
display: none;
}
.toggleContent[data-state=open] {
display: block;
}
.toggleIcon[data-icon=plus]:after {
content: '+';
float: right;
}
.toggleIcon[data-icon=minus]:after {
content: '-';
float: right;
}
<ul>
<li>
<div class="li-label">
list item one <i class="toggleIcon" data-icon="plus"></i>
</div>
<div class="toggleContent" data-state="closed">toggle content one</div>
</li>
<li>
<div class="li-label">
list item two <i class="toggleIcon" data-icon="plus"></i>
</div>
<div class="toggleContent" data-state="closed">toggle content two</div>
</li>
<li>
<div class="li-label">
list item three <i class="toggleIcon" data-icon="plus"></i>
</div>
<div class="toggleContent" data-state="closed">toggle content three</div>
</li>
</ul>
Hope it helps.

Categories

Resources