How can I repeat a single column across each row? - javascript

Context:
I have a small piece of code that I wrote to visualise some binary numbers. It's a self contained HTML file that should work in the browser.
Output:
Question:
How can I show the "Input" block (.input-block div) in each row of this flexbox layout?
Is this achievable using Flexbox?
.container {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: flex-start;
height: 80vh;
}
.block {
padding: 15px;
flex: 0 1 150px;
}
.input-block {
border: 1px solid black;
}
.block:hover {
background-color: rgb(231, 231, 231);
}
.row {
padding: 10px;
text-align: right;
color: rgb(61, 61, 61);
letter-spacing: 1.4px;
}
.row:first-child {
font-weight: bold;
color: rgb(83, 83, 83);
background-color: burlywood;
}
.row:nth-child(even) {
background-color: rgb(255, 241, 219);
}
.row-head:nth-child(even) {
background-color: rgb(205, 255, 200);
}
<body>
<div id="root"></div>
</body>
<script type="module">
import { html, render } from 'https://unpkg.com/lit-html?module';
function main() {
const arr = new Array(32).fill(0).map((x, i) => i);
const elements = arr.map(x => getBinaryAssociates(x));
let state = { elements }
display(state);
}
/**
* #param { { elements: ReturnType<typeof getBinaryAssociates>[] } } state
*/
function display(state) {
const root = document.getElementById("root");
/** #type {(_: typeof state) => any} */
const tmpl = (state) => html`<div class="container">
${getHeaderBlockTmpl()}
${state.elements.map(getItemBlockTmpl)}
<div>`;
render(tmpl(state), root);
}
function getHeaderBlockTmpl() {
return html`<div class="block input-block">
<div class="row row-head">Input</div>
${FUNCTIONS.map(x => html`<div class="row row-head">${x.toString()}</div>`)}
</div>`;
}
function getItemBlockTmpl(item) {
return html`<div class="block">
<div class="row">
${numToString(item.input)} ➡ ${item.binary}
</div>
${item.outputs.map(x => html`<div class="row">
${x.value} ➡ ${x.binary}
</div>`)}
</div>`;
}
const FUNCTIONS = [
x => x + 1,
x => x - 1,
x => x & (x - 1),
x => ~x + 1,
x => -(x + 1),
x => ~x,
];
function numToString(n) {
return n.toString();
}
function getBinaryAssociates(number) {
const binary = (x) => (x >>> 0).toString(2).slice(-6).padStart(6, '0');
let res = FUNCTIONS
.map(x => ({
function: x,
value: numToString(x(number)),
binary: binary(x(number)),
}));
let result = res.reduce((acc, n) => {
acc.outputs.push(n);
return acc;
}, {
input: number,
binary: binary(number),
/** #type {typeof res} */
outputs: [],
});
return result;
}
function init() {
document.addEventListener("DOMContentLoaded", () => {
main();
});
}
init();
</script>

Related

Drag-and-Drop Functionality in React Without Animations

