Vuedraggable how to swap item between 2 draggable - vuejs3

Hi all, i have a problem in vuedraggable. When i drag "item 2" to "item 3" => I want "item 3" will swap with "item 2".
Please help me.

Assuming you have multiple lists and each list has its own <draggable> element, then you will need to assign a new method handleMove(event) to your <draggable> elements. The event parameter is important because event.draggedContext contains the index of the item that you are trying to move (index) and also the index of the other item which is currently overlapping with your grabbed item (futureIndex). It also contains information from which list is your grabbed item (event.from) and where you want to drop it (event.to). Store these 4 variables somewhere and use them when you are done dragging (function handleDragEnd()).
Inside handleDragEnd() simply swap those 2 items and Vue will update the HTML template.
LIVE DEMO HERE
<template>
<div class="row">
<div class="col-3">
<h3>My LEGO Bionicles</h3>
<draggable
class="list-group"
data-list="list1"
:list="list1"
group="bionicles"
#change="log"
itemKey="id"
:move="handleMoveItem"
#end="handleDragEndItem"
:options="{ animation: 500 }"
>
<template #item="{ element, index }">
<div class="list-group-item" :style="element.style">
{{ element.name }}
</div>
</template>
</draggable>
</div>
<div class="col-3">
<h3>Favourite LEGO Bionicle</h3>
<draggable
class="list-group"
data-list="list2"
:list="list2"
group="bionicles"
#change="log"
itemKey="id"
:move="handleMoveItem"
#end="handleDragEndItem"
:options="{ animation: 500 }"
>
<template #item="{ element, index }">
<div class="list-group-item" :style="element.style">
{{ element.name }}
</div>
</template>
</draggable>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable';
export default {
name: 'two-lists-swap',
display: 'Swapping between 2 lists',
order: 1,
components: {
draggable,
},
data() {
return {
list1: [
{ name: 'TOA Mata Nui', id: 1, style: { background: 'gold' } },
{
name: 'TOA Tahu',
id: 2,
style: { background: 'red', color: 'yellow' },
},
{ name: 'TOA Kopaka', id: 3, style: { background: 'white' } },
{
name: 'TOA Anakin',
id: 4,
style: { background: 'black', color: 'yellow' },
},
],
list2: [
{
name: 'TOA Gali',
id: 5,
style: { background: 'blue', color: 'yellow' },
},
{
name: 'TOA Lewa',
id: 6,
style: { background: 'green', color: 'yellow' },
},
{
name: 'TOA Pohatu',
id: 7,
style: { background: 'brown', color: 'white' },
},
],
};
},
methods: {
handleDragEndItem() {
if (this.originalList === this.futureList) {
this.movingItem = this[this.futureList][this.originalIndex];
this.futureItem = this[this.futureList][this.futureIndex];
if (this.movingItem && this.futureItem) {
let _list = Object.assign([], this[this.futureList]);
_list[this.futureIndex] = this.movingItem;
_list[this.originalIndex] = this.futureItem;
this[this.futureList] = _list;
}
} else {
this.movingItem = this[this.originalList][this.originalIndex];
this.futureItem = this[this.futureList][this.futureIndex];
if (this.movingItem && this.futureItem) {
let _listFrom = Object.assign([], this[this.originalList]);
let _listTo = Object.assign([], this[this.futureList]);
_listTo[this.futureIndex] = this.movingItem;
_listFrom[this.originalIndex] = this.futureItem;
this[this.originalList] = _listFrom;
this[this.futureList] = _listTo;
}
}
document
.querySelectorAll('.list-group-item')
.forEach((el) => (el.style.border = 'none'));
this.$toast.show('dragEnd');
},
handleMoveItem(event) {
document
.querySelectorAll('.list-group-item')
.forEach((el) => (el.style.border = 'none'));
const { index, futureIndex } = event.draggedContext;
this.originalIndex = index;
this.futureIndex = futureIndex;
this.originalList = event.from.getAttribute('data-list');
this.futureList = event.to.getAttribute('data-list');
if (this[this.futureList][this.futureIndex]) {
event.to.children[this.futureIndex].style.border = '2px solid orange';
}
return false; // disable sort
},
},
};
</script>
<style>
.list-group-item {
padding: 5px 10px;
cursor: grab;
}
</style>

