React dynamic, sortable and draggable grid - javascript

I have a task for React with JS ES6 which I have been trying to complete for the last two weeks. Examples and code are at the bottom of this post.
My goal is to make a “dynamically sortable grid”.
I read some material here, Googled and tried libraries like React Sortable (HOC), react-sortablejs, React Sortable, and many others. I have tried to rewrite one library, but without success.
Which library would you use in this case or what can you advice me to do in this situation?
On the input I have a JSON from an API like this:
{
"items":[
{
"sub_items":[
{
"id":1,
"name":"Item 1"
}
],
"type":"Type 1"
},
{
"sub_items":[
{
"id":2,
"name":"Item 2"
},
{
"id":3,
"name":"Item 3"
}
],
"type":"Type 2"
},
{
"sub_items":[
{
"id":4,
"name":"Item 4"
}
],
"type":"Type 3"
},
{
"sub_items":[
{
"id":5,
"name":"Item 5"
},
{
"id":6,
"name":"Item 6"
},
{
"id":7,
"name":"Item 7"
}
],
"type":"Type 4"
}
]
}
The JSON is saved in the React state and then accessed inside the render() method.
My code:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
App.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
// import { SortableItem, swapArrayPositions } from 'react-sort-list';
// import {SortableContainer, SortableElement} from 'react-sortable-hoc';
import { Container, Row, Col, Form, Nav, NavItem } from 'reactstrap';
import NewItem from "./NewItem";
import './App.css';
class App extends Component {
constructor(props){
super(props);
this.state = {
draggable: true,
items: [
{
"sub_items": [
{
"id": 1,
"name": "Item 1"
}
],
"type": "Type 1"
},
{
"sub_items": [
{
"id": 2,
"name": "Item 2"
},
{
"id": 3,
"name": "Item 3"
}
],
"type": "Type 2"
},
{
"sub_items": [
{
"id": 4,
"name": "Item 4"
}
],
"type": "Type 3"
},
{
"sub_items": [
{
"id": 5,
"name": "Item 5"
},
{
"id": 6,
"name": "Item 6"
},
{
"id": 7,
"name": "Item 7"
}
],
"type": "Type 4"
}
]
}
}
dragStart( event ){
var c = event.target.closest(".td");
var p = c.closest(".tr");
event.dataTransfer.setData("text", c.id);
c.style.width = "150px";
if ( p.children.length !== 3 ){
p.classList.remove("full_row")
}
}
dragEnter(event){
}
dragEnd(event){
var data = event.dataTransfer.getData("text");
var p = document.getElementById(data);
var p = event.target.closest(".tr");
if ( p.children.length === 0 ){
try{
p.remove();
}catch( err ){
console.log( err )
}
}else if ( p.children.length === 1 ){
p.classList.remove("full_row")
}else if ( p.children.length === 2 ){
p.classList.remove("full_row")
for ( var i=0, item; item=p.children[i]; i++ ){
item.classList.remove("row_1");
item.classList.remove("row_3");
item.classList.add("row_2");
}
}else if ( p.children.length === 3 ){
for ( var i=0, item; item=p.children[i]; i++ ){
item.classList.remove("row_1");
item.classList.remove("row_2");
item.classList.add("row_3");
}
}
event.target.style.width = null;
var empty_table_rows = document.getElementsByClassName("tr");
for (var i=0, item; item=empty_table_rows[i]; i++ ){
if ( item.children.length === 0 ){
try{
item.remove();
}catch( err ){
console.log( err );
}
}else{
switch ( item.children.length ){
case 1:
item.children[0].classList.remove("row_3");
item.children[0].classList.remove("row_2");
item.children[0].classList.add("row_1");
break;
case 2:
for (var j=0, child; child=item.children[j]; j++ ){
child.classList.remove("row_1");
child.classList.remove("row_3");
child.classList.add("row_2");
}
break;
case 3:
for (var j=0, child; child=item.children[j]; j++ ){
child.classList.remove("row_1");
child.classList.remove("row_2");
child.classList.add("row_3");
}
break;
}
}
}
var new_rows = document.getElementsByClassName("tr_for_drop");
for ( var i=0; i < new_rows.length; i++ ){
if ( new_rows[i].children.length === 1 || new_rows[i].children.length === 0 ){
new_rows[i].remove();
}
}
var new_items = document.getElementsByClassName("new_item");
for ( var i=0; i < new_items.length; i++ ){
var k = new_itms[i].parentNode;
var m = k.closest(".tr");
k.remove();
if ( m.children.length === 0 ){
m.remove();
}
}
}
dragOver( event ){
event.preventDefault();
event.target.closest(".td").classList.remove("row_1");
event.target.closest(".td").classList.remove("row_2");
event.target.closest(".td").classList.add("row_3");
}
tableRowDragOver( event ){
var _this = this;
console.log("TABLE ROW DRAG OVER")
var table_row = event.target.closest(".tr");
var child_number = table_row.children.length;
console.log(child_number);
switch( child_number ){
case 1:
var d = document.createElement("div");
ReactDOM.render(<NewItem
draggable={ _this.state.draggable }
dragStart={ _this.dragStart.bind( _this ) }
dragEnd={ _this.dragEnd.bind( _this ) }
dragOver={ _this.dragOver.bind( _this ) }
dragEnter={ _this.dragEnter.bind( _this ) }
/>, d)
table_row.appendChild(d)
}
}
drop( event ){
var trigger = event.target.closest(".tr").classList.contains("full_row");
if ( trigger ){
event.preventDefault();
return 0;
}else{
event.preventDefault();
var data = event.dataTransfer.getData("text");
var t = document.getElementById( data );
var p = event.target.closest(".tr");
p.appendChild(t);
if (p.children.length === 3){
p.classList.add("full_row")
for ( var i=0, item; item=p.children[i]; i++ ){
item.classList.remove("row_1");
item.classList.remove("row_2");
item.classList.add("row_3");
}
}else if ( p.children.length === 2 ){
for ( var i=0, item; item=p.children[i]; i++ ){
item.classList.remove("row_1");
item.classList.remove("row_3");
item.classList.add("row_2");
}
}
}
var empty_table_rows = document.getElementsByClassName("tr");
for (var i=0, item; item=empty_table_rows[i]; i++ ){
if ( item.children.length === 0 ){
try{
item.remove()
}catch( err ){
console.log( err )
}
}else{
switch ( item.children.length ){
case 1:
item.children[0].classList.remove("row_3");
item.children[0].classList.remove("row_2");
item.children[0].classList.add("row_1");
break;
case 2:
for (var j=0, child; child=item.children[j]; j++ ){
child.classList.remove("row_1");
child.classList.remove("row_3");
child.classList.add("row_2");
}
break;
case 3:
for (var j=0, child; child=item.children[j]; j++ ){
child.classList.remove("row_1");
child.classList.remove("row_2");
child.classList.add("row_3");
}
break;
}
}
}
var new_rows = document.getElementsByClassName("tr_for_drop");
for ( var i=0; i < new_rows.length; i++ ){
if ( new_rows[i].children.length === 1 || new_rows[i].children.length === 0 ){
new_rows[i].remove();
}
}
var new_items = document.getElementsByClassName("new_item");
for ( var i=0; i < new_items.length; i++ ){
var item = new_items[i];
var ch = item.closest(".tr_for_drop").children;
console.log(item.closest(".tr_for_drop"))
if ( ch.length === 1 ){
// item.closest(".tr_for_drop").remove();
}else{
item.closest(".tr_for_drop").classList.add("real_row");
item.closest(".tr_for_drop").classList.remove("tr_for_drop");
item.remove();
}
}
return false;
}
render() {
const Items = (data) => {
console.log(data.items.length)
switch (data.items.length) {
case 1:
return (
<div onDragOver={this.tableRowDragOver.bind(this)}
onDrop={this.drop.bind(this)}
className="tr real_row">
{
data.items.map((item, item_index) => {
return (
<div key={item_index}
onDragStart={this.dragStart.bind(this)}
onDragEnd={this.dragEnd.bind(this)}
onDragOver={this.dragOver.bind(this)}
onDragEnter={this.dragEnter.bind(this)}
draggable={this.state.draggable}
id={"item" + item.id}
className="disable_select td real_item item_cell row_1 ui-sortable itemParent"
data-name={item.name}
data-parent-row="1"
data-col={item_index + 1}>
<div className="command_navigate" style={{
opacity: "0.5",
position: "absolute",
transform: "rotateZ(90deg)",
left: "10px",
top: "10px"
}}>
<i className="ion-ios-more"/>
<i style={{marginTop: "-5px", position: "absolute", left: "0"}}
className="ion-ios-more"/>
</div>
<div data-type="draft_item" className="item_container disable_select">
{item.name}
</div>
</div>
)
})
}
</div>
);
case 2:
return (
<div onDragOver={this.tableRowDragOver.bind(this)}
onDrop={this.drop.bind(this)}
className="tr real_row">
{
data.items.map((item, item_index) => {
return (
<div key={item_index}
onDragStart={this.dragStart.bind(this)}
onDragEnd={this.dragEnd.bind(this)}
onDragOver={this.dragOver.bind(this)}
onDragEnter={this.dragEnter.bind(this)}
draggable={this.state.draggable}
id={"item_" + item.id}
className="disable_select td real_item item_cell row_2 ui-sortable itemParent"
data-id={item.id}
data-name={item.name}
data-col={item_index + 1}>
<div className="item_navigate" style={{
opacity: "0.5",
position: "absolute",
transform: "rotateZ(90deg)",
left: "10px",
top: "10px"
}}>
<i className="ion-ios-more"/>
<i style={{marginTop: "-5px", position: "absolute", left: "0"}}
className="ion-ios-more"/>
</div>
<div data-type="draft_item" className="item_container">
{item.name}
</div>
</div>
)
})
}
</div>
);
case 3:
return (
<div onDragOver={this.tableRowDragOver.bind(this)}
onDrop={this.drop.bind(this)}
className="tr real_row full_row">
{
data.items.map((item, item_index) => {
return (
<div key={item_index}
onDragStart={this.dragStart.bind(this)}
onDragEnd={this.dragEnd.bind(this)}
onDragOver={this.dragOver.bind(this)}
onDragEnter={this.dragEnter.bind(this)}
draggable={this.state.draggable}
id={"item_" + item.id}
className="disable_select td real_item item_cell row_3 ui-sortable itemParent" data-id={item.id}
data-name={item.name}
data-parent-row="1"
data-col={item_index + 1}>
<div className="item_navigate" style={{
opacity: "0.5",
position: "absolute",
transform: "rotateZ(90deg)",
left: "10px",
top: "10px"
}}>
<i className="ion-ios-more"/>
<i style={{marginTop: "-5px", position: "absolute", left: "0"}}
className="ion-ios-more"/>
</div>
<div data-type="draft_item" className="item_container">
{item.name}
</div>
</div>
)
})
}
</div>
)
}
};
return (
<div>
<div className="table ui-sortable">
<div className="tbody">
{
this.state.items.map((item, index) => {
return (
<Items key={ index }
row={ index + 1 }
length={ item.length }
items={ item.sub_items }/>
)
})
}
</div>
</div>
<div className="row_1 item_creator">
Add new item
</div>
</div>
)
}
}
export default App;
NewItem.js
import React, { Component } from 'react';
import './App.css';
export default class NewItem extends Component{
render(){
return(
<div draggable={ this.props.draggable }
onDragStart={ this.props.dragStart }
onDragEnd={ this.props.dragEnd }
onDragOver={ this.props.dragOver }
onDragEnter={ this.props.dragEnter }
className="td new_item">
<div className="item_navigate" style={{ opacity: "0.5", position: "absolute", transform: "rotateZ(90deg)", left: "10px", top: "10px"}}>
<i className="ion-ios-more" />
<i style={{ marginTop: "-5px", position: "absolute", left: "0"}} className="ion-ios-more" />
</div>
<div data-type="draft_item" className="item_container">
DROP HERE
</div>
</div>
)
}
}
App.css
.tr{
margin: 10px 0;
width: 100%;
float: left;
}
.td{
cursor: pointer;
position: relative;
float: left;
border-radius: 5px;
}
.row_1{
width: 465px;
}
.item_creator{
text-align: center;
float: left;
width: 465px;
padding: 10px 0;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
cursor: pointer;
border:dashed 1px grey;
}
.row_2{
float: left;
width: 230px;
margin-right: 10px;
}
.row_2:nth-child(2){
margin-right: 0;
}
.row_3{
float: left;
width: 150px !important;
margin-right: 10px;
max-height: 40px;
overflow: hidden;
white-space: pre-wrap;
text-overflow: ellipsis;
line-height: 1.7;
box-shadow: 0 0 10px -2px rgba(42, 42, 42, 0.3);
}
.row_3:nth-child(3){
margin-right: 0;
}
.item_container{
background: #fff;
padding: 10px 15px;
border-radius: 5px;
text-align: center;
box-shadow: 0 0 10px -2px rgba(42, 42, 42, 0.3);
}
As a result it should look like this:
Example 1:
When I get item 2 and put it on the line between two instances (between random lines, or when I have to put it on the top line (above line with item 1)). After sorting Item 3 MUST change it’s width to 100% of the block (I change the class from _row_3_ to _row_1_). Item 1 will have an attribute of width: 100% and you can see the result above. At the end of the operation all items with the label "DROP HERE" must be removed.
Example 2:
If I drag item 4 and try to drop (drag element over the other line) it on the line with 2 elements, they must change their widths to ~50% (_row_2_ class) until I drop the dragged element. All elements on this line have to be ~33% width. At the end of the operation all items with the label "DROP HERE" must be removed.
Also, If I want to get an item from a line with 3 items and drop it on the other line, elements have to change their size depending on the amount of the items.
And how would I handle switching elements between columns of lines (from one column on one line to another column on another line)?
Lines have a minimum of 1 and a maximum of 3 elements.
The number of lines is dynamic and thus unlimited.

