VueDraggable multiDrag and selected-class props not work in Nuxt.js - vue.js

I currently use vue-draggable to make drag and drop component in my latest Nuxt project.
My package.json is like that (with the latest version of vuedraggable)
"nuxt": "^2.14.12",
"vuedraggable": "^2.24.3",
// I also use those plugins
"#nuxtjs/vuetify": "^1.11.3",
First of all, I tried to make mini-sample referred to this sample.
And I made code like below
I just changed :list to v-model in draggable tag, and use nuxt-property-decorator instead of export default. referred to latest vue-draggable
<template>
<div id="app">
<div class="flex">
<div class="flex-1">
Target Category Items: {{ targetCategoryItems.length }}
<div class="draggable-container">
<draggable
v-model="targetCategoryItems"
draggable=".element"
group="elements"
:multi-drag="true"
class="draggable"
selected-class="selected-item"
>
<div
v-for="element in targetCategoryItems"
:key="element.ItemID"
class="draggable-element"
>
<div class="item-text">
<div>[{{ element.ItemID }}]</div>
<div>{{ element.ItemName }}</div>
</div>
</div>
</draggable>
</div>
</div>
<div class="flex-1">
Source Category Items: {{ sourceCategoryItems.length }}
<div class="draggable-container">
<draggable
v-model="sourceCategoryItems"
draggable=".element"
group="elements"
:multi-drag="true"
class="draggable"
selected-class="selected-item"
#select="selectItems"
>
<div
v-for="element in sourceCategoryItems"
:key="element.ItemID"
class="draggable-element"
>
<div class="item-text">
<div>[{{ element.ItemID }}]</div>
<div>{{ element.ItemName }}</div>
</div>
</div>
</draggable>
</div>
</div>
</div>
</div>
</template>
<script>
import draggable from "vuedraggable";
export default {
components: { draggable },
data() {
return {
sourceCategoryItems: [
{ ItemID: "566GR", ItemName: "Leaf Urn", PhotoName: "" },
{ ItemID: "575GD", ItemName: "Italian Villa Planter", PhotoName: "" },
{ ItemID: "576GR", ItemName: "Palm Topiary Planter", PhotoName: "" },
],
targetCategoryItems: [
{ ItemID: "F238", ItemName: "Cuadrado Side Table", PhotoName: "" },
{ ItemID: "F239", ItemName: "Triangulo Side Table", PhotoName: "" },
{ ItemID: "F285", ItemName: "Kew Occassional Table", PhotoName: "" },
{ ItemID: "F286", ItemName: "Tuileries Coffee Table", PhotoName: "" },
{ ItemID: "F296", ItemName: "Heligan Table", PhotoName: "" },
],
};
},
methods: {
toggle(todo) {
todo.done = !todo.done;
},
selectItems(event) {
console.log(event.items);
},
},
};
</script>
<style scoped>
.flex {
display: flex;
}
.flex-1 {
flex: 1;
}
.draggable-container {
border: 1px solid black;
padding: 10px;
margin: 10px;
}
.draggable-element {
padding: 10px;
margin: 10px;
background-color: lightgrey;
}
.selected-item {
background-color: red;
opacity: 0.5;
}
</style>
The result is like that
It seems that there is no problem, but I clicked the left side table, background-color never changed and I couldn't drag and drop any items.
I also checked the same issues, "SortableJS / Vue.Draggable multi-drag option not working" and "Use with Nuxt? #525". So I tried to make drag.js in src/plugins, but it doesn't work either.
So, how can I correctly use muli-drag and selected-class props in Nuxt?
This is issues Github repository.

Related

Horizontally draggable quasar q-cards using vue.draggable.next