Related

I'm not able to change the style of DevExtreme's popup modal dialog using CSS

I have Vue.js app where use DevExtreme's DxDataGrid and DxPopup components. The datagrid contains a list of records (rows). When I double click particular row, the popup modal dialog opens and shows appropriate fields. That's fine.
The issue is I'm not able to change the style of popup modal dialog using CSS selectors in my component. I need to change all - border, colors, size of fields, fonts and so on.
The code looks as following:
The list:
<template>
<div>
<DxDataGrid
:data-source="testArray"
:row-alternation-enabled="true"
:show-borders="true"
:columns="columns"
#row-dbl-click="rowDblClick"
>
</DxDataGrid>
<v-app-types-by-product-modal ref="appTypesByProductModal" />
</div>
</template>
<script>
import "devextreme/dist/css/dx.light.css";
import { DxDataGrid } from "devextreme-vue/data-grid";
import VAppTypesByProductModal from "./v-app-types-by-product-modal.vue";
export default {
components: {
DxDataGrid,
VAppTypesByProductModal,
},
data() {
return {
testArray: [
{ Id: 1, ProductName: "Product 1", Status: "Yes", StageName: "Begin", SignDate: "11.07.2022" },
{ Id: 2, ProductName: "Product 2", Status: "Yes", StageName: "Middle", SignDate: "12.07.2022" },
{ Id: 3, ProductName: "Product 3", Status: "No", StageName: "End", SignDate: "13.07.2022" },
],
columns: [
{ dataField: "Id", caption: "ID", dataType: "number", width: "50px" },
{ dataField: "ProductName", caption: "PRODUCT", dataType: "string", width: "150px" },
{ dataField: "Status", caption: "STATUS", dataType: "string", width: "150px" },
{ dataField: "StageName", caption: "STAGE", dataType: "string", width: "150px"},
{ dataField: "SignDate", caption: "DATA", dataType: "string",width: "150px"},
{
type: "buttons",
width: "150px",
buttons: [
{
hint: "Edit",
icon: "edit",
visible(e) {
return !e.row.isEditing;
},
onClick: (e) => {
this.$refs.appTypesByProductModal.showModal(e.row.data);
},
},
{
hint: "Remove",
icon: "remove",
visible(e) {
return !e.row.isEditing;
},
},
],
},
],
selectedElement: {},
};
},
methods: {
rowDblClick(e) {
this.$refs.appTypesByProductModal.showModal(e.data);
},
},
};
</script>
<style scoped>
</style>
The modal dialog:
<template>
<v-modal-dialog
:visible="isModalVisible"
title="Title"
:width="500"
:height="500"
#hiding="onHiding"
>
<DxForm :form-data="selectedItem" label-mode="static">
<DxSimpleItem data-field="Id" :is-required="true" />
<DxSimpleItem data-field="ProductName" :is-required="true" :editor-options="{disabled: true}" />
<DxSimpleItem data-field="Status" />
<DxSimpleItem data-field="StageName" />
<DxSimpleItem data-field="SignDate" />
</DxForm>
<DxToolbarItem
widget="dxButton"
toolbar="bottom"
:options="okButtonOptions"
/>
<DxToolbarItem
widget="dxButton"
toolbar="bottom"
:options="closeButtonOptions"
/>
</v-modal-dialog>
</template>
<script>
import "devextreme/dist/css/dx.light.css";
import { DxForm, DxSimpleItem } from "devextreme-vue/form";
import { DxToolbarItem } from "devextreme-vue/popup";
import VModalDialog from "./v-modal-dialog.vue";
export default {
components: {
VModalDialog,
DxForm,
DxSimpleItem,
DxToolbarItem,
},
data() {
return {
isModalVisible: false,
selectedItem: {},
okButtonOptions: {
text: "Ok",
onClick: () => {
this.clearFormData();
this.isModalVisible = false;
},
},
closeButtonOptions: {
text: "Close",
onClick: () => {
this.clearFormData();
this.isModalVisible = false;
},
},
};
},
methods: {
showModal(item) {
this.selectedItem = {...item};
this.isModalVisible = true;
},
clearFormData() {
this.selectedItem = null;
},
onHiding() {
this.clearFormData();
this.isModalVisible = false;
},
},
};
</script>
<style scoped>
</style>
The modal dialog base:
<template>
<DxPopup
v-bind="$attrs"
v-on="$listeners"
:hide-on-outside-click="false"
:drag-enabled="true"
position="center"
#hiding="onHiding"
>
<slot v-for="(_, name) in $slots" :name="name" :slot="name" />
</DxPopup>
</template>
<script>
import "devextreme/dist/css/dx.light.css";
import { DxPopup } from "devextreme-vue/popup";
export default {
inheritAttrs: false,
components: {
DxPopup,
},
data() {
return {}
},
methods: {
onHiding() {
this.$emit("hiding");
},
},
};
</script>
<style scoped>
/* ::v-deep .dx-popup-normal {
border-radius: 20px !important;
}
::v-deep .dx-popup-title {
border-bottom: 0px !important;
} */
/* #dxPopupContainer .dx-popup-normal {
border-radius: 20px !important;
}
#dxPopupContainer .dx-toolbar {
border-bottom: 0px;
} */
/* #dxPopupContainer {
color: red;
background-color: green;
} */
/* ::v-deep .dx-overlay-content .dx-popup-normal .dx-popup-draggable .dx-resizable {
border-radius: 20px;
}
::v-deep .dx-toolbar > .dx-widget > dx-visibility-change-handler > dx-collection > dx-popup-title dx-has-close-button {
border-bottom: 0px;
} */
/* #dxPopupContainer .dx-popup-normal {
border-radius: 20px !important;
}
#dxPopupContainer .dx-popup-title {
border-bottom: 0px !important;
} */
</style>
Here's the CodeSandbox demo:
https://codesandbox.io/s/overview-devextreme-data-grid-forked-5cegqw
As you can see I've tried something, but nothing helped.
How to overcome this issue?
Replace
::v-deep .dx-popup-normal {
border-radius: 20px !important;
}
with
:global(.dx-popup-normal) {
border-radius: 20px !important;
}

