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>
Related
In the following code, I have two columns. One column has all the tasks. The other column has a group of selected tasks. So, my expectation is that, when a user drags and drops a task from the total tasks column to the selected tasks column, the task should still be there in total tasks column. When the tasks are there in the selected tasks column, there will be a button visible to delete the tasks from selected tasks column. When I click this button, the task gets removed from the selected tasks column. But I am not able to drag it again from the total tasks column. I am getting the following error:
Unable to find draggable with id: task-2
Request you guys to take a look and tell me what's the mistake I am doing
Please find the link for the codesandbox:
https://codesandbox.io/s/unruffled-waterfall-wdyc5?file=/src/task.jsx
Also this is my code:
App.jsx:
import React from 'react';
import '#atlaskit/css-reset';
import styled from 'styled-components';
import { DragDropContext } from 'react-beautiful-dnd';
import initialData from './initial-data';
import Column from './column';
const Container = styled.div`
display: flex;
`;
class App extends React.Component {
state = initialData;
onDragStart = start => {
const homeIndex = this.state.columnOrder.indexOf(start.source.droppableId);
this.setState({
homeIndex,
});
};
onDragEnd = result => {
this.setState({
homeIndex: null,
});
const { destination, source, draggableId } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
const home = this.state.columns[source.droppableId];
const foreign = this.state.columns[destination.droppableId];
if (home === foreign) {
const newTaskIds = Array.from(home.taskIds);
newTaskIds.splice(source.index, 1);
newTaskIds.splice(destination.index, 0, draggableId);
const newHome = {
...home,
taskIds: newTaskIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newHome.id]: newHome,
},
};
this.setState(newState);
return;
}
const foreignTaskIds = Array.from(foreign.taskIds);
foreignTaskIds.splice(destination.index, 0, draggableId);
const newForeign = {
...foreign,
taskIds: foreignTaskIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newForeign.id]: newForeign,
},
};
this.setState(newState);
};
deleteHandler = (taskId) => {
console.warn("I am going to delete: " + taskId);
var columnId = 'column-2';
const column = this.state.columns[columnId];
const columnTaskIds = Array.from(column.taskIds);
columnTaskIds.splice(columnTaskIds.indexOf(taskId), 1);
const newcolumn = {
...column,
taskIds: columnTaskIds,
};
var newState = null;
newState = {
...this.state,
columns: {
...this.state.columns,
[newcolumn.id]: newcolumn
}
};
this.setState(newState);
console.log(newState);
}
render() {
return (
<DragDropContext
onDragStart={this.onDragStart}
onDragEnd={this.onDragEnd}
>
<Container>
{this.state.columnOrder.map((columnId, index) => {
const column = this.state.columns[columnId];
const tasks = column.taskIds.map(
taskId => this.state.tasks[taskId],
);
const isDropDisabled = index < this.state.homeIndex;
return (
<Column
key={column.id}
column={column}
tasks={tasks}
isDropDisabled={isDropDisabled}
deleteHandler={this.deleteHandler}
/>
);
})}
</Container>
</DragDropContext>
);
}
}
export default App
column.jsx
import React from 'react';
import styled from 'styled-components';
import { Droppable } from 'react-beautiful-dnd';
import Task from './task';
const Container = styled.div`
margin: 8px;
border: 1px solid lightgrey;
border-radius: 2px;
width: 220px;
display: flex;
flex-direction: column;
`;
const Title = styled.h3`
padding: 8px;
`;
const TaskList = styled.div`
padding: 8px;
transition: background-color 0.2s ease;
background-color: ${props => (props.isDraggingOver ? 'skyblue' : 'white')};
flex-grow: 1;
min-height: 100px;
`;
export default class Column extends React.Component {
isSelectedTasksColumn = this.props.column.id === 'column-2';
render() {
return (
<Container>
<Title>{this.props.column.title}</Title>
<Droppable
droppableId={this.props.column.id}
isDropDisabled={this.props.isDropDisabled}
>
{(provided, snapshot) => (
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapshot.isDraggingOver}
>
{this.props.tasks.map((task, index) => (
<Task key={task.id} task={task} index={index} isSelectedTasksColumn={this.isSelectedTasksColumn} deleteHandler={this.props.deleteHandler}/>
))}
{provided.placeholder}
</TaskList>
)}
</Droppable>
</Container>
);
}
}
task.jsx
import React from 'react';
import styled from 'styled-components';
import { Draggable } from 'react-beautiful-dnd';
const Container = styled.div`
border: 1px solid lightgrey;
border-radius: 2px;
padding: 8px;
margin-bottom: 8px;
background-color: ${props =>
props.isDragDisabled
? 'lightgrey'
: props.isDragging
? 'lightgreen'
: 'white'};
`;
const DeleteButton = styled.button`
justify-self: center;
align-self: center;
border: 0;
background-color: black;
color: white;
padding: 7px;
cursor: pointer;
display: ${props => props.isSelectedTasksColumn ? "inline" : "none"};
float: right;
`;
const DisplayValue = styled.p`
align-self: center;
font-size: 20px;
padding: 0;
margin: 0;
display: inline;
`;
export default class Task extends React.Component {
render() {
const isDragDisabled = false;
return (
<Draggable
draggableId={this.props.task.id}
index={this.props.index}
isDragDisabled={isDragDisabled}
>
{(provided, snapshot) => (
<Container
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
isDragging={snapshot.isDragging}
isDragDisabled={isDragDisabled}
>
<DisplayValue>{this.props.task.content}</DisplayValue>
<DeleteButton isSelectedTasksColumn = {this.props.isSelectedTasksColumn}
onClick={() => this.props.deleteHandler(this.props.task.id)}>
✖
</DeleteButton>
</Container>
)}
</Draggable>
);
}
}
initial-data.js:
const initialData = {
tasks: {
'task-1': { id: 'task-1', content: 'Task 1' },
'task-2': { id: 'task-2', content: 'Task 2' },
'task-3': { id: 'task-3', content: 'Task 3' },
'task-4': { id: 'task-4', content: 'Task 4' },
},
columns: {
'column-1': {
id: 'column-1',
title: 'All tasks',
taskIds: ['task-1', 'task-2', 'task-3', 'task-4'],
},
'column-2': {
id: 'column-2',
title: 'Selected tasks',
taskIds: [],
}
},
// Facilitate reordering of the columns
columnOrder: ['column-1', 'column-2'],
};
export default initialData;
Removing React.StrictMode could help
https://reactjs.org/docs/strict-mode.html
Some time ago I created an autocomplete component in vue for a project in which I am involved.
But today I detected a small bug.
When I select the option I want with the click of the mouse, the option does not get transmitted, as you can see in the console.log () that is in the example. If I click on another option again, what will appear in console.log () is the option previously selected.
If I put a setTimeout( () => {}, 200) it already detects and emit the option, but I think it is not the best solution for this case.
Any suggestion?
example
const Autocomplete = {
name: "autocomplete",
template: "#autocomplete",
props: {
items: {
type: Array,
required: false,
default: () => Array(150).fill().map((_, i) => `Fruit ${i+1}`)
},
isAsync: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isOpen: false,
results: [],
search: "",
isLoading: false,
arrowCounter: 0
};
},
methods: {
onChange() {
console.log( this.search)
// Let's warn the parent that a change was made
this.$emit("input", this.search);
},
setResult(result, i) {
this.arrowCounter = i;
this.search = result;
this.isOpen = false;
},
showAll() {
this.isOpen = !this.isOpen;
(this.isOpen) ? this.results = this.items : this.results = [];
},
},
computed: {
filterResults() {
// first uncapitalize all the things
this.results = this.items.filter(item => {
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
});
return this.results;
},
},
watch: {
items: function(val, oldValue) {
// actually compare them
if (val.length !== oldValue.length) {
this.results = val;
this.isLoading = false;
}
}
},
mounted() {
document.addEventListener("click", this.handleClickOutside);
},
destroyed() {
document.removeEventListener("click", this.handleClickOutside);
}
};
new Vue({
el: "#app",
name: "app",
components: {
autocomplete: Autocomplete
}
});
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
.autocomplete {
position: relative;
width: 130px;
}
.autocomplete-results {
padding: 0;
margin: 0;
border: 1px solid #eeeeee;
height: 120px;
overflow: auto;
width: 100%;
}
.autocomplete-result {
list-style: none;
text-align: left;
padding: 4px 2px;
cursor: pointer;
}
.autocomplete-result.is-active,
.autocomplete-result:hover {
background-color: #4aae9b;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<autocomplete />
</div>
<script type="text/x-template" id="autocomplete">
<div class="autocomplete">
<input type="text" #blur="onChange" v-model="search" #click="showAll" />
<ul id="autocomplete-results" v-show="isOpen" ref="scrollContainer" class="autocomplete-results">
<li class="loading" v-if="isLoading">
Loading results...
</li>
<li ref="options" v-else v-for="(result, i) in filterResults" :key="i" #click="setResult(result, i)" class="autocomplete-result" :class="{ 'is-active': i === arrowCounter }">
{{ result }}
</li>
</ul>
</div>
</script>
You were using onblur event, but its fired when you click outside and before the onclick's item listener, so the value wasn't updated.
Use onchange event to capture data if user types anything in the input and call onChange() method inside setResult().
const Autocomplete = {
name: "autocomplete",
template: "#autocomplete",
props: {
items: {
type: Array,
required: false,
default: () => Array(150).fill().map((_, i) => `Fruit ${i+1}`)
},
isAsync: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isOpen: false,
results: [],
search: "",
isLoading: false,
arrowCounter: 0
};
},
methods: {
onChange() {
console.log( this.search)
// Let's warn the parent that a change was made
this.$emit("input", this.search);
},
setResult(result, i) {
this.arrowCounter = i;
this.search = result;
this.isOpen = false;
// Fire onChange, because it won't do it on blur
this.onChange();
},
showAll() {
this.isOpen = !this.isOpen;
(this.isOpen) ? this.results = this.items : this.results = [];
},
},
computed: {
filterResults() {
// first uncapitalize all the things
this.results = this.items.filter(item => {
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
});
return this.results;
},
},
watch: {
items: function(val, oldValue) {
// actually compare them
if (val.length !== oldValue.length) {
this.results = val;
this.isLoading = false;
}
}
},
mounted() {
document.addEventListener("click", this.handleClickOutside);
},
destroyed() {
document.removeEventListener("click", this.handleClickOutside);
}
};
new Vue({
el: "#app",
name: "app",
components: {
autocomplete: Autocomplete
}
});
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
.autocomplete {
position: relative;
width: 130px;
}
.autocomplete-results {
padding: 0;
margin: 0;
border: 1px solid #eeeeee;
height: 120px;
overflow: auto;
width: 100%;
}
.autocomplete-result {
list-style: none;
text-align: left;
padding: 4px 2px;
cursor: pointer;
}
.autocomplete-result.is-active,
.autocomplete-result:hover {
background-color: #4aae9b;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<autocomplete />
</div>
<script type="text/x-template" id="autocomplete">
<div class="autocomplete">
<input type="text" #change="onChange" v-model="search" #click="showAll" />
<ul id="autocomplete-results" v-show="isOpen" ref="scrollContainer" class="autocomplete-results">
<li class="loading" v-if="isLoading">
Loading results...
</li>
<li ref="options" v-else v-for="(result, i) in filterResults" :key="i" #click="setResult(result, i)" class="autocomplete-result" :class="{ 'is-active': i === arrowCounter }">
{{ result }}
</li>
</ul>
</div>
</script>
blur is the wrong event to use here, and I think you're over complicating it. Simply call your emit in setResult:
setResult(result, i) {
this.arrowCounter = i;
this.search = result;
this.isOpen = false;
this.$emit("input", this.search);
},
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.
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>;
}
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>