Related

Show a returned object on a div when button is clicked using React

I made a tic tac toe game. It works fine but the player's name are static.
I have a form with two player's name as an text field which sets state values when typed something.
I am having a problem to render the game when button is clicked.
classNames :
Board-> contains the tic tac toe game
Game -> contains Board class with extra divs for "turns" "player names"
StartGame -> contains the form
I have written the following code to get the tic tac toe game div when button is clicked.
<label> Player 1: </label>
<input
type="text"
onChange={() => this.setState({ p1: event.target.value })}
defaultValue={this.state.p1}
placeholder="Player 1 Name"
/>
<br /> <br />
<label> Player 2: </label>
<input
type="text"
onChange={() => this.setState({ p2: event.target.value })}
defaultValue={this.state.p2}
placeholder="Player 2 Name"
/>
<br /> <br />
<input
type="button"
value="Start New Game"
onClick={() => {
this.renderGame(this.state.p1, this.state.p2);
}}
/>
Code for whole project is:
https://codepen.io/damodar-bhattarai/pen/YzKWREN?editors=0010
I want to display the tic tac toe game only when form is filled and button is clicked.
update:
Code for renderGame function
renderGame(p11, p22){
if (p11 && p22) {
return <Game p1={p11} p2={p22} />;
} else {
return "error";
}
};
Final Update
Game Working link with restart new game:
https://codepen.io/damodar-bhattarai/pen/zYOBexp?editors=0010
The simplest solution will be to use a flag to render the component and toggle the flag when the user clicks on the 'Start new Game' button.
Hope the below solution helps. Please reach out to me if you need any clarification.
function Square(props) {
return ( <
button className = "square"
onClick = {
props.onClick
} > {
props.value
} <
/button>
);
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
clicks: 0,
p1: props.p1,
p2: props.p2,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
let count = this.state.clicks;
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
clicks: ++count
});
}
renderSquare(i) {
return <Square
value = {
this.state.squares[i]
}
onClick = {
() => this.handleClick(i)
}
/>;
}
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X(' + this.state.p1 + ')' : 'O(' + this.state.p2 + ')');
}
let countclick;
countclick = this.state.clicks;
return ( <
div >
<
p > No.of Clicks: {
countclick
} < /p> <
div className = "status" > {
status
} < /div> <
div className = "board-row" > {
this.renderSquare(0)
} {
this.renderSquare(1)
} {
this.renderSquare(2)
} <
/div> <
div className = "board-row" > {
this.renderSquare(3)
} {
this.renderSquare(4)
} {
this.renderSquare(5)
} <
/div> <
div className = "board-row" > {
this.renderSquare(6)
} {
this.renderSquare(7)
} {
this.renderSquare(8)
} <
/div> < /
div >
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
p1: props.p1,
p2: props.p2,
};
}
render() {
return ( <
div className = "game" >
<
div className = "game-board" >
<
Board p1 = {
this.state.p1
}
p2 = {
this.state.p2
}
/> < /
div >
<
/div>
);
}
}
class StartGame extends React.Component {
constructor(props) {
super(props);
this.state = {
p1: '',
p2: '',
showGame: false
};
}
renderGame(p11, p22) {
debugger;
if (p11 && p22) {
this.setState({
showGame: true
});
}
}
render() {
return ( <
div className = "game-info" >
<
label > Player 1: < /label> <
input type = "text"
onChange = {
() => this.setState({
p1: event.target.value
})
}
defaultValue = {
this.state.p1
}
placeholder = "Player 1 Name" / >
<
br / > < br / >
<
label > Player 2: < /label> <
input type = "text"
onChange = {
() => this.setState({
p2: event.target.value
})
}
defaultValue = {
this.state.p2
}
placeholder = "Player 2 Name" / >
<
br / > < br / >
<
input type = "button"
value = "Start New Game"
onClick = {
() => {
this.renderGame(this.state.p1, this.state.p2);
}
}
/>
{
this.state.showGame && < Game
p1 = {
this.state.p1
}
p2 = {
this.state.p2
}
/>} < /
div >
);
}
}
// ========================================
ReactDOM.render( <
StartGame / > ,
document.getElementById('root')
);
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol,
ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="errors" style="
background: #c00;
color: #fff;
display: none;
margin: -20px -20px 20px;
padding: 20px;
white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
document.body.classList.add('mouse-navigation');
document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
if (e.keyCode === 9) {
document.body.classList.add('kbd-navigation');
document.body.classList.remove('mouse-navigation');
}
});
window.addEventListener('click', function(e) {
if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
e.preventDefault();
}
});
window.onerror = function(message, source, line, col, error) {
var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
errors.textContent += text + '\n';
errors.style.display = '';
};
console.error = (function(old) {
return function error() {
errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
errors.style.display = '';
old.apply(this, arguments);
}
})(console.error);
</script>
it's easier to do this using a boolean to determine if you should render a component. in render game just set a variable to true on the state if both names are filled out.
class StartGame extends React.Component {
constructor(props){
super(props);
this.state={
p1: '',
p2:'',
};
// so this will refer to the component and not the function
this.renderGame = this.renderGame.bind(this);
}
renderGame(){
// if p1 and p2 then renderGame should be true and clear any error messages.
if (this.state.p1 && this.state.p2) {
this.setState({ renderGame: true, error: '' })
// else tell the user they need to enter player names.
} else {
this.setState({ error: 'Please enter player names.' })
}
}
render() {
return (
<div className="game-info">
{this.state.error}
<br/>
<label> Player 1: </label>
<input type="text"
onChange={() => this.setState({ p1: event.target.value })}
defaultValue={this.state.p1}
placeholder="Player 1 Name" />
<br /> <br />
<label> Player 2: </label>
<input type="text"
onChange={() => this.setState({p2:event.target.value})}
defaultValue={this.state.p2}
placeholder="Player 2 Name" />
<br /> <br />
<input type="button" value="Start New Game" onClick={this.renderGame}/>
// if the boolean is true then go ahead and render the game component
{this.state.renderGame && <Game p1={this.state.p1} p2={this.state.p2}/>}
</div>
);
}
}
function Square(props) {
return ( <
button className = "square"
onClick = {
props.onClick
} > {
props.value
} <
/button>
);
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
clicks: 0,
p1: props.p1,
p2: props.p2,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
let count = this.state.clicks;
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
clicks: ++count
});
}
renderSquare(i) {
return <Square
value = {
this.state.squares[i]
}
onClick = {
() => this.handleClick(i)
}
/>;
}
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X(' + this.state.p1 + ')' : 'O(' + this.state.p2 + ')');
}
let countclick;
countclick = this.state.clicks;
return ( <
div >
<
p > No.of Clicks: {
countclick
} < /p> <
div className = "status" > {
status
} < /div> <
div className = "board-row" > {
this.renderSquare(0)
} {
this.renderSquare(1)
} {
this.renderSquare(2)
} <
/div> <
div className = "board-row" > {
this.renderSquare(3)
} {
this.renderSquare(4)
} {
this.renderSquare(5)
} <
/div> <
div className = "board-row" > {
this.renderSquare(6)
} {
this.renderSquare(7)
} {
this.renderSquare(8)
} <
/div> < /
div >
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
p1: props.p1,
p2: props.p2,
};
}
render() {
return ( <
div className = "game" >
<
div className = "game-board" >
<
Board p1 = {
this.state.p1
}
p2 = {
this.state.p2
}
/> < /
div >
<
/div>
);
}
}
class StartGame extends React.Component {
constructor(props) {
super(props);
this.state = {
p1: '',
p2: '',
};
// so this will refer to the component and not the function
this.renderGame = this.renderGame.bind(this);
}
renderGame() {
// if p1 and p2 then renderGame should be true and clear any error messages.
if (this.state.p1 && this.state.p2) {
this.setState({
renderGame: true,
error: ''
})
// else tell the user they need to enter player names.
} else {
this.setState({
error: 'Please enter player names.'
})
}
}
render() {
return ( <
div className = "game-info" > {
this.state.error
} <
br / >
<
label > Player 1: < /label> <
input type = "text"
onChange = {
() => this.setState({
p1: event.target.value
})
}
defaultValue = {
this.state.p1
}
placeholder = "Player 1 Name" / >
<
br / > < br / >
<
label > Player 2: < /label> <
input type = "text"
onChange = {
() => this.setState({
p2: event.target.value
})
}
defaultValue = {
this.state.p2
}
placeholder = "Player 2 Name" / >
<
br / > < br / >
<
input type = "button"
value = "Start New Game"
onClick = {
this.renderGame
}
/>
{
this.state.renderGame && < Game p1 = {
this.state.p1
}
p2 = {
this.state.p2
}
/>} < /
div >
);
}
}
// ========================================
ReactDOM.render( <
StartGame / > ,
document.getElementById('root')
);
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol,
ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="errors" style="
background: #c00;
color: #fff;
display: none;
margin: -20px -20px 20px;
padding: 20px;
white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
document.body.classList.add('mouse-navigation');
document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
if (e.keyCode === 9) {
document.body.classList.add('kbd-navigation');
document.body.classList.remove('mouse-navigation');
}
});
window.addEventListener('click', function(e) {
if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
e.preventDefault();
}
});
window.onerror = function(message, source, line, col, error) {
var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
errors.textContent += text + '\n';
errors.style.display = '';
};
console.error = (function(old) {
return function error() {
errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
errors.style.display = '';
old.apply(this, arguments);
}
})(console.error);
</script>
I forked your codepen and made the changes.
https://codepen.io/therj/pen/yLBJZJb
constructor(props){
super(props);
this.state={
p1: '',
p2:'',
player_set: false
};
Introduced a new state player_set.
You were handling onChange inline, I cloned it locally and React threw global-event error. Fixed by creating a handleChange method.
const handleChange = (event) => {
this.setState(
{...this.state,[event.target.name]: event.target.value,
}, ()=>{
this.setState({...this.state, 'player_set': this.state.p1 && this.state.p2})
})
}
I added name = "p1" and name = "p2", that's how single onChange will set state for both. I changed player_set state in callback tp setState, setting player_set and p1/p2 together might cause issues (I can't confirm, someone can comment on it, maybe?!).
Finally, in the StartGame:
{this.state.player_set &&
<Game
p1={this.state.p1}
p2={this.state.p2}
/>
}
This will render Game if player_set state is true!
You can use those states for player names as well.