Font awesome icon in Vue.js does not display

I am trying to add a font-awesome arrow icon via my css code like this:
<style>
.negative {
color: red;
}
.positive {
color: green;
}
.negative::after {
content: "\f106";
}
</style>
I have font-awesome included in my html via CDN. For some reason my icon does not display properly, it just shows a square. Any ideas why and how I can fix it?
Here is the rest of my code, showing the logic behind the displaying of percentages:
<template>
<div>
<v-data-table
:headers="headers"
:items="rowsToDisplay"
:hide-default-footer="true"
class="primary"
>
<template #item.thirtyDaysDiff="{ item }">
<span :class="item.thirtyDaysDiffClass">{{ item.thirtyDaysDiff }}%</span>
</template>
<template #item.sevenDaysDifference="{ item }">
<span :class="item.sevenDaysDiffClass">{{ item.sevenDaysDiff }}%</span>
</template>
</v-data-table>
</div>
</template>
<script>
import axios from 'axios';
export default {
data () {
return {
bitcoinInfo: [],
isPositive: false,
isNegative: false,
headers: [
{
text: 'Currency',
align: 'start',
value: 'currency',
},
{ text: '30 Days Ago', value: '30d' },
{ text: '30 Day Diff %', value: 'thirtyDaysDiff'},
{ text: '7 Days Ago', value: '7d' },
{ text: '7 Day Diff %', value: 'sevenDaysDifference' },
{ text: '24 Hours Ago', value: '24h' },
],
}
},
methods: {
getBitcoinData() {
axios
.get('data.json')
.then((response => {
var convertedCollection = Object.keys(response.data).map(key => {
return {currency: key, thirtyDaysDiff: 0, sevenDaysDifference: 0, ...response.data[key]}
})
this.bitcoinInfo = convertedCollection
}))
.catch(err => console.log(err))
},
calculateDifference(a, b) {
let calculatedPercent = 100 * Math.abs((a - b) / ((a + b) / 2));
return Math.max(Math.round(calculatedPercent * 10) / 10, 2.8).toFixed(2);
},
getDiffClass(a, b) {
return a > b ? 'positive': a < b ? 'negative' : ''
},
calculateSevenDayDifference(item) {
let calculatedPercent = 100 * Math.abs((item['24h'] - item['7d']) / ((item['24h'] + item['7d']) / 2));
return Math.max(Math.round(calculatedPercent * 10) / 10, 2.8).toFixed(2);
}
},
computed: {
rowsToDisplay() {
return Object.keys(this.bitcoinInfo)
.map(key => {
return {
currency: key,
...this.bitcoinInfo[key]
}
}).map((item) => ({
...item,
thirtyDaysDiff: this.calculateDifference(item['7d'], item['30d']),
thirtyDaysDiffClass: this.getDiffClass(item['7d'], item['30d']),
sevenDaysDiff: this.calculateDifference(item['24h'], item['7d']),
sevenDaysDiffClass: this.getDiffClass(item['24h'], item['7d']),
}))
}
},
mounted() {
this.getBitcoinData()
}
}
</script>
have you tried to import your icons inside the template area with <i ...></i>?
here is an working example. Check out the cdn.fontawesome/help-page to get more information.
Vue.createApp({
data () {
return {
isPositive: false,
isNegative: true
}
}
}).mount('#demo')
.negative {
color: red;
}
.positive {
color: green;
}
.neutral {
color: #666;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#next"></script>
<div id="demo">
<i class="fas" :class="isPositive ? 'fa-angle-up positive' : isNegative ? 'fa-angle-down negative' : 'fa-minus neutral' "></i>
<br>
<br>
<button #click="isPositive = !isPositive; isNegative = !isNegative" v-text="'change pos and neg'" />
</div>
so basically you'll bind the icon classes to your own conditions. You could write the conditions for example with the tenary operator into your template area. Hope you get the idea.
Host Fontawesome yourself by following the steps in this Fontawesome documentation.
https://fontawesome.com/docs/web/setup/host-yourself/webfonts
i hope this help.

How to change the label color of Material-UI <TextField/>

How can I change the color from "email" label and make it the same as the border line?
Here's my code:
import React, { Component } from "react";
import { Icon } from "semantic-ui-react";
import { Divider } from "semantic-ui-react";
import { TextField, Button, Grid } from "#material-ui/core";
import PropTypes from "prop-types";
import classNames from "classnames";
import { withStyles } from "#material-ui/core/styles";
let self;
const styles = theme => ({
container: {
display: "flex",
flexWrap: "wrap"
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
width: 280
},
cssLabel: {
color: "#d3d3d3"
},
cssOutlinedInput: {
"&:not(hover):not($disabled):not($cssFocused):not($error) $notchedOutline": {
borderColor: "#d3d3d3" //default
},
"&:hover:not($disabled):not($cssFocused):not($error) $notchedOutline": {
borderColor: "#d3d3d3" //hovered #DCDCDC
},
"&$cssFocused $notchedOutline": {
borderColor: "#23A5EB" //focused
}
},
cssInputLabel: {
color: "#d3d3d3"
},
notchedOutline: {},
cssFocused: {},
error: {},
disabled: {}
});
class NewLoginComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedIn: false,
user: "",
errorMsg: "",
errorMsgLength: "",
loginErrorMsg: "",
flag: false,
password: "",
hidden: true,
valuePassText: "SHOW"
};
self = this;
}
componentDidMount() {
this._isMounted = true;
if (this.props.password) {
this.setState({ password: this.props.password });
}
}
componentDidUpdate(prevProps) {}
render() {
const { classes } = this.props;
let username = "";
let password = "";
return (
<div className="container-fluid backround">
<div className="container" id="loginform">
<div className="form-group">
<div>
<div className="emailInput">
<TextField
className={classes.textField}
id="email-txt-input"
label="Email"
variant="outlined"
inputRef={node => (username = node)}
InputLabelProps={{
classes: {
root: classes.cssLabel,
focused: classes.cssFocused
}
}}
InputProps={{
classes: {
root: classes.cssOutlinedInput,
focused: classes.cssFocused,
notchedOutline: classes.notchedOutline
},
inputMode: "numeric"
}}
/>
</div>
<div className="passwordInput">
<TextField
className={classes.textField}
id="password-txt-input"
label="Password"
variant="outlined"
inputRef={node => (password = node)}
type={this.state.hidden ? "password" : "text"}
value={this.state.password}
onChange={this.handlePasswordChange}
InputLabelProps={{
classes: {
root: classes.cssLabel,
focused: classes.cssFocused
}
}}
InputProps={{
classes: {
root: classes.cssOutlinedInput,
focused: classes.cssFocused,
notchedOutline: classes.notchedOutline
},
inputMode: "numeric"
}}
/>
</div>
</div>
<div className="form-group form">
<Button
variant="contained"
id="normal-signin-Btn"
type={"submit"}
className={"btn btn-primary loginButton"}
>
LogIn
</Button>
</div>
</div>
</div>
</div>
);
}
}
NewLoginComponent.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(NewLoginComponent);
Below is one way to control the focused color of the input label to be the same as the focused color of the border:
cssLabel: {
color: "#d3d3d3",
"&.Mui-focused": {
color: "#23A5EB"
}
},
You can simply use the InputLabelProps in your TextField component , for exemple :
<TextField InputLabelProps={{style : {color : 'green'} }}></TextField>
Try using this CSS code in your CSS file to change its border color:
.css-1d3z3hw-MuiOutlinedInput-notchedOutline{
color: yellow !important;
border: 2px solid yellow !important;
}
and to change its label you can use the following inline style in your <TextField/> :
InputLabelProps={{style : {color : 'yellow'} }}
This method should work if you still can't change it, You can also try changing its color by inspecting it from the browser.
try this: put in the TextField
sx={{
"& label": {
"&.Mui-focused": {
color: 'your color*'
}
}
}}