I've seen a few 'solutions' online about this but to no avail for us. We're working with the quasar framework and trying to setup a horizontally wrapped set of that are are draggable (for re-ordering). Below is the basic stub of the code we're working with to get this going. However, we're not able to get the cards to layout horizontally everything is stacked in a list. What are we doing wrong?
<template>
<draggable
v-model="items"
group="items"
direction="horizontal"
item-key="id"
#start="drag = true"
#end="drag = false"
>
<template #item="{ element }">
<q-card class="card">
<q-img src="~assets/temp/bb-auto.png" :alt="`Image ${element.name}`">
<div class="absolute-bottom text-subtitle2 text-center">
Image {{ element.name }}
</div>
</q-img>
</q-card>
</template>
</draggable>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import draggable from 'vuedraggable';
const drag = ref(false);
const items = ref([
{ name: 'item1' },
{ name: 'item2' },
{ name: 'item3' },
{ name: 'item4' },
{ name: 'item5' },
]);
</script>
<style scoped>
.card {
max-height: 200px;
max-width: 200px;
display: flex;
}
</style>
Turns out our overall structure was incorrect and once we sorted that out was able to back this into the draggable. We then had to basically treat our as the row.
<template>
<draggable
v-model="items"
group="items"
class="q-pa-md row items-start q-gutter-md"
direction="horizontal"
item-key="name"
#start="drag = true"
#end="drag = false"
>
<template #item="{ element }">
<q-card class="card">
<q-img src="~assets/temp/bb-auto.png" :alt="`Image ${element.name}`">
<div class="absolute-bottom text-subtitle2 text-center">
Image {{ element.name }}
</div>
</q-img>
</q-card>
</template>
</draggable>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import draggable from 'vuedraggable';
const drag = ref(false);
const items = ref([
{ name: 'item1' },
{ name: 'item2' },
{ name: 'item3' },
{ name: 'item4' },
{ name: 'item5' },
]);
</script>
<style scoped>
.card {
width: 100%;
max-height: 200px;
max-width: 200px;
display: flex;
}
</style>

Opening a modal from each row in table of Bootstrap-Vue

I'm using Vue2 and Bootstrap-Vue. I have a table with data (I use b-table). I want to have "edit" option on each row in order to edit the table. This option (which is an icon of gear) will open a modal and display a view boxes. In my view I have:
<template>
<div>
<b-table class="text-center" striped hover
:items="items"
:bordered=tableBordered
:fields=tableFields
:label-sort-asc=tableLabelSortAsc>
<template #cell(view)="data">
<a target="_blank" rel="noopener" class="no-link" :href="data.item.url">
<b-icon icon="eye-fill"/>
</a>
</template>
<template #cell(edit)="data">
<b-icon icon="gear-fill"/>
<edit-info-modal :data="data"/>
</template>
</b-table>
</div>
</template>
<script>
import EditInfoModal from './EditInfoModal.vue';
import { BIcon } from 'bootstrap-vue';
export default {
components: {
'b-icon': BIcon,
'edit-info-modal': EditInfoModal
},
data() {
return {
tableBordered: true,
tableLabelSortAsc: "",
tableFields: [
{ sortable: false, key: 'edit', label: 'edit' },
{ sortable: true, key: 'comments', label: 'comments' },
{ sortable: false, key: 'view', label: 'view' }
],
items: [
{
"comments": "test",
"url": "some_url"
}
]
}
}
}
</script>
<style scoped>
div {
margin: auto 0;
width: 100%;
}
a.no-link {
color: black;
text-decoration: none;
}
a:hover.no-link {
color: black;
text-decoration: none;
cursor: pointer;
}
</style>
It creates a table with three columns - the view column (with eye icon) which redirects to the url, the comments column and the edit column (with gear icon) which should open the modal.
Now, I'm trying to have the modal in a separated Vue file called EditInfoModal:
<template>
<div>
<b-modal id="modal-1" title="BootstrapVue">
<p class="my-4">Hello from modal!</p>
</b-modal>
</div>
</template>
<script>
import { BModal } from 'bootstrap-vue';
export default {
props: {
data: Object
},
components: {
'b-modal': BModal
}
}
</script>
<style scoped>
div {
margin: auto 0;
width: 100%;
}
</style>
First of all, it does not open the modal. Reading over the internet I noticed that I should add isModalOpen field and update it each time and then create the watch method. But here I have a modal for each row. What is the recommended way to keep track of the opened modal (only one is opened at any given time)?
Step 1: install BootstrapVue package and references in main.js
import { BootstrapVue, BootstrapVueIcons } from "bootstrap-vue";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
Vue.use(BootstrapVue);
Vue.use(BootstrapVueIcons);
Step 2: App.vue component
<template>
<div id="app">
<b-table
class="text-center"
striped
hover
:items="items"
:bordered="tableBordered"
:fields="tableFields"
:label-sort-asc="tableLabelSortAsc">
<template #cell(view)="data">
<a target="_blank" rel="noopener" class="no-link" :href="data.item.url">
<b-icon icon="eye-fill" />
</a>
</template>
<template #cell(edit)="data">
<b-icon icon="gear-fill" #click.prevent="editTable(data)" />
</template>
</b-table>
<edit-info-modal :data="data" :showModal="showModal" />
</div>
</template>
<script>
import { BIcon, BTable } from "bootstrap-vue";
import EditInfoModal from "./components/EditInfoModal.vue";
export default {
name: "App",
components: {
"b-table": BTable,
"b-icon": BIcon,
"edit-info-modal": EditInfoModal,
},
data() {
return {
tableBordered: true,
tableLabelSortAsc: "",
tableFields: [
{ sortable: false, key: "edit", label: "edit" },
{ sortable: true, key: "comments", label: "comments" },
{ sortable: false, key: "view", label: "view" },
],
items: [
{
comments: "Vue CRUD Bootstrap app",
url: "https://jebasuthan.github.io/vue_crud_bootstrap/",
},
{
comments: "Google",
url: "https://www.google.com/",
},
],
data: "",
showModal: false,
};
},
methods: {
editTable(data) {
this.data = Object.assign({}, data.item);;
this.showModal = true;
// this.$root.$emit("edit-table", Object.assign({}, data));
// this.$bvModal.show("modal-1");
},
},
};
</script>
<style scoped>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
div {
margin: auto 0;
width: 100%;
}
a.no-link {
color: black;
text-decoration: none;
}
a:hover.no-link {
color: black;
text-decoration: none;
cursor: pointer;
}
</style>
Step 3: Child component EditInfoModal.vue
<template>
<div>
<b-modal v-model="showModal" id="modal-1" title="Edit Table">
<p class="my-4">Hello from modal!</p>
<p>Comments: {{ data.comments }}</p>
<p>
URL: <a :href="data.url">{{ data.url }}</a>
</p>
</b-modal>
</div>
</template>
<script>
import { BModal } from "bootstrap-vue";
export default {
// data() {
// return {
// data: "",
// showModal: "",
// };
// },
props: ["data", "showModal"],
components: {
"b-modal": BModal,
},
// mounted() {
// this.$root.$on("edit-table", (data) => {
// this.data = data.item;
// });
// },
};
</script>
<style scoped>
div {
margin: auto 0;
width: 100%;
}
</style>
DEMO Link