Graphical representation (Tree-View) of a JavaScript object?

I have a problem I can't solve on my own.
I have a JavaScript object with so-called GROUPS. Each group can have either one or more subgroups or also so-called systems.
My task now is to display this structure graphically - a kind of tree view (colored DIV elements).
But I don't have any idea how to read this object in order to build it up graphically.
My Object:
const test = {
grp: [
{
groupID: 'group-1',
grp : [
{
groupID: 'group-2',
grp: [
{
groupID: 'group-3',
sys: ['sys1','sys2','sys3']
},
{
groupID: 'group-4',
grp: [
{
groupID: 'group-5',
sys: ['sys4','sys5','sys6']
},
{
groupID: 'group-6',
grp: [
{
groupID: 'group-7',
sys: ['sys7','sys8','sys9']
}
]
}
]
}
],
sys: ['sys0']
}
]
}
]
};
Here is a graphical example:
https://pic-hoster.net/view/69453/grp-sys.jpg.htm
I very much hope that someone here can help me.
How would you approach this task?
Graphical representation (Tree-View) of a JavaScript object
The important thing here is to identify your data structure so that you can attack the problem recursively.
In your case your groups can be defined like this:
{
groupID: string,
sys?: string[],
grp?: IGroup[]
}
(Where IGroup defined the above structure)
All groups has a groupID, some have sys and some have grp children.
From here we can define a function where the logic is as the following:
Generate a two-row structure for the current group.
For each sys child element (if they exists), add a cell to the top row with the sys value.
For each grp child element (if they exists), call this function and append the returning structure to a cell in the top row.
Insert a cell into the bottom row at the child-element colspan width. Set cell content to the current grp groupID
Return the two-row structure element, either feeding into the recursive build process or as a final result.
Here is a rough implementation of the above points:
function groupToHTML(grp) {
//Table to append to and return
var container = document.createElement("table");
container.border = "1";
container.style.borderCollapse = "collapse";
//Insert row to children of this node
var childRow = container.appendChild(document.createElement("tr"));
//Append all "sys" elements as cells
if (grp.sys !== void 0) {
for (var sysIndex = 0; sysIndex < grp.sys.length; sysIndex++) {
var sys = grp.sys[sysIndex];
var sysCell = childRow.appendChild(document.createElement("td"));
sysCell.innerHTML = sys;
sysCell.style.backgroundColor = "red";
sysCell.style.verticalAlign = "bottom";
}
}
//Append all "grp" children by calling "groupToHTML" on them and appending the returning table
if (grp.grp !== void 0) {
for (var grpIndex = 0; grpIndex < grp.grp.length; grpIndex++) {
var child = grp.grp[grpIndex];
var grpCell = childRow.appendChild(document.createElement("td"));
grpCell.appendChild(groupToHTML(child));
grpCell.style.verticalAlign = "bottom";
}
}
//Add a row and cell for "this" grp
var thisRow = container.appendChild(document.createElement("tr"));
var thisCell = thisRow.appendChild(document.createElement("th"));
thisCell.innerHTML = grp.groupID;
thisCell.style.textAlign = "center";
//Set cell colspan to number of child elements
thisCell.colSpan = Math.max(1, (grp.grp !== void 0 ? grp.grp.length : 0) + (grp.sys !== void 0 ? grp.sys.length : 0));
//Return table
return container;
}
//TEST
//testdata
var data = {
groupID: 'group-1',
grp: [
{
groupID: 'group-2',
grp: [
{
groupID: 'group-3',
sys: ['sys1', 'sys2', 'sys3']
}, {
groupID: 'group-4',
grp: [
{
groupID: 'group-5',
sys: ['sys4', 'sys5', 'sys6']
}, {
groupID: 'group-6',
grp: [
{
groupID: 'group-7',
sys: ['sys7', 'sys8', 'sys9']
}
]
}
]
}
],
sys: ['sys0']
}
]
};
//Initiate
var node = groupToHTML(data);
//Append
document.body.appendChild(node);
You could use recursive function to create nested levels for each grp and its systems. So each level will have name and children elements. Children elements will be nested groups and systems.
Pure javascript solution
const test = {"grp":[{"groupID":"group-1","grp":[{"groupID":"group-2","grp":[{"groupID":"group-3","sys":["sys1","sys2","sys3"]},{"groupID":"group-4","grp":[{"groupID":"group-5","sys":["sys4","sys5","sys6"]},{"groupID":"group-6","grp":[{"groupID":"group-7","sys":["sys7","sys8","sys9"]}]}]}],"sys":["sys0"]}]}]}
function tree(data, parent) {
if(data.grp) {
data.grp.forEach(obj => {
const child = document.createElement('div')
child.className = 'child'
const children = document.createElement('div')
children.className = 'children'
if(obj.groupID) {
const name = document.createElement('div');
name.className = 'name'
name.textContent = obj.groupID
child.appendChild(name)
}
if(obj.sys) {
const system = document.createElement('div')
system.className = 'system';
obj.sys.forEach(s => {
const sys = document.createElement('div')
sys.className = 'item'
sys.textContent = s
system.appendChild(sys)
})
children.appendChild(system)
}
child.appendChild(children)
parent.appendChild(child)
tree(obj, children)
})
}
}
const root = document.body.querySelector('#root')
tree(test, root)
#root * {
color: white;
}
.system {
background: #E00022;
display: flex;
flex-direction: column-reverse;
padding: 10px;
}
.name {
background: #595959;
padding: 10px;
}
.child {
display: flex;
flex-direction: column-reverse;
}
.children {
display: flex;
align-items: flex-end;
}
.children > div {
flex: 1;
border-bottom: 1px solid white;
}
<div id="root"></div>
React solution
const {Component} = React;
const data = {"grp":[{"groupID":"group-1","grp":[{"groupID":"group-2","grp":[{"groupID":"group-3","sys":["sys1","sys2","sys3"]},{"groupID":"group-4","grp":[{"groupID":"group-5","sys":["sys4","sys5","sys6"]},{"groupID":"group-6","grp":[{"groupID":"group-7","sys":["sys7","sys8","sys9"]}]}]}],"sys":["sys0"]}]}]}
class Systems extends Component {
render() {
const { data } = this.props;
return <div className="systems">
{data.map((sys, i) => (
<div key={i} className="system">{sys}</div>
))}
</div>
}
}
class Group extends Component {
render() {
const { data } = this.props;
return data.map((group, i) => (
<div key={i} className="group">
{group.groupID && <div className="group-name">{group.groupID}</div>}
<div className="children">
{group.sys && <Systems data={group.sys} />}
{group.grp && <Group data={group.grp} />}
</div>
</div>
))
}
}
class App extends Component {
state = {
data: {}
}
componentWillMount = () => {
this.setState({ data: this.props.data })
}
render() {
console.log(this.state)
return <div className="root">
<Group data={this.state.data.grp} />
</div>
}
}
ReactDOM.render(
<App data={data} />,
document.getElementById('container')
);
#root * {
color: white;
}
.group {
display: flex;
flex-direction: column-reverse;
}
.group-name {
background: rgb(89, 89, 89);
padding: 10px;
color: white;
border-top: 1px solid white;
}
.group-name {
opacity: 0.85;
transition: all 0.25s;
}
.children {
display: flex;
}
.children > * {
flex: 1;
}
.systems {
display: flex;
flex-direction: column-reverse;
}
.system {
background: red;
color: white;
padding: 10px;
opacity: 0.6;
transition: all 0.25s;
border-top: 1px solid white;
}
.system:hover,
.group-name:hover{
opacity: 1;
}
.as-console-wrapper {
display: none !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container"></div>

Allow user to rate from 0.5 to 5

I have made a rating component where user can rate but there is a problem. The user can rate from 0 to 4.5(0, 0.5, 1, 1.5 till 4.5) which is unexpected behavior, instead, I want the user to rate from 0.5 till 5. How do i make it so? Here is the component for star rating
Here is the workaround
https://codesandbox.io/s/9y14x3704
class Rating extends React.Component {
constructor(props) {
super(props);
this.state = {
rating: props.defaultRating || null,
maxRating: props.maxRating || null,
temp_rating: null
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.defaultRating !== this.props.defaultRating) {
this.setState({
rating: nextProps.defaultRating,
maxRating: nextProps.maxRating
});
}
}
handleMouseover(rating) {
this.setState(prev => ({
rating: rating / 2,
temp_rating: prev.rating
}));
}
handleMouseout() {
// this.state.rating = this.state.temp_rating;
// this.setState({ rating: this.state.rating });
this.setState(prev => ({
rating: prev.temp_rating
}));
}
rate(rating) {
this.setState(
{
rating: rating / 2,
temp_rating: rating / 2
},
() => this.props.handleRate(this.state.rating)
);
}
render() {
const { rating } = this.state;
let stars = [];
for (let i = 0; i < 11; i++) {
let klass = "icon-star-o";
if (this.state.rating >= i / 2 && this.state.rating !== null) {
klass = "icon-star";
}
stars.push(
<i
style={{
display: "inline-block",
width: "10px",
overflow: "hidden",
direction: i % 2 === 0 ? "ltr" : "rtl"
}}
className={klass}
key={i}
onMouseOver={() => this.handleMouseover(i)}
onClick={() => this.rate(i)}
onMouseOut={() => this.handleMouseout()}
/>
);
}
return <div className="rating">{stars}</div>;
}
}
const props = {
defaultRating: 2,
maxRating: 5,
handleRate: (...args) => {
console.log(args)
}
}
ReactDOM.render(<Rating {...props} />, document.querySelector('.content'))
.rating{
border: 1px solid gray
padding: 5px;
}
i[class^='icon-star'] {
border: 1px solid black;
margin: 4px;
padding: 5px;
height: 10px;
}
.icon-star {
background: gray;
}
.icon-star-o {
background: yellow;
}
<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>
<div class='content'></div>
Can anyone help me at this, please?
Ok, Have a look at updated codebox
I've changed the count to be UNTIL 10 and reversed the stars
Here's the updated render() method:
render() {
const { rating } = this.state;
let stars = [];
for (let i = 1; i <= 10; i++) { /* note starting at one UNTIL 10 */
let klass = "fa fa-star-o";
if (rating >= i / 2 && rating !== null) {
klass = "fa fa-star";
}
stars.push(
<i
style={{
display: "inline-block",
width: "8px",
overflow: "hidden",
direction: i % 2 ? "ltr" : "rtl" /* reverse the half stars */
}}
className={klass}
key={i}
onMouseOver={() => this.handleMouseover(i)}
onClick={() => this.rate(i)}
onMouseOut={() => this.handleMouseout()}
/>
);
}
return <div className="rating">
{stars}<br />
{rating}
</div>;
}

Component not rerendering - ReactJS

I have something similar to a notes app, and want to be able to drag and drop cards from one group to another (by using react-dnd). Naturally, after a card is dropped, I want to remove it from the source group and add it to the target group. Removing works fine, but the card is not being rendered in the target group. Here is the relevant code:
App = React.createClass({
getInitialState: function() {
...
return {
appState: appState
}
}
removeCard: function(card) {
var content = this.state.appState[card.groupId].content;
content.splice(content.indexOf(card), 1);
this.setState({ appState: this.state.appState });
},
addCard: function(card, target) {
var content = this.state.appState[target.groupId].content;
content.splice(content.indexOf(target) + 1, 0, card);
this.setState({ appState: this.state.appState });
},
onCardDrop: function(source, target) {
this.addCard(source, target); // didn't work
this.removeCard(source); // worked
},
render: function() {
var that = this;
var appState = this.state.appState;
return (
<div>
{_.map(appState, function(group) {
return (
<Group
key={group.id}
id={group.id}
group={group}
onCardDrop={that.onCardDrop} />
)
})}
</div>
)
}
});
So, the card is removed from the source group, but it never appears in the target group even though the console.log of the target group shows the card is there. Is it possible that for some reason the component is not rerendering.
The Group and Card components are rendering ul and li respectively.
I took some time to make a working example based on the code you provided... but it did work. No problems in the code you provided. This indicates that the problem lies elsewhere in your code.
I cannot give you a complete answer because the snippet you provided does not follow the Minimal, Complete, and Verifiable example rule. Though it is minimal, it's incomplete, and also not verifiable.
What I can do is paste the whole code that I made here and hope that it will be useful to you.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script src="https://npmcdn.com/react-dnd-html5-backend#2.1.2/dist/ReactDnDHTML5Backend.min.js"></script>
<script src="https://npmcdn.com/react-dnd#2.1.0/dist/ReactDnD.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
<style>
ul {
display: inline-block;
padding: 10px;
width: 100px;
border: 1px solid gray;
vertical-align: top;
}
li {
display: block;
padding: 0;
width: 100px;
text-align: center;
box-sizing: border-box;
position: relative;
}
li.group {
}
li.card {
height: 100px;
border: 1px solid black;
line-height: 100px;
margin-top: 5px;
font-size: 25px;
font-weight: bold;
cursor: move;
}
li > span {
vertical-align: middle;
display: inline-block;
}
</style>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
window.ItemTypes = {
CARD: "card",
GROUP_TITLE: "group-title"
};
</script>
<script type="text/babel">
var cardSource = {
beginDrag: function (props) {
return { cardId: props.id, groupId: props.groupId, card: props.card };
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}
}
var cardTarget = {
drop: function (props, monitor) {
var item = monitor.getItem();
console.log(item.card)
console.log(props.card)
props.onCardDrop(item.card, props.card);
},
canDrop: function (props, monitor) {
var item = monitor.getItem();
return item.cardId != props.id;
}
};
function collectTgt(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
};
}
window.Card = React.createClass({
propTypes: {
connectDragSource: React.PropTypes.func.isRequired,
isDragging: React.PropTypes.bool.isRequired,
isOver: React.PropTypes.bool.isRequired,
canDrop: React.PropTypes.bool.isRequired
},
renderOverlay: function (color) {
return (
<div style={{
position: 'absolute',
top: 0,
left: 0,
height: '100%',
width: '100%',
zIndex: 1,
opacity: 0.5,
backgroundColor: color,
}} />
);
},
render: function() {
var connectDragSource = this.props.connectDragSource;
var isDragging = this.props.isDragging;
var connectDropTarget = this.props.connectDropTarget;
var isOver = this.props.isOver;
var canDrop = this.props.canDrop;
return connectDropTarget(connectDragSource(
<li className="card" style={{opacity: isDragging ? 0.5 : 1}}
><span>{this.props.card.name}-{this.props.card.groupId}</span>
{isOver && !canDrop && this.renderOverlay('red')}
{!isOver && canDrop && this.renderOverlay('yellow')}
{isOver && canDrop && this.renderOverlay('green')}
</li>
));
}
});
window.Card = ReactDnD.DragSource(ItemTypes.CARD, cardSource, collect)(window.Card);
window.Card = ReactDnD.DropTarget(ItemTypes.CARD, cardTarget, collectTgt)(window.Card);
</script>
<script type="text/babel">
window.Group = React.createClass({
render: function() {
console.log(this.props.group)
var that = this;
return (
<ul>
<li className="group">Group #{this.props.group.id}</li>
{_.map(this.props.group.content, function(card) {
return (
<Card
key={card.name}
id={card.name}
groupId={card.groupId}
card={card}
onCardDrop={that.props.onCardDrop}
/>
)
})}
</ul>
);
}
});
</script>
<script type="text/babel">
window.App = React.createClass({
getInitialState: function() {
return {
appState: [
{
id: 0,
content: [
{
groupId: 0,
name: "C1"
},
{
groupId: 0,
name: "C2"
},
{
groupId: 0,
name: "C3"
},
{
groupId: 0,
name: "C4"
}
]
},
{
id: 1,
content: [
{
groupId: 1,
name: "C5"
},
{
groupId: 1,
name: "C6"
},
{
groupId: 1,
name: "C7"
},
{
groupId: 1,
name: "C8"
}
]
}
]
};
},
removeCard: function(card) {
var content = this.state.appState[card.groupId].content;
content.splice(content.indexOf(card), 1);
this.setState({ appState: this.state.appState });
},
addCard: function(card, target) {
var content = this.state.appState[target.groupId].content;
content.splice(content.indexOf(target) + 1, 0, card);
card.groupId = target.groupId;
this.setState({ appState: this.state.appState });
},
onCardDrop: function(source, target) {
this.removeCard(source); // worked
this.addCard(source, target); // worked
},
render: function() {
var that = this;
var appState = this.state.appState;
return (
<div>
{_.map(appState, function(group) {
return (
<Group
key={group.id}
id={group.id}
group={group}
onCardDrop={that.onCardDrop}
/>
)
})}
</div>
)
}
});
window.App = ReactDnD.DragDropContext(ReactDnDHTML5Backend)(window.App);
</script>
<script type="text/babel">
ReactDOM.render(
<App />,
document.getElementById('example')
);
</script>
</body>
</html>