I am currently in the process of replicating some of the core functionalities that exists in Trello. One of the functionalities i am looking into is the drag-and-drop.
I have managed to implement the functionality, albeit with an issue. When you drag a card from one column to another column, there is some form of animation (see the linked YouTube video for reference). To describe it in words, the animation portray a movement back to the starting position of the dragged card, although it is not the case.
https://www.youtube.com/watch?v=wSoaaKLj3r0
My current code:
/* Column.js */
import React, {useState, useRef} from 'react';
import './Column.css';
function Column({data}) {
const [list, setList] = useState(data);
const [dragging, setDragging] = useState(false);
const dragItem = useRef();
const dragNode = useRef();
const handleDragStart = (event, params) => {
console.log('drag starting', params)
dragItem.current = params;
dragNode.current = event.target;
dragNode.current.addEventListener('dragend', handleDragEnd)
setTimeout(() => {
setDragging(true);
}, 0)
}
const handleDragEnter = (event, params) => {
console.log('entering drag', params);
const currentItem = dragItem.current;
if(event.target !== dragNode.current) {
console.log('TARGET IS NOT THE SAME')
setList(oldList => {
let newList = JSON.parse(JSON.stringify(oldList));
newList[params.groupIndex].items.splice(params.itemIndex, 0, newList[currentItem.groupIndex].items.splice(currentItem.itemIndex,1)[0])
dragItem.current = params
return newList;
})
}
}
const handleDragEnd = () => {
console.log('Ending Drag')
setDragging(false);
dragNode.current.removeEventListener('dragend', handleDragEnd)
dragItem.current = null;
dragNode.current = null;
}
const getStyles = (params) => {
const currentItem = dragItem.current;
if (currentItem.groupIndex === params.groupIndex && currentItem.itemIndex === params.itemIndex) {
return 'current drag-and-drop-item';
}
return 'drag-and-drop-item'
};
return (
<section className="drag-and-drop-container">
<div className="drag-and-drop">
{list.map((group, groupIndex) => (
<div
key={group.title}
className="drag-and-drop-group"
onDragEnter={dragging && !group.items.length
? (event) => handleDragEnter(event, {groupIndex, itemIndex:0})
: null
}
>
<div className="drag-and-drop-group-title">{group.title}</div>
{group.items.map((item, itemIndex) => (
<div
draggable
onDragStart={(dragEvent) => {handleDragStart(dragEvent, {groupIndex, itemIndex})}}
onDragEnter={dragging
? (event) => {handleDragEnter(event, {groupIndex, itemIndex})}
: null
}
key={item}
className={dragging
? getStyles({groupIndex, itemIndex})
: "drag-and-drop-item"
}
>
{item}
</div>
))}
</div>
))}
</div>
</section>
)
}
export default Column;
/* Column.css */
#import url(./../../index.css);
.drag-and-drop{
display: grid;
gap: 0.5rem;
width: 100%;
height: 100%;
grid-template-columns: repeat(auto-fill, 300px);
background-color: var(--primary-color);
padding: 0.5rem;
align-items: start;
}
.drag-and-drop-group{
background-color: var(--secondary-color-80);
padding: 0.5rem;
}
.drag-and-drop-group-title{
margin-bottom: 0.5rem;
font-size: 1.2rem;
font-family: 'Poppins';
}
.drag-and-drop-item {
background-color: #FFFFFF;
color: var(--primary-color);
min-height: 150px;
}
.drag-and-drop-item:not(:last-of-type){
margin-bottom: 0.5rem;
}
.current{
background-color: var(--primary-color);
}
Findings:
The problem occurs exclusively on my MacBook, and therefore not on my Windows computer.
I have tried with Event.preventDefault();, however without luck.
Question:
What can I do to prevent the animation on Mac OS?

By using React framer motion "drag and reorder" I can drag the card but does not stick to the new position. After dragging it, return to the original