nuxtjs add and remove class on click on elements

I am new in vue and nuxt and here is my code I need to update
<template>
<div class="dashContent">
<div class="dashContent_item dashContent_item--active">
<p class="dashContent_text">123</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">456</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">789</p>
</div>
</div>
</template>
<style lang="scss">
.dashContent {
&_item {
display: flex;
align-items: center;
}
&_text {
color: #8e8f93;
font-size: 14px;
}
}
.dashContent_item--active {
.dashContent_text{
color:#fff;
font-size: 14px;
}
}
</style>
I tried something like this:
<div #click="onClick">
methods: {
onClick () {
document.body.classList.toggle('dashContent_item--active');
},
},
but it changed all elements and I need style change only on element I clicked and remove when click on another
also this code add active class to body not to element I clicked
This is how to get a togglable list of fruits, with a specific class tied to each one of them.
<template>
<section>
<div v-for="(fruit, index) in fruits" :key="fruit.id" #click="toggleEat(index)">
<span :class="{ 'was-eaten': fruit.eaten }">{{ fruit.name }}</span>
</div>
</section>
</template>
<script>
export default {
name: 'ToggleFruits',
data() {
return {
fruits: [
{ id: 1, name: 'banana', eaten: false },
{ id: 2, name: 'apple', eaten: true },
{ id: 3, name: 'watermelon', eaten: false },
],
}
},
methods: {
toggleEat(clickedFruitIndex) {
this.fruits = this.fruits.map((fruit) => ({
...fruit,
eaten: false,
}))
return this.$set(this.fruits, clickedFruitIndex, {
...this.fruits[clickedFruitIndex],
eaten: true,
})
},
},
}
</script>
<style scoped>
.was-eaten {
color: hsl(24, 81.7%, 49.2%);
}
</style>
In Vue2, we need to use this.$set otherwise, the changed element in a specific position of the array will not be detected. More info available in the official documentation.