Vis Network + Vuetify Tabs - Networks won't fill screen

I am having some problems getting some Vis JS Networks to fill the available space under the App Bar correctly in a '' element. The graph element will fill the space, but the 100% I height I have had to apply to both '.v-tabs-items' and '.v-window-item' seems to ignore the padding and creates a scroll-bar, I have also set 100% width and there is no horizontal scroll.
It also doesn't adjust properly when resizing the window, requiring a tab switch to readjust to the correct size.
If I remove the 100% Height and Width then the tab content is constrained to a small rectange.
I have simplified the relevant code and it is below, there are two parts, the App.Vue file which contains the main screen and also the GraphView.Vue file which contains the Component that is used to draw the graph.
App.Vue
<template>
<v-app>
<v-app-bar app>
<v-toolbar-title class="headline text-uppercase">
<span>Test</span>
</v-toolbar-title>
<template v-slot:extension>
<v-tabs v-model="graphTab">
<v-tab v-for="(graph, title) in getGraphData()" :key="title">
{{ title }}
<span style="display: none"> {{ graph }}</span>
</v-tab>
</v-tabs>
</template>
</v-app-bar>
<v-content>
<v-container fluid fill-height>
<v-tabs-items v-model="graphTab">
<v-tab-item v-for="(graph, title) in getGraphData()" :key="title">
<GraphView
:nodes="graph.nodes"
:edges="graph.edges"
:options="defaultVisOptions"
></GraphView>
</v-tab-item>
</v-tabs-items>
</v-container>
</v-content>
</v-app>
</template>
<script>
import GraphView from "./components/GraphView";
import { DataSet } from "vis-network";
const defaultVisOptions = {
autoResize: true,
height: "100%",
width: "100%",
edges: {
arrows: "to"
},
nodes: {
shape: "circle"
}
};
export default {
name: "App",
components: {
GraphView
},
data: () => ({
defaultVisOptions: defaultVisOptions,
graphTab: null
}),
methods: {
getGraphData: function() {
return {
"Graph 1": {
nodes: new DataSet([
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" }
]),
edges: new DataSet([
{ from: 1, to: 3 },
{ from: 1, to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 }
])
},
"Graph 2": {
nodes: new DataSet([
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" }
]),
edges: new DataSet([
{ from: 1, to: 2 },
{ from: 2, to: 3 },
{ from: 3, to: 4 },
{ from: 4, to: 5 }
])
}
};
}
}
};
</script>
<style scoped>
.v-tabs-items {
height: 100%;
width: 100%;
}
.v-window-item {
height: 100%;
width: 100%;
}
</style>
GraphView.Vue
<template>
<div class="graphContainer" ref="graphContainer"></div>
</template>
<script>
import { Network } from "vis-network";
export default {
name: "GraphView",
props: ["nodes", "edges", "options"],
mounted() {
this.network = new Network(
this.$refs.graphContainer,
{ nodes: this.$props.nodes, edges: this.$props.edges },
this.$props.options
);
}
};
</script>
<style scoped>
.graphContainer {
width: 100%;
height: 100%;
}
</style>
As explained above, the expected result is that the Graphs fill the available space without creating scroll bars but this doesn't happen.
I didn't test it with your code but the container should be styled like:
<div style="position: relative; width: 100%; height: 100%;">
<div
style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px;"
ref="graphContainer"
/>
</div>
This way it will fill all available space (you may need to set position: relative on the parent) without stretching it any further.