I am trying to achieve this: https://codesandbox.io/s/framer-motion-2-drag-to-reorder-fc4rt?file=/src/use-position-reorder.js
Basically you can drag a card, place it somewhere in the container and reorder the card based on where you drag/put it from the original place.
The problem is that in my code, you can drag the card but not place it. I tried to change the code so many times but I can't find a way out of it. Below you will find the code where I render the component FavouriteTreeNodeComponent which is a LI(List) nested in FavouriteTreeNodesList a UL(Unordered List) :
// react
import { useRef, useEffect, useCallback, useState } from 'react';
// animation spring
import { animated, Transition, useSpring } from 'react-spring';
import { AnimateSharedLayout } from 'framer-motion';
// styled components
import styled from 'styled-components';
// framer motion
import { motion } from 'framer-motion';
import { usePositionReorder } from './ReorderingModalNodes/use-position-reorder';
// icons
import { MdClose } from 'react-icons/md';
// components
import TemplateItem from './TemplateItem';
import SearchBar from './SearchBar';
import DurationIntervalComponent from './DurationIntervalComponent';
import ContextItem from './ContextItem';
import FavouriteTreeNodeComponent from './FavouriteTreeNodeComponent';
const TimeEntryModal = ({ showModal, setShowModal }) => {
// STATES
const [selected, setSelected] = useState('');
const [reportingTemplates, setReportingTemplates] = useState([]);
const [reportingContexts, setReportingContexts] = useState([]);
// USEREF
const modalRef = useRef();
// ANIMATION MODAL
const animation = useSpring({
config: {
duration: 250,
},
opacity: showModal ? 1 : 0,
transform: showModal ? `translateY(0%)` : `translateY(-100%)`,
});
// CLOSING MODAL FROM BACKGROUND
const closeModal = (e) => {
if (modalRef.current === e.target) {
setShowModal(false);
}
};
// CLOSING MODAL WITH ESCAPE BUTTON
const keyPress = useCallback(
(e) => {
if (e.key === 'Escape' && showModal) {
setShowModal(false);
console.log('I pressed');
}
},
[setShowModal, showModal]
);
useEffect(() => {
document.addEventListener('keydown', keyPress);
return () => document.removeEventListener('keydown', keyPress);
}, [keyPress]);
const variants = {
hidden: {
y: '-100vh',
},
visible: { y: 0, transition: { ease: 'easeInOut', duration: 1 } },
exit: {
y: '-100vh',
},
};
// DATA
const colorsData = [
'#86d8bda1',
'#eb472a83',
'#8fcfe3ad',
'#db8ddfb0',
'#e8e46fc0',
'#e1aa66b2',
];
const List = [
'3000',
'4000',
'5000',
'6000',
'7000',
'8000',
];
// FAVOURITES LIST ANIMATION
const [updatedList, updatePosition, updateOrder] =
usePositionReorder(List);
// FETCHES
useEffect(() => {
getTemplates();
}, []);
const url = 'http://localhost:8080/graphql';
const getTemplates = async () => {
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `query {
reportingTemplates {
name
_id
colorScheme
reportingContexts {
name
_id
}
}
}`,
}),
});
const data = await res.json();
console.log(data);
const dataWithColors = data.data.reportingTemplates.map((obj, i) => ({
...obj,
color: colorsData[i],
}));
setReportingTemplates(dataWithColors);
setSelected(dataWithColors[0].color);
setReportingContexts(dataWithColors[0].reportingContexts);
console.log('data', dataWithColors);
} catch (err) {
console.log('ERROR', err);
}
};
console.log('data state', reportingTemplates);
return (
<Background ref={modalRef} onClick={closeModal}>
<motion.div
variants={variants}
initial="hidden"
animate="visible"
exit="exit"
>
<ModalWrapper>
<ModalContent>
<Nav>
<h3>New Entry Tirsdag 2. Sep </h3>
</Nav>
<ModalColumns>
<ColumnOne>
<TemplatesContainer>
<AnimateSharedLayout>
<Templates>
{reportingTemplates.map((template, index) => (
<TemplateItem
key={template.name}
color={template.color}
title={template.name}
isSelected={selected === template.color}
onClick={() => {
setReportingContexts(
reportingTemplates[index].reportingContexts
);
setSelected(template.color);
}}
/>
))}
</Templates>
</AnimateSharedLayout>
</TemplatesContainer>
<InformationBlock>
<h4>Information</h4>
{reportingContexts.map((context) => (
<ContextItem
key={context.name}
text={context.name}
// isSelected={selected === context.color}
// onClick={() => setSelected(context.color)}
/>
))}
</InformationBlock>
<DurationIntervalComponent />
</ColumnOne>
<ColumnTwo>
<SearchBar />
<Recent>
<h4>Recent</h4>
</Recent>
<Favourites>
<h4>Favourites</h4>
<FavouriteTreeNodesList>
{updatedList.map((treeNode, index) => (
<FavouriteTreeNodeComponent
key={index}
index={index}
text={treeNode}
updateOrder={updateOrder}
updatePosition={updatePosition}
/>
))}
</FavouriteTreeNodesList>
</Favourites>
</ColumnTwo>
</ModalColumns>
</ModalContent>
<CloseModalButton
aria-label="Close Modal"
onClick={() => setShowModal((prev) => !prev)}
/>
<Footer>
<DeleteBlock>
<p> Delete </p>
</DeleteBlock>
<CancelNSaveBlock>
<Cancel>
<p> Cancel </p>
</Cancel>
<SaveNClose>
<p> Save and close </p>
</SaveNClose>
<SaveNAdd>
<p> Save and add another </p>
</SaveNAdd>
</CancelNSaveBlock>
</Footer>
</ModalWrapper>
</motion.div>
</Background>
);
};
// STYLES
const Background = styled.div`
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
position: fixed;
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
`;
const ModalWrapper = styled.div`
width: 80vw;
height: 80vh;
box-shadow: 0 5px 16px rgba(0, 0, 0, 0.2);
background: #fff;
color: #000;
display: flex;
flex-direction: column;
align-items: stretch;
border-radius: 10px;
`;
const CloseModalButton = styled(MdClose)`
cursor: pointer;
position: absolute;
top: 20px;
right: 20px;
width: 32px;
height: 32px;
padding: 0;
z-index: 10;
`;
// MODAL CONTENT
const ModalContent = styled.div`
padding: 20px;
position: relative;
`;
// NAV
const Nav = styled.div``;
const ModalColumns = styled.div`
padding: 10px 0px;
display: flex;
`;
// COLUMN ONE
const ColumnOne = styled.div`
width: 50%;
padding: 20px;
`;
const TemplatesContainer = styled.div``;
const Templates = styled.ul`
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
flex-wrap: initial;
justify-content: center;
`;
const InformationBlock = styled.div`
padding-bottom: 20px;
`;
// COLUMN TWO
const ColumnTwo = styled.div`
width: 50%;
padding: 20px;
`;
const Recent = styled.div`
background-color: #f6f6f9;
border: 1px solid #c5c5c5;
border-radius: 20px;
`;
const Favourites = styled.div`
list-style: none;
`;
const FavouriteTreeNodesList = styled.ul`
position: relative;
width: 100%;
`;
// FOOTER
const Footer = styled.div`
display: flex;
position: fixed;
padding: 0 20px;
bottom: 10px;
/* text-align: center; */
width: 100%;
`;
const DeleteBlock = styled.div`
color: #fb423a;
`;
const CancelNSaveBlock = styled.div`
display: flex;
margin-left: auto;
`;
// const [{name:"cancel", color func: ()=>sdasd }, "sada", "asdasd"]
const Cancel = styled.div`
color: #02b396;
margin-right: 40px;
`;
const SaveNClose = styled.div`
font-weight: bolder;
color: #02b396;
margin-right: 40px;
`;
const SaveNAdd = styled.div`
font-weight: bolder;
color: #02b396;
`;
export default TimeEntryModal;
After that the list FavouriteTreeNodeComponent is rendered in the code below:
import { useState } from 'react';
// styled components
import styled from 'styled-components';
// framer motion
import { motion } from 'framer-motion';
import { useMeasurePosition } from './ReorderingModalNodes/use-measure-position';
const FavouriteTreeNodeComponent = ({
text,
updateOrder,
updatePosition,
index,
}) => {
const [isdragged, setIsDragged] = useState(false);
const itemRef = useMeasurePosition((pos) => updatePosition(index, pos));
return (
<ListContainer>
<motion.div
style={{
zIndex: isdragged ? 2 : 1,
height: text.length * 10,
padding: "5px",
}}
dragConstraints={{
top: 0,
bottom: 0,
}}
dragElastic={1}
layout
ref={itemRef}
onDragStart={() => setIsDragged(true)}
onDragEnd={() => setIsDragged(false)}
animate={{
scale: isdragged ? 1.05 : 1,
}}
onViewportBoxUpdate={(_, delta) => {
isdragged && updateOrder(index, delta.y.translate);
}}
drag="y">{text}
</motion.div>
</ListContainer>
);
};
// STYLES
const ListContainer = styled.li`
height: auto;
gap: 9px 0px;
position: relative;
padding: 10px 0px;
list-style: none;
div {
background-color: white;
border-radius: 5px;
border: 1px solid #c5c5c5;
}
`;
export default FavouriteTreeNodeComponent;
The above code is connected to the file: useMeasurePosition same as the original project:
import { useEffect, useRef } from 'react';
export function useMeasurePosition(update) {
// We'll use a `ref` to access the DOM element that the `motion.li` produces.
// This will allow us to measure its height and position, which will be useful to
// decide when a dragging element should switch places with its siblings.
const ref = useRef(null);
// Update the measured position of the item so we can calculate when we should rearrange.
useEffect(() => {
update({
height: ref.current.offsetHeight,
top: ref.current.offsetTop,
});
});
return ref;
}
and to the file: usePositionReorder:
import { useState, useRef } from 'react';
import { clamp, distance } from 'popmotion';
import { arrayMoveImmutable as move} from 'array-move';
export function usePositionReorder(initialState) {
const [order, setOrder] = useState(initialState);
// We need to collect an array of height and position data for all of this component's
// `Item` children, so we can later us that in calculations to decide when a dragging
// `Item` should swap places with its siblings.
const positions = useRef([]).current;
const updatePosition = (i, offset) => (positions[i] = offset);
// Find the ideal index for a dragging item based on its position in the array, and its
// current drag offset. If it's different to its current index, we swap this item with that
// sibling.
const updateOrder = (i, dragOffset) => {
const targetIndex = findIndex(i, dragOffset, positions);
if (targetIndex !== i) setOrder(move(order, i, targetIndex));
};
return [order, updatePosition, updateOrder];
}
const buffer = 30;
export const findIndex = (i, yOffset, positions) => {
let target = i;
const { top, height } = positions[i];
const bottom = top + height;
// If moving down
if (yOffset > 0) {
const nextItem = positions[i + 1];
if (nextItem === undefined) return i;
const swapOffset =
distance(bottom, nextItem.top + nextItem.height / 2) + buffer;
if (yOffset > swapOffset) target = i + 1;
// If moving up
} else if (yOffset < 0) {
const prevItem = positions[i - 1];
if (prevItem === undefined) return i;
const prevBottom = prevItem.top + prevItem.height;
const swapOffset = distance(top, prevBottom - prevItem.height / 2) + buffer;
if (yOffset < -swapOffset) target = i - 1;
}
return clamp(0, positions.length, target);
};