How can I toggle one state only and undo it when I toggle the next (VueJS)

First, I apologize for the way I phrased the question. I couldn't find a way to word it in one sentence. So, allow me to explain.
I have an API with thousands of items being looped in a v-for. The user will select one and only one at a time from the collection. And upon clicking on it will show on a separate place along with some other data (* so it's not a case of string interpolation. It's an object. I'm just simplifying the code in my jsfiddle to make it less confusing and run without dependencies)
I added a boolean property to the API to toggle it true/false and a method function that does the toggle. And with the help of some v-if and v-show directives I'm hiding the other elements from rendering when they are false.
<div id="app">
<div uk-grid class="card-body">
<div class="uk-width-1-4#m">
<div>1. Select an Item</div>
<div class="pods" v-for="pod in pods" :key="pod.id" :id="pod.id">
<div class="ws-in-col" v-for="workstations in pod.workstations" :key="workstations.id" :id="workstations.id" #click="selectWS(workstations, pod)">
<div class="ws ws-number uk-text-default">{{workstations.number}}</div>
</div>
</div>
</div>
<div class="uk-width-1-4#m">
<div>2. Selected Item</div>
<div class="pods" v-for="pod in pods" :key="pod.id" :id="pod.id" v-show="pod.selected===true">
<div class="ws-in-col" v-for="workstations in pod.workstations" :key="workstations.id" :id="workstations.id" v-show="workstations.selected===true">
<div v-if="workstations.selected === true">
<div class="group">
<div class="ws ws-number uk-text-default">{{workstations.number}}</div>
<div class="ws ws-number uk-text-default">{{workstations.text}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
and in the script
methods: {
selectWS(workstations, pods) {
pods.selected = !pods.selected;
workstations.selected = !workstations.selected;
}
}
However, not only it is very messy and rookie. It's buggy. The only way it works is if the user clicks on one item to show it, and clicks it again to toggle it off before clicking another one to turn it on. That's far from user-friendly.
How can I resolve this in a cleaner and professional way so that if the user clicks on 1.1, it shows 1.1 and if he wants to see 1.2, all he has to do is click on 1.2 without having to turn off 1.1 first?
Here's a JSFIDDLE replicating my problem
Thanks guys. Being the only Vue dev in this place is tough.
Agree with IVO, instead of tracking in-item, use a value(or two) to track selection
The only reason I'm posting separate answer is that I'd recommend using a computed value if you need to have the selected instance available as an object.
new Vue({
el: "#app",
data: {
workstation: null,
pod: null,
pods: [
{
id: "pod1",
number: 1,
selected: false,
"workstations": [
{
id: "ws11",
number: "1.1",
text: "Some text",
selected: false,
close: false
},
{
id: "ws12",
number: "1.2",
text: "Some text",
selected: false,
close: false
},
{
id: "ws13",
number: "1.3",
text: "Some text",
selected: false,
close: false
}
]
},
{
id: "pod2",
number: 2,
selected: false,
"workstations": [
{
id: "ws21",
number: "2.1",
text: "Some text",
selected: false,
close: false
},
{
id: "ws22",
number: "2.2",
text: "Some text",
selected: false,
close: false
},
{
id: "ws23",
number: "2.3",
text: "Some text",
selected: false,
close: false
}
]
}
]
},
computed: {
selection(){
if(this.workstations !== null && this.pod !== null){
let s = this.pods.filter(p => p.id === this.pod).map(p => {
let r = {...p}
r.workstations = p.workstations.filter(w => w.id === this.workstation)
return r
})
if (s.length === 1) return s[0]
}
return false
}
},
methods: {
selectWS(workstations, pods) {
if (this.pod == pods.id && this.workstation == workstations.id){
this.pod = null;
this.workstation = null;
} else{
this.pod = pods.id;
this.workstation = workstations.id;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.7/js/uikit.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.7/css/uikit.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div uk-grid class="card-body">
<div class="uk-width-1-4#m">
<div>1. Select an Item</div>
<div class="pods" v-for="pod in pods" :key="pod.id" :id="pod.id">
<div class="ws-in-col" v-for="workstations in pod.workstations" :key="workstations.id" :id="workstations.id" #click="selectWS(workstations, pod)">
<div class="ws ws-number uk-text-default">{{workstations.number}}</div>
</div>
</div>
</div>
<div class="uk-width-1-4#m">
<div>2. Selected Item</div>
<div v-if="selection" class="pods">
<div class="ws-in-col">
<div v-if="selection.workstations.length > 0">
<div class="group">
<div class="ws ws-number uk-text-default">{{selection.workstations[0].number}}</div>
<div class="ws ws-number uk-text-default">{{selection.workstations[0].text}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
You have to simply set the last selected pod/workstation and then show its properties (please run the snippet in full page otherwise you will be unable to see the right column):
new Vue({
el: "#app",
data: {
selectedPod: null,
selectedWorkstation: null,
pods: [
{
id: "pod1",
number: 1,
selected: false,
"workstations": [
{
id: "ws11",
number: "1.1",
text: "Some text 1",
selected: false,
close: false
},
{
id: "ws12",
number: "1.2",
text: "Some text 2",
selected: false,
close: false
},
{
id: "ws13",
number: "1.3",
text: "Some text 3",
selected: false,
close: false
}
]
},
{
id: "pod2",
number: 2,
selected: false,
"workstations": [
{
id: "ws21",
number: "2.1",
text: "Some text 4",
selected: false,
close: false
},
{
id: "ws22",
number: "2.2",
text: "Some text 5",
selected: false,
close: false
},
{
id: "ws23",
number: "2.3",
text: "Some text 6",
selected: false,
close: false
}
]
}
]
},
methods: {
selectWS(workstation, pod) {
this.selectedPod = pod;
this.selectedWorkstation = workstation;
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.7/js/uikit.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.1.7/css/uikit.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div uk-grid class="card-body">
<div class="uk-width-1-4#m">
<div>1. Select an Item</div>
<div class="pods" v-for="pod in pods" :key="pod.id">
<div class="ws-in-col" v-for="workstation in pod.workstations" :key="workstation.id" #click="selectWS(workstation, pod)">
<div class="ws ws-number uk-text-default">
{{workstation.number}}
{{workstation.text}}
</div>
</div>
</div>
</div>
<div class="uk-width-1-4#m">
<div>2. Selected Item</div>
<div class="pods" v-if="selectedWorkstation">
<div class="ws-in-col">
<div>
<div class="group">
<div class="ws ws-number uk-text-default">{{selectedWorkstation.number}}</div>
<div class="ws ws-number uk-text-default">{{selectedWorkstation.text}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

Repeated data in vue

i`m having a problem with my vue, the problem is im trying to print 2 words, that is 'A.2' and 'B.3', but when im printing it, it just show 'B.3' and 'B.3'. here is my code
this is a simple quiz project, so everytime a user choose option a with true status it should be adding 1 point to the score, i haven`t made that yet.
<template>
<div class="hello">
<h1 v-if="show">hai</h1>
<h1 v-else>hehe</h1>
<p>{{ nama}}</p>
<input type="text" v-model="nama">
<button type="button" v-on:click="hideTitle">Click Me</button>
<h3> 1.Yang Dipakai Di sepatu adalah </h3>
<p>{{ nama}}</p>
<h3 v-for="j in jawaban">
<input type="radio">
{{j}}
</h3>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data : function() {
return{
nama: 'Luthfi',
show: true
},
{
jawaban: 'A.2',
correct: true
},
{
jawaban: 'B.3',
correct: false
},
{
jawaban: 'C.4',
correct: false
}
},
methods: {
hideTitle() {
this.show = !this.show
}
},
mounted: function () {
this.nama = 'Fitra'
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
i expect there is 4 output from option A to D, but it kept showing me same option
In your code, data() returns only one object that contains
{
nama: 'Luthfi',
show: true
}
You must change this like:
data : function() {
return{
nama: 'Luthfi',
show: true,
jawaban: 'A.22',
correct: true,
jawabann: 'B.3'
}
}