ReactJS - put overflown elements into a "..." dropdown button

I have following UI element on the top of my page:
|[Static1] [Dynamic1] [Dynamic2] [Dynamic3] [Static2]|
So Static1 is some logo component that sticks to the left, Static2 is some user menu component that sticks to the right.
Now inside of it I have a collection component that displays several dynamic elements loaded from the DB.
All is good, if there are not too much of those components, but if there are more, I don't wan't any linebreaks, only some fort of "More" menu, something like:
|[Static1] [Dynamic1] [Dynamic2] [Dynamic3] [Dynamic4][...][Static2]|
and when I click the [...] button I wan't a vertical list of the dynamic components.
The list of dynamic items is stored in an ElementList component, with following code:
React.createClass({
render() {
return (
<div ref="listparent">
{ this.props.elements.map((element) => {
return <Element
ref={"element-"+element.name}
key={element.name}
})}
</div>
)
}
});
this.props.elements is a collection passed as a prop. I tried something allong those lines, but it either didn't work or worked but not on each page refresh:
export default React.createClass({
getInitialState(){
return {
visibleElements: this.props.elements,
hiddenElements: []
}
},
componentDidMount() {
this.rearrange();
},
componentDidUpdate(){
this.rearrange();
},
rearrange(){
var element = ReactDOM.findDOMNode(this.refs.listparent);
let visibleElements = [];
let hiddenElements = [];
for(var i=0; i< this.props.elements.length; i++)
{
var currentElement = this.props.elements[i];
var domElement = ReactDOM.findDOMNode(this.refs["element-"+element.name]);
if(domElement) {
if (domElement.offsetTop + domElement.offsetHeight >
element.offsetTop + element.offsetHeight ||
domElement.offsetLeft + domElement.offsetWidth >
element.offsetLeft + element.offsetWidth - 200) {
hiddenElements.push(currentElement);
}
else {
visibleElements.push(currentElement);
}
}
}
if(this.state.visibleElements.length != visibleElements.length) {
this.setState({
visibleElements: visibleElements,
hiddenElements: hiddenElements
})
}
},
render() {
return (
<div ref="listparent">
{ this.state.visibleElements.map((element) => {
return <Element
ref={"element-"+element.name}
key={element.name} />
})}
{ this.state.hiddenElements.length >0 &&
<DropdownMenu
Header="..."
>
{ this.state.hiddenElements.map((element) => {
return <Element
ref={"element-"+element.name}
key={element.name} />
})}
</DropdownMenu>
}
</div>
)
}
});
Here is a rough jsFiddle with what I want to do: https://jsfiddle.net/3uf9r8ne/
Got it working, I don't know if that's the best solution, or how robust it is, but works for me at least for now.
JsFiddle: https://jsfiddle.net/1w6m1n6h/
var Dropdown = React.createClass({
getInitialState(){
return {
isOpen: false
}
},
componentWillMount() {
document.addEventListener('click', this.handleClick, false);
},
componentWillUnmount() {
document.removeEventListener('click', this.handleClick, false);
},
handleClick: function (e) {
var component = ReactDOM.findDOMNode(this.refs.component);
if (e.target == component || $(component).has(e.target).length) {
// Inside the component
}
else{
// Outide
this.setState({ isOpen: false});
}
},
render()
{
return (
<div ref="component" className="dropdown">
<div className="dropdown-button" onClick={() => this.setState({ isOpen : !this.state.isOpen})}>{this.props.Header}</div>
{
this.state.isOpen && (
<div className="dropdown-menu" onClick={() => this.setState({ isOpen : false})}>
{React.Children.map(this.props.children, (item) => item)}
</div>
)
}
</div>
);
}
});
var Card = React.createClass({
render() {
let className = "card";
if(this.props.isHidden)
className += " is-hidden";
return (
<div className={className}>{this.props.name}</div>
)
}
});
var Cards = React.createClass({
getInitialState() {
return {
vCards: [],
hCards: [],
lastSetCards: [],
preMounted: false,
laidOut: false
};
},
rearrange() {
_.throttle(this.setState({laidOut: false, preMounted: false}), 100);
},
componentDidMount() {
window.addEventListener('resize', this.rearrange);
},
componentWillUnmount() {
window.removeEventListener('resize', this.rearrange);
},
componentDidUpdate() {
if(this.props.cards.length != this.state.lastSetCards || !this.state.preMounted) {
this.setState({
lastSetCards: this.props.cards.length,
vCards: this.props.cards,
preMounted: true,
laidOut: false
});
}
if(this.state.preMounted && !this.state.laidOut) {
var element = ReactDOM.findDOMNode(this.refs.listparent);
let visibleCards = [];
let hiddenCards = [];
for(var i=0; i< this.props.cards.length; i++)
{
var card = this.props.cards[i];
var cardElement = ReactDOM.findDOMNode(this.refs["card-"+card]);
if(cardElement) {
if (cardElement.offsetTop + cardElement.offsetHeight >
element.offsetTop + element.offsetHeight ||
cardElement.offsetLeft + cardElement.offsetWidth >
element.offsetLeft + element.offsetWidth - 160) {
hiddenCards.push(card);
}
else {
visibleCards.push(card);
}
}
}
this.setState({
vCards: visibleCards,
hCards: hiddenCards,
laidOut: true
});
}
},
render() {
return (<div className="cards-top" ref="listparent">
<div className="cards" >
{this.state.vCards.map((c)=> <Card ref={"card-"+c} key={c} name={c} />)}
</div>
<Dropdown Header="MORE">
{this.state.hCards.map((c)=> <Card isHidden={true} key={c} name={c} />)}
</Dropdown>
</div>
)
}
});
var Hello = React.createClass({
getInitialState() {
return {
cards: ["one", "two" ]
};
},
componentDidMount() {
this.setState({
cards: ["one", "two", "three", "four", "five", "six", "seven", "eight",
"nine", "ten", "eleven", "twelve", "thirteen", "fourteen"]
});
},
render: function() {
let addNew = () => {
this.state.cards.push("additional_"+this.state.cards.length);
this.setState({
cards: this.state.cards
})
};
return (
<div>
<div className="header">
<div className="logo">Logo</div>
<div className="user">User</div>
<Cards cards={this.state.cards} />
<div className="clear"></div>
</div>
<br/><br/>
<button onClick={addNew}>Add</button>
</div>);
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
.logo
{
float: left;
margin: 5px;
padding: 5px;
border: solid 1px blue;
}
.user
{
float: right;
margin: 5px;
padding: 5px;
border: solid 1px blue;
}
.header{
position: relative;
max-height: 10px;
height: 10px;
width: 100%;
}
.cards
{
position: relative;
display: inline-block;
vertical-align: top;
white-space: nowrap;
}
.clear
{
clear: both;
}
.card
{
display: inline-block;
margin: 5px;
padding: 5px;
border: solid 1px blue;
}
.cards-top
{
display: block;
white-space: nowrap;
vertical-align: top;
width: 100%;
border: green 1px solid;
}
.dropdown
{
display: inline-block;
}
.is-hidden
{
display: block;
}
.dropdown-button
{
margin: 5px;
padding: 5px;
border: solid 1px blue;
}
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>

Categories

Resources