Angular. Drag & drop in flex list - html

I need drag & drop divs in the list. But div move strangely. Objects are not moved to the places that I need.
TS:
timePeriods = [
'1', '2', '3', '4', '5', '6', '7'
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.timePeriods, event.previousIndex, event.currentIndex);
}
HTML:
<div cdkDropList cdkDropListOrientation="horizontal" class="list" (cdkDropListDropped)="drop($event)">
<div class="box" *ngFor="let timePeriod of timePeriods" cdkDrag>{{timePeriod}}</div>
CSS:
.box {
width: 33%;
border: solid 1px #ccc;
margin: 3em;
}
.list {
width: 100%;
border: solid 1px #ccc;
height: 90%;
display: flex;
flex-direction: row;
background: white;
border-radius: 4px;
overflow: hidden;
justify-content: space-around;
flex-wrap: wrap;
}

This is a duplicate to Angular CDK drag and drop issue inside CSS flexbox.
As answered there I suggest creating an items table matrix representing your flex list. I have posted a detailed explanation here: https://taitruong.github.io/software-developer.org/post/2019/10/26/Angular-drag'n'drop-mixed-orientation-in-flex-row-wrap/
I have implemented a solution on StackBlitz for your problem here: https://stackblitz.com/edit/angular-drag-and-drop-in-flex-list
Template:
<div #rowLayout cdkDropListGroup>
<!-- based on row layout's width and item box width, columns per row can be calculated and a items table matrix is initialized-->
<div
*ngFor="let itemsRow of getItemsTable(rowLayout)"
cdkDropList
class="list"
cdkDropListOrientation="horizontal"
[cdkDropListData]="itemsRow"
(cdkDropListDropped)="reorderDroppedItem($event)"
>
<!-- drop reorders items and recalculates table matrix-->
<div class="box" *ngFor="let item of itemsRow" cdkDrag>
<div class="drag-placeholder" *cdkDragPlaceholder></div>
{{ item }}
</div>
</div>
</div>
CSS:
.box {
width: 33%;
border: solid 1px #ccc;
margin: 1em;
}
.list {
width: 100%;
border: solid 1px #ccc;
height: 90%;
display: flex;
flex-direction: row;
background: white;
border-radius: 4px;
overflow: hidden;
justify-content: space-around;
flex-wrap: wrap;
}
.drag-placeholder {
background: #ccc;
width: 3em;
border: dotted 3px #999;
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
Component:
export class AppComponent {
timePeriods = ["1", "2", "3", "4", "5", "6", "7"];
itemsTable: Array<string[]>;
getItemsTable(rowLayout: Element): string[][] {
if (this.itemsTable) {
return this.itemsTable;
}
// calculate column size per row
const { width } = rowLayout.getBoundingClientRect();
const boxWidth = Math.round(width * .33); // 33% as defined in css
const columnSize = Math.round(width / boxWidth);
// calculate row size: items length / column size
// add 0.5: round up so that last element is shown in next row
const rowSize = Math.round(this.timePeriods.length / columnSize + .5);
// create table rows
const copy = [...this.timePeriods];
this.itemsTable = Array(rowSize)
.fill("")
.map(
_ =>
Array(columnSize) // always fills to end of column size, therefore...
.fill("")
.map(_ => copy.shift())
.filter(item => !!item) // ... we need to remove empty items
);
return this.itemsTable;
}
reorderDroppedItem(event: CdkDragDrop<number[]>) {
// clone table, since it needs to be re-initialized after dropping
let copyTableRows = this.itemsTable.map(_ => _.map(_ => _));
// drop item
if (event.previousContainer === event.container) {
moveItemInArray(
event.container.data,
event.previousIndex,
event.currentIndex
);
} else {
transferArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex
);
}
// update items after drop
this.timePeriods = this.itemsTable.reduce((previous, current) =>
previous.concat(current)
);
// re-initialize table
let index = 0;
this.itemsTable = copyTableRows.map(row =>
row.map(_ => this.timePeriods[index++])
);
}
}

Related