How can resize boxes, if I remove 2 of them?

How can resize boxes, if I remove 2 of them?
For example, I have 6 boxes, and I remove 2 of them, the other 4 should change their width.
How can I do this, any suggestions?
you can use flex:
.box{
background-color:gray;
height:150px;
margin:25px;
flex:1 1 0;
}
.container{
display:flex;
flex-wrap:wrap;
}
<div class="container">
<div class="box">1</div>
<div class="box">2</div>
<div class="box">3</div>
<div class="box">4</div>
<div class="box">5</div>
<div class="box">6</div>
</div>
You can use grid, flexbox or even set width manually. Everything is possible in React world. Well, for the last one you have to be quite tricky!
Example of each approach
const { useState, useEffect, useRef } = React;
const getItems = () => Promise.resolve(Array(6).fill(0).map((pr, index) => ({id: index})))
const random = () => Boolean(Math.floor(Math.random() * 1.4))
const App = () => {
const [items, setItems] = useState([]);
const thirdContainerRef = useRef(null);
const [thirdContainerSize, setThirdContainerSize] = useState({
width: 0,
height: 0
});
useEffect(() => {
let isUnmounted = false;
let handle = null;
getItems()
.then(items => {
if(!isUnmounted) {
setItems(items);
}
})
const loop = () => {
getItems()
.then(items => {
if(!isUnmounted) {
let filtered = items.slice(0, 4);
if(random()) {
filtered = items.slice(0, 6);
}
setItems(filtered);
}
})
handle = setTimeout(loop, 1000);
}
handle = setTimeout(loop, 1000);
return () => {
clearTimeout(handle);
isUnmounted = true;
}
}, [])
const computeFlex = () => {
const flexBasis = items.length === 6 ? 25 : 33;
return {
flex: `1 1 ${flexBasis}%`
}
}
const computeGrid = () => {
const numberOfcolumns = items.length === 6 ? 3 : 2;
return {
gridTemplateColumns: `repeat(${numberOfcolumns}, auto)`
}
}
useEffect(() => {
if(thirdContainerRef && thirdContainerRef.current) {
const element = thirdContainerRef.current;
setThirdContainerSize(element.getBoundingClientRect());
}
}, [thirdContainerRef])
const computeSize = () => {
if(!thirdContainerSize.width) {
return {}
}
const containerWidth = thirdContainerSize.width;
const containerHeight = thirdContainerSize.height;
const margin = 5 * 2;
const width = items.length === 6 ? (containerWidth / 3) - margin : (containerWidth / 2) - margin;
const height = containerHeight / 2 - margin;
return {
width: `${width}px`,
height: `${height}px`,
float: `left`
}
}
return <div>
Flexbox
<div className="container">
{items.map(pr => <div key={pr.id} style={computeFlex()} className="item"></div>)}
</div>
Grid
<div style={computeGrid()} className="second-container">
{items.map(pr => <div key={pr.id} className="item"></div>)}
</div>
Width
<div ref={thirdContainerRef} className="third-container">
{items.map(pr => <div style={computeSize()} key={pr.id} className="item"></div>)}
</div>
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
html, body {
height: 100%;
box-sizing: border-box;
margin: 0;
}
.container {
width: 100%;
height: 100px;
display: flex;
flex-wrap: wrap;
}
.second-container {
width: 100%;
height: 100px;
display: grid;
}
.third-container {
width: 100%;
height: 100px;
}
.item {
background: gray;
margin: 5px;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>

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>

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