How to bind a sortable list to vuejs?

I am trying to bind a sortable list in Vue.js, but the underlying data list is not updated:
Vue.component('lessons', {
template: "#lessons-template",
data: function() {
return {
list: ['Item 1', 'Item 2', 'Item 3']
};
},
methods: {
onChange: function(evt) {
console.log(this.list); // allways ['Item 1', 'Item 2', 'Item 3']
}
},
ready: function(value) {
Sortable.create(this.$els.sortable, {
draggable: 'li',
onSort: this.onChange
});
}
});
new Vue({ el: 'body'});
ul { list-style: none;}
ul li { padding: 10px; display: block; background: #EFEFEF; margin-bottom: 5px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<script src="http://rubaxa.github.io/Sortable/Sortable.js"></script>
<div>
<lessons></lessons>
</div>
<template id="lessons-template">
<ul v-el:sortable>
<li v-for="item in list">{{ item }}</li>
</ul>
<pre>{{ list | json }}</pre>
</template>
Here we go:
Vue.js:
Vue.component('lessons', {
template: "#lessons-template",
data: function() {
return {
list: ['Item 1', 'Item 2', 'Item 3']
};
},
methods: {
onChange: function(evt) {
console.log(this.list); // allways ['Item 1', 'Item 2', 'Item 3']
}
},
ready: function(value) {
var self = this;
Sortable.create(this.$els.sortable, {
draggable: 'li',
onSort: this.onChange,
onEnd: function (evt) {
self.list.splice(evt.oldIndex,1);
self.list.splice(evt.newIndex,0,evt.item.id);
},
});
}
});
new Vue({ el: 'body'});
HTML:
<div>
<lessons></lessons>
</div>
<template id="lessons-template">
<ul v-el:sortable>
<li v-for="item in list" id="{{item}}">{{ item }}</li>
</ul>
</template>
Working Vue.js Sortable jsFiddle: https://jsfiddle.net/crabbly/vhsd3wwu/

Resources