Fixed table layout and a single row w/ pseudo element leading to shrunk table width

I've a table in my project with a pseudo element to show which row is active. Having changed the table layout to fixed (which is needed), I started getting this strange layout where the active row would expand to take up the entire table, but the other rows would not:
I've replicated a similar problem here (codepen, snippet below) - it's not exactly the same (the active row doesn't extend), but I'm fairly sure any answer to this would help me fix my problem.
If you comment out the top active::after style you'll see the table return to its correct size.
Thanks
// TABLE DATA
const headers = ['Id', 'Name', 'Age', 'Location'];
const datasetOne = [
['1','John Jones','27','Swindon'],
['2', 'Pete Best', '23', 'Glasgow'],
['3', 'Jules West', '22', 'Exeter'],
['4', 'Kate Ford', '33', 'Fife'],
];
const datasetTwo = [
['5','Ruth Thompson','27','Birmingham'],
['6', 'Dominic Lawson', '23', 'Greater London'],
['7', 'Really really long name', '22', 'Manchester'],
['8', 'Nicholas Johnson', '33', 'Liverpool'],
];
const tableWrapper = document.querySelector('.table-wrapper');
const btn = document.querySelector('.btn');
let dataset = 1;
// Listeners
window.addEventListener('load', () => {
const data = formatData(datasetOne);
tableWrapper.insertAdjacentHTML('afterbegin', createTable(headers, data));
});
btn.addEventListener('click', () => {
// Remove the table
const table = document.querySelector('.table')
table.parentElement.removeChild(table);
// Create and insert a new table
let data;
if(dataset === 1) {
data = formatData(datasetTwo);
dataset = 2;
}
else if(dataset === 2) {
data = formatData(datasetOne);
dataset = 1;
}
tableWrapper.insertAdjacentHTML('afterbegin', createTable(headers, data));
})
// Functions to create the table
function formatData(data) {
const rows = data.map(row => {
return createHTMLRow(row);
});
return rows;
}
function createHTMLRow([id, name, age, location]) {
const row = [
`<td class="td--id">${id}</td>`,
`<td class="td--name">${name}</td>`,
`<td class="td--age">${age}</td>`,
`<td class="td--location">${location}</td>`
];
return row;
}
function createTable (theads, rows) {
const markup = `
<table class="table">
<thead class="thead">
<tr>
${theads.map((thead) => {
return `<th class="th--${thead.toLowerCase()}">${thead}</th>`;
}).join('')}
</tr>
</thead>
<tbody class="tbody">
${
rows.map((row, index) => {
return `<tr class="row ${index===0? 'active':''}">${row.map(col => {
return `${col}`
}).join('')}</tr>`
}).join('')
}
</tbody>
</table>
`;
return markup;
};
.active::after {
position: absolute;
content: '';
left: 0;
top: 0;
width: 2px;
height: 100%;
background-color: green;
}
* {
margin: 0;
box-sizing: border-box;
}
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
background-color: firebrick;
}
.table-wrapper {
display: flex;
background-color: white;
width: 30rem;
overflow: hidden;
}
.table {
display: table;
table-layout:fixed;
border-collapse: collapse;
overflow: hidden;
width: 100%;
}
th {
text-align: start;
padding: 1rem;
background-color: lemonchiffon;
border: 1px solid lightgrey;
}
.th--age, .th--id {
width: 4rem;
}
td {
padding: .5rem 1rem;
border: 1px solid lightgrey;
white-space: nowrap;
}
.td--name {
overflow: hidden;
text-overflow: ellipsis;
}
.row {
position: relative;
height: 2rem;
}
.btn {
padding: 1rem .8rem;
width: 7rem;
background-color: white;
margin-top: 2rem;
}
<div class="container">
<div class="table-wrapper"></div>
<div class="btn">Change Data</div>
</div>
**Edit:
#zer00ne's answer of using min-width did fix the row issue, but it's unfortunately caused other problems with text-overflow:ellipsis and column widths
If you click on the codepen, item 7 currently works and overflows as intended, and all the columns remain a fixed width, even if they aren't given a width in the css (extra space seems to be distributed evenly between them).
Adding min-width to the table, while fixing the row issue, unfortunately breaks this behaviour
Was hoping someone had any ideas on how I can keep the columns fixed (as the codepen currently behaves), while being able to add the pseudo element (or some way of achieving the same effect)
Thanks!
**Edit 2:
I guess I could just manually divide up the total table width between each of the columns, but that seems a bit fragile
Update
I believe that the pseudo-element .active::after may still be the culprit. It feels like a code smell, a quick google lead me to nowhere official, but from what little I found coincides with what I was saying before, It's basically an extra cell in a single row. Because it's a pseudo-element, it's not part of the DOM so if you put one in a place where other elements cannot exist (like being a child of a <tr>), you may get unexpected results.
So here's what I did, it looks good. Try to break it and let me know if it actually does:
I changed where .active is generated -- it is now assigned to the first <td> of the first <tr> within the <tbody>. <td> can hold just about anything and it looks exactly like it did as it was on the <tr>.
Figure I
function formatData(data) { // ↙️ Add the index parameter
const rows = data.map((row, idx) => {
return createHTMLRow(idx, row);
}); // ↖️ Pass it as the 1st parameter
//...
Figure II
// ↙️ Pass the index reference as 1st parameter
function createHTMLRow(i, [id, name, age, location]) {
const row = [
`<td class="td--id ${i===0? 'active':''}">${id}</td>`,
//... ↖️ Here's the index reference interpolated
Original Solution
Still good for most cases. The update is for an edge case.
The table is at width: 100% which doesn't guarantee 100% with fixed tables that have undefined <th> widths. Change table width:
table {min-width: 100%}
// TABLE DATA
const headers = ['Id', 'Name', 'Age', 'Location'];
const datasetOne = [
['1', 'John Jones', '27', 'Swindon'],
['2', 'Pete Best', '23', 'Glasgow'],
['3', 'Jules West', '22', 'Exeter'],
['4', 'Kate Ford', '33', 'Fife'],
];
const datasetTwo = [
['5', 'Ruth Thompson', '27', 'Birmingham'],
['6', 'Dominic Lawson', '23', 'Greater London'],
['7', 'XXXXXXXXXX XXXXXXXXXXX XXXXXXXXXXXXXXXX XXXXXXXXXXXX XXXXXXXXXXXXXX XXXXXXXx XXXXXX', '22', 'Manchester'],
['8', 'Nicholas Johnson', '33', 'Liverpool'],
];
const tableWrapper = document.querySelector('.table-wrapper');
const btn = document.querySelector('.btn');
let dataset = 1;
// Listeners
window.addEventListener('load', () => {
const data = formatData(datasetOne);
tableWrapper.insertAdjacentHTML('afterbegin', createTable(headers, data));
});
btn.addEventListener('click', () => {
// Remove the table
const table = document.querySelector('.table')
table.parentElement.removeChild(table);
// Create and insert a new table
let data;
if (dataset === 1) {
data = formatData(datasetTwo);
dataset = 2;
} else if (dataset === 2) {
data = formatData(datasetOne);
dataset = 1;
}
tableWrapper.insertAdjacentHTML('afterbegin', createTable(headers, data));
});
// Functions to create the table
function formatData(data) {
const rows = data.map((row, idx) => {
return createHTMLRow(idx, row);
});
return rows;
}
function createHTMLRow(i, [id, name, age, location]) {
const row = [
`<td class="td--id ${i===0? 'active':''}">${id}</td>`,
`<td class="td--name">${name}</td>`,
`<td class="td--age">${age}</td>`,
`<td class="td--location">${location}</td>`
];
return row;
}
function createTable(theads, rows) {
const markup = `
<table class="table">
<thead class="thead">
<tr>
${theads.map((thead) => {
return `<th class="th--${thead.toLowerCase()}">${thead}</th>`;
}).join('')}
</tr>
</thead>
<tbody class="tbody">
${
rows.map((row, index) => {
return `<tr class="row">${row.map(col => {
return `${col}`
}).join('')}</tr>`
}).join('')
}
</tbody>
</table>
`;
return markup;
};
.active::after {
position: absolute;
content: '';
left: 0;
top: 0;
width: 2px;
height: 100%;
background-color: green;
}
* {
margin: 0;
box-sizing: border-box;
}
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background-color: firebrick;
}
.table-wrapper {
display: flex;
background-color: white;
width: 30rem;
overflow: hidden;
}
.table {
display: table;
table-layout: fixed;
border-collapse: collapse;
overflow: hidden;
width: 100%;
}
th {
text-align: start;
padding: 1rem;
background-color: lemonchiffon;
border: 1px solid lightgrey;
}
.th--age,
.th--id {
width: 4rem;
}
td {
padding: .5rem 1rem;
border: 1px solid lightgrey;
white-space: nowrap;
}
.td--name {
overflow: hidden;
text-overflow: ellipsis;
}
.row {
position: relative;
height: 2rem;
}
.btn {
padding: 1rem .8rem;
width: 7rem;
background-color: white;
margin-top: 2rem;
}
<div class="container">
<div class="table-wrapper"></div>
<div class="btn">Change Data</div>
</div>

Flexboxes: stacked, centered, wrapping, with color-matching sides and dynamic, synchronized width (see example)

Basically, I want something that acts similar to the following snippet. Notice how adding an item to one flex row increases the width of both.
function initItems() {
const numArr = Array.from(Array(3).keys());
const items = numArr.map(() => "item");
return items;
}
function FlexItems() {
const [items, setItems] = React.useState(initItems);
const addItem = () => setItems([...items, "item"]);
return (
<div className="flex-row">
<button onClick={addItem}>Add Item</button>
{items.map((item, idx)=><div className="item" key={idx}>{item}</div>)}
</div>
);
}
function StackedCenteredFlexItems() {
return (
<table>
<tr>
<td className="side first">.</td>
<td className="center first"><FlexItems/></td>
<td className="side first">.</td>
</tr>
<tr>
<td className="side second">.</td>
<td className="center second"><FlexItems/></td>
<td className="side second">.</td>
</tr>
</table>
)
}
ReactDOM.render(
<StackedCenteredFlexItems />,
document.getElementById("react")
);
.flex-row {
display: flex;
/* flex-wrap: wrap; */
border: 1px solid black;
}
table {
width: 100%;
border-collapse: collapse;
}
td.center {
width: 1px;
white-space: nowrap;
}
td.side {
padding: 0;
font-size: 1px;
color: transparent;
}
.first {
background: lightblue;
}
.second {
background: lightgreen;
}
.item {
margin: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
This solution is close, the active ingredient being that the flex rows are in the center cells of a table with this applied:
td.center {
width: 1px;
white-space: nowrap;
}
In that example flex-wrap is set to nowrap, however, because with wrap turned on, all the items in the flex rows just stack on top of one another. But I need the items to wrap in response to window size, like this:
function initItems() {
const numArr = Array.from(Array(20).keys());
const items = numArr.map(() => "item");
return items;
}
function FlexItems({ className }) {
const [items, setItems] = React.useState(initItems);
const addItem = () => setItems([...items, "item"]);
return (
<div className={"flex-row " + className}>
<button onClick={addItem}>Add Item</button>
{items.map((item, idx)=><div className="item" key={idx}>{item}</div>)}
</div>
);
}
ReactDOM.render(
<div>
<FlexItems className="first" />
<FlexItems className="second" />
</div>,
document.getElementById("react")
);
.flex-row {
display: flex;
flex-wrap: wrap;
}
.first {
background: lightblue;
}
.second {
background: lightgreen;
}
.item {
margin: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
The first example has most of what I want:
The flex rows are centered in the window
When an item is added to one flex row, the width of both increases
To the left and right of each flex row, the background matches the flex row's color
The second example has the other thing:
Flex wrap responds to the edges of the window
How can I get all of it?
You just need to remove width from td and justify-content: center to the flex-row class;
Final solution: https://jsfiddle.net/s1gu8L4t/

Flexbox spacing items in nav bar

I'm trying to space items within a nav bar where I have tags and search bar. I tried space-between, but not show how to properly position the search bar to get it to be slightly larger. Is this some combo of flex-end?
Attempt
Mock-Up
The box on the right should be slightly larger.
Code
Container
const TagsContainer = styled.div`
display: flex;
width: 100%;
margin-top: 15px;
`
export default function Tags() {
return (
<TagsContainer>
<Tag>Algorithms</Tag>
<Tag>Videos</Tag>
<Tag>Books</Tag>
<Tag>Tutorials</Tag>
<Tag>Health</Tag>
<Tag>Finance</Tag>
<Tag>Rants</Tag>
<Tag>Stream</Tag>
<Tag>Music</Tag>
<Search />
</TagsContainer>
)
}
Tag
const TagContainer = styled.div`
height: 100%;
max-height: 40px;
opacity: .8;
text-align: center;
`
const TagStyle = styled.span`
font-family: 'Lora';
min-width: 80px;
color: white;
padding: 10px;
border: 1px solid white;
font-size: 15px;
`
export default function Tag({ children }) {
return (
<>
<TagContainer>
<TagStyle>
{ children }
</TagStyle>
</TagContainer>
</>
)
}
Search
const SearchContainer = styled.div`
color: white;
border: 1px solid black;
flex-grow: 2;
min-height: 40px;
height: 100%;
`
export default function Search() {
return (
<>
<SearchContainer>
This is a search box
</SearchContainer>
</>
)
}
Add flex-grow: 1; to TagContainer to allow all flex items to grow

the table border-bottom disappear?

I try to implement a table with large size of data. then due to the performance issue, I just want to render the data in the body window.
But the new render element border disappear.
HTML:
<script src="//unpkg.com/vue#2.5.15/dist/vue.js"></script>
<script type="text/x-template" id="list-template">
<div class='table-body' ref="body" #scroll="handleScroll">
<div class="list-view">
<div
class="list-view-phantom"
:style="{
height: contentHeight
}">
</div>
<div class="list-view-colgroup">
<div class="list-view-item-col-g" v-for='count in 5'>
</div>
</div>
<div
ref="content"
class="list-view-content">
<ul
class="list-view-item"
:style="{
height: itemHeight + 'px'
}"
v-for="item in visibleData" :key='item.value'>
<li class="list-view-item-col" v-for='count in 5'>
{{item.value+count}}
</li>
</ul>
</div>
</div>
</div>
</script>
<div id="app">
<template>
<list-view :data="data"></list-view>
</template>
</div>
JS:
const ListView = {
name: 'ListView',
template: '#list-template',
props: {
data: {
type: Array,
required: true
},
itemHeight: {
type: Number,
default: 30
}
},
computed: {
contentHeight() {
return this.data.length * this.itemHeight + 'px';
}
},
mounted() {
this.updateVisibleData();
},
data() {
return {
visibleData: []
};
},
methods: {
updateVisibleData(scrollTop) {
scrollTop = scrollTop || 0;
const visibleCount = Math.ceil(this.$el.clientHeight / this.itemHeight);
const start = Math.floor(scrollTop / this.itemHeight);
const end = start + visibleCount;
this.visibleData = this.data.slice(start, end);
this.$refs.content.style.transform = `translate3d(0, ${ start * this.itemHeight }px, 0)`;
},
handleScroll() {
const scrollTop = this.$refs.body.scrollTop;
this.updateVisibleData(scrollTop);
}
}
};
new Vue({
components: {
ListView
},
data() {
const data = [];
for (let i = 0; i < 1000; i++) {
data.push({ value: i });
}
return {
data
};
}
}).$mount('#app')
code example:
https://jsfiddle.net/441701328/hq1ej6bx/6/
you can see only the data render in the first time can have border.
could anyone help?
thanks all!!!
table-row-group does not work with divs you can change the whole layout and use tables or instead you can do it like this.
.list-view-item {
padding: 5px;
color: #666;
display: table;
line-height: 30px;
box-sizing: border-box;
border-bottom: 1px solid red;
min-width: 100vw;
}
.list-view-item-col {
display: table-cell;
min-width: 50px;
}
jsfiddle for table-row-group
Hope it helps.
Use display :flex for list-view-item class, Try with following code.Hope it will work fine for you.
.list-view-item {
padding: 5px;
color: #666;
display: flex;
flex-basis: 100%;
flex-direction: row;
line-height: 30px;
box-sizing: border-box;
border-bottom: 1px solid red;
}
Try with this CSS. I hope it will works for you.
.list-view-item {
padding: 5px;
color: #666;
display:table;
line-height: 30px;
box-sizing: border-box;
border-bottom: 1px solid green;
}
I try to change the js code :
this.$refs.content.style.transform = `translateY(0, ${ start * this.itemHeight }px, 0)`;
to :
this.$refs.content.style.transform = `translateY(${ start * this.itemHeight }px)`;
and add a css to div class is list-view:
transform:translateY(0)px;
then the border showed.
don't understand why this action work!
.list-view-item {
padding: 5px;
color: #666;
display: flex;
flex-basis: 100%;
flex-direction: row;
line-height: 30px;
box-sizing: border-box;
border-bottom: 1px solid red;
}

ionic 3 grid view with two col per row

I have one screen , which will have the data to display from database. I already tried grid view in ionic 1 its fine. But ionic 3 i don't know how to do the grid view.From database i will get like 10 or 11 or 13 category names , that names i need to display in grid view with background some image.
I know how to display background image.But i need to display 2 col per row.here my code that i will use to fetch data from database.....
another(loading:any) {
this.subcatdata = { CatID: this.categoryid };
this.authService.subcatte(this.subcatdata).then((result) => {
this.data = result;
console.log(this.data);
if (this.data.status == 1) {
this.Catdata = this.data.SubCatgeoryList;
for (let i = 0; i < this.Catdata.length; i++) {
console.log(this.Catdata[i].SubCategoryName);
}
}
else if (this.data.status == 0) {
let alert = this.alertCtrl.create({
title: 'Error',
subTitle: 'Please Enter Valid Username & Password',
buttons: ['OK']
});
alert.present();
}
loading.dismiss();
}, (err) => {
loading.dismiss();
});
}
In my above code i will get the subcatgory name by using below code :
for (let i = 0; i < this.Catdata.length; i++) {
console.log(this.Catdata[i].SubCategoryName);
}
In my html :
<div class="item item-body no-padding" style="border-width: 0px !important;">
<div class="row no-padding" *ngFor="let data of Catdata; let i = index" (click)="openresources(Catdata[i].SubCatID)">
<div class="col col-50 custom-design2" style="background: url(background url) no-repeat center;background-size: cover;">
<div class="custom-design1"><span class="grid-title">{{Catdata[i].SubCategoryName}}</span></div>
</div>
</div>
</div>
My scss :
.gallery {
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
}
div.no-padding, ion-item.no-padding {
padding: 0 !important;
}
div.custom-design2 {
height: 153px;
padding: 1px;
}
.swiper-pagination-bullet-active {
opacity: 1;
background: #FFF !important;
}
.no-scroll .scroll-content{
overflow: hidden;
}
div.custom-design1 {
text-align: center;
padding: 1px;
height: 153px;
vertical-align: middle;
position: relative;
background-color: rgba(0,0,0,0.5);
color: #fff;
width: 100%;
}
div.custom-design1.extended {
height: 153px;
}
span.grid-title {
font-weight: 700;
position: absolute;
top: 50%;
left: 0;
right: 0;
}
.transparent {
background: transparent !important;
}
.full_height {
height: 100% !important;
border: none;
}
Now in my screen its coming like one data per row, with full row background.
But what i need is two col ( 2 data/sub cat name per row).I know we need to use index + 1, but in ionic 3 i dont know how to do.
If any help, that will be helpfull
Thanks.