Vue.draggable - swapping between two list - javascript

I have a problem. I need to organaze two draggable table like swapping.
1 table: have 7 fixed items.
2 table: Other items.
When you swapping items from the second table to the first, a dialog box should appear to confirm swapping.
I did swap for first list, but not understand how to do for two table.
my code now:
<template>
<table>
<draggable v-model="items" tag="tbody" :move="handleMove" #end="handleDragEnd" :options="{animation:500}">
<tr v-for="item in items" :key="item.id">
<td>{{ item.id }}</td><td>{{ item.name }}</td><td>{{ item.age }}</td>
</tr>
</draggable>
</table>
</template>
<script>
import draggable from "vuedraggable";
export default {
components: {
draggable,
},
data() {
const items = [
{ id: 1, name: "Bianka Effertz", age: 37 },
{ id: 2, name: "Mckayla Bogan", age: 20 },
{ id: 3, name: "Estevan Mann", age: 17 },
{ id: 4, name: "Cloyd Ziemann", age: 55 }
]
return { items }
},
computed: {
orders() {
return this.items.map(x => x.id)
}
},
methods: {
handleDragEnd() {
this.$toast.show('dragEnd')
this.futureItem = this.items[this.futureIndex]
this.movingItem = this.items[this.movingIndex]
const _items = Object.assign([], this.items)
_items[this.futureIndex] = this.movingItem
_items[this.movingIndex] = this.futureItem
this.items = _items
},
handleMove(e) {
const { index, futureIndex } = e.draggedContext
this.movingIndex = index
this.futureIndex = futureIndex
return false // disable sort
}
}
</script>

Related

Setting value for each item in for loop to true or false

I am trying to create a v-for that shows a list of exercises containing several sets. I have created a loop with a row for each set underneath each exercise.
my data looks like this.
const exercises = [
{ id: 1, name: exercise1, sets: 3 },
{ id:2, name: exercise2, sets: 2 }
{ id:3, name: exercise3, sets: 4 }
]
And my component looks something like this:
<template v-for="exercise in exercises" :key="exercise.id">
<span> {{ exercise.name }} </span>
<template v-for="set in exercise.sets" :key="set">
<span #click="completeSet()"> {{ set }} </span>
</template>
</template>
Now I want to be able to mark each set as completed by setting the value on each set to either true or false through a click event. But I am not sure about how to do this since each set doesn't have a property to set a value because it's looping through a number.
What would be the right approach to this problem?
First and foremost, you can't loop through a number. To be able to loop the sets, you'd have to
<template v-for="let set = 0; set < exercise.sets; set++" :key="set">
<span #click="completeSet()"> {{ set }} </span>
</template>
However, setting a property on a number is equally impossible. You have to prepare your data to be able to make that adjustment:
const exercises = [
{ id: 1, name: 'exercise1', sets: 3 },
{ id: 2, name: 'exercise2', sets: 2 } ,
{ id: 3, name: 'exercise3', sets: 4 }
].map(exercise => ({
id: exercise.id,
name: exercise.name,
sets: Array.from(
{ length: exercise.sets },
() => ({ completed: false })
),
}))
You can create array with finished sets and compare it (try the snippet pls):
new Vue({
el: "#demo",
data() {
return {
exercises: [{ id: 1, name: 'exercise1', sets: 3 }, { id: 2, name: 'exercise2', sets: 2 }, { id: 3, name: 'exercise3', sets: 4 }],
finishedSets: []
}
},
computed: {
checkAll() {
return this.exercises.reduce((acc, curr) => acc + curr.sets, 0) === this.finishedSets.length
}
},
methods: {
compareObjects(o1, o2) {
return Object.entries(o1).sort().toString() !== Object.entries(o2).sort().toString()
},
findObject(id, set) {
return this.finishedSets.find(f => f.id === id && f.set === set)
},
completeSet(id, set) {
this.findObject(id, set) ?
this.finishedSets = this.finishedSets.filter(f => {return this.compareObjects(f, this.findObject(id, set))}) :
this.finishedSets.push({id, set})
},
isFinished(id, set) {
return this.findObject(id, set) ? true : false
},
}
})
.set {
width: 70px;
cursor: pointer;
}
.finished {
background-color: seagreen;
}
.finished__not {
background-color: tomato;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div v-for="exercise in exercises" :key="exercise.id">
<span> {{ exercise.name }} </span>
<div v-for="set in exercise.sets" :key="set">
<div #click="completeSet(exercise.id, set)" class="set" :class="isFinished(exercise.id, set) ? 'finished' : 'finished__not'"> {{ set }} <span>
<span v-if="isFinished(exercise.id, set)">finished</div>
</div>
</div>
<button v-if="checkAll">submit</button>
<p>{{finishedSets}}</p>
</div>

Join multiple array maps in ReactJs

im new in reactjs, is that possible to do in reactjs,like mysql inner join, here my sample code.
{dataCustomer.map((customer) => (
<tr>
<td>{customer.custid}</td>
<td>{customer.custname}</td>
<td>{customer.mailingaddr}</td>
<td>{customer.stateid}</td>
<td>{customer.districtid}</td>
<td>{customer.postcode}</td>
</tr>
))}
{dataState.map((state) => (
<tr>
<td>{state.stateid}</td>
<td>{state.statename}</td>
</tr>
))}
how to join array map data from customer.stateid & state.stateid to call state.statename without use SQL syntax?..Is that possible?
This should work
let joinedData=[]
dataCustomer.forEach((customer)=>{
let st= dataState.filter((state)=>state.stateid===customer.stateid);
if(st.length>0)
joinedData.push({...customer,statename:st[0].statename})
})
joinedData array will now contain the customer objects with the corresponding statename and you can use it as
{joinedData.map((customer) => (
<tr>
<td>{customer.custid}</td>
<td>{customer.custname}</td>
<td>{customer.mailingaddr}</td>
<td>{customer.stateid}</td>
<td>{customer.districtid}</td>
<td>{customer.postcode}</td>
<td>{customer.statename}</td>
</tr>
))}
It is not exactly the same but I think the function below that I wrote can help you:
let customers = [{
name: "Foo",
stateid: 1
},
{
name: "Boo",
stateid: 3
},
{
name: "Goo",
stateid: 2
},
{
name: "Zoo",
stateid: 2
},
];
let states = [{
name: "State1",
id: 1
},
{
name: "State2",
id: 2
},
];
function join(customers, states, column1, column2) {
return customers.map(customer => {
states.forEach(state => {
if (customer[column1] === state[column2]) {
customer["state"] = { ...state };
}
})
return customer;
})
}
console.log(join(customers, states, "stateid", "id"))
Note that column1 and column2 parameters are the properties you are joining on.

Vue.js find which component emitted an event

I'm trying to have a component representing a shopping item.
I'll have one of this component for every item in my shopping list.
I don't know how to update the parent data (the shopping list) when the child is edited (the shopping item)
Shopping List
<template>
<div id="app">
<shopping-item
v-for="(item, index) in shoppingList"
:key="index"
:propsName="item.name"
:propsQuantity="item.quantity"
#shoppingItemEdited="handleEdit"
></shopping-item>
</div>
</template>
<script>
import ShoppingItem from "./components/ShoppingItem.vue";
export default {
name: "App",
components: {
ShoppingItem,
},
data() {
return {
shoppingList: [
{ name: "apple", quantity: 8 },
{ name: "banana", quantity: 3 },
{ name: "kiwi", quantity: 7 },
{ name: "peach", quantity: 5 },
],
};
},
methods: {
handleEdit(itemEdited) {
// How to get the index of the shopping-item that has been updated ?
// shoppingList[???] = itemEdited
console.log(itemEdited);
// => {name: "white peach", quantity: "6"}
},
},
};
</script>
Shopping Item
<template>
<div>
<input v-model="name" placeholder="ex: banana" #change="updateParent" />
<input
v-model="quantity"
type="number"
placeholder="ex: 3"
#change="updateParent"
/>
</div>
</template>
<script>
export default {
data() {
return {
name: "",
quantity: null,
};
},
props: {
propsName: String,
propsQuantity: Number,
},
created() {
this.name = this.propsName;
this.quantity = this.propsQuantity;
},
methods: {
updateParent() {
this.$emit("shoppingItemEdited", {
name: this.name,
quantity: this.quantity,
});
},
},
};
</script>
So I have few questions:
How can I know witch component emited the event 'shoppingItemEdited' ? If I knew it, I could find out which shoppingList item I should update.
I red I should not update props in the child, so I create data based on props, is that a standard way of doing that ?
this.name = this.propsName;
this.quantity = this.propsQuantity;
Just pass an index to a handler: #shoppingItemEdited="handleEdit(index, $event)"
No it's not "standard" - created hook is called only once when component is created, so if value of prop changes later (from parent), data will not update. It's probably not a problem in your case but usually its better to use computed:
computed: {
name: {
get() { return this.propsName },
set(value) {
this.$emit("shoppingItemEdited", {
name: value,
quantity: this.quantity,
});
}
}
}
...handle event in parent and the change will propagate (by props) to a child

Vue v-for loop - How To Target Component When Array is Filtered

I am using a computed value to dynamically filter an array ("orders").
The computed .filter() function allows the user to dynamically search by order number, name or reference:
data() {
return {
orders: [],
search: "" // search string from a text input
};
},
computed: {
filtered:
return this.orders.filter(order => {
const s =
order.order_number + order.reference + order.name;
const su = s.toUpperCase();
return su.match(this.search.toUpperCase());
});
}
I am using a v-for loop to render the search results as follows:
<tbody v-for="(order, index) in filtered" :key="order.id">
<tr>
<td #click="add_events(order, index)>{{order.order_number}}</td>
<td>{{order.reference}}</td>
<td>{{order.name}}</td>
...
</tr>
</tbody>
I want to use the #click to target a specific component (an object) in the filtered array and use $set to append a value ("objEvents") to that object:
methods: {
add_events (order, index) {
const objEvents= [ external data from an API ]
this.$set(this.orders[index], "events", objEvents)
}
}
However the index of the component in the filtered array ("filtered") is not the same as its index in the original array ("orders") and so the add_events method targets the wrong component.
Can I use key to target the correct component? or is there some other way to identify the target component in the filtered array?
There's no need to track index. filtered is just an array of references to the original objects in orders, so you could modify the order iterator in add_events() to achieve the desired effect:
this.$set(order, 'events', objEvents);
new Vue({
el: '#app',
data() {
return {
orders: [
{id: 1, order_number: 111, name: 'John', reference: 'R111'},
{id: 2, order_number: 222, name: 'Bob', reference: 'R222'},
{id: 3, order_number: 333, name: 'Bob', reference: 'R333'},
],
search: ''
};
},
computed: {
filtered() {
return this.orders.filter(order => {
const s =
order.order_number + order.reference + order.name;
const su = s.toUpperCase();
return su.match(this.search.toUpperCase());
});
}
},
methods: {
add_events(order, index) {
const objEvents = [
{id: 1, name: 'Event 1'},
{id: 2, name: 'Event 2'},
{id: 3, name: 'Event 3'}
];
this.$set(order, "events", objEvents);
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<input type="text" v-model="search" placeholder="Search">
<table>
<tbody v-for="(order, index) in filtered" :key="order.id">
<tr>
<td #click="add_events(order, index)">{{order.order_number}}</td>
<td>{{order.reference}}</td>
<td>{{order.name}}</td>
<td>{{order.events}}</td>
</tr>
</tbody>
</table>
<pre>{{orders}}</pre>
</div>
You could map the original array and add the an origIndex property to each item as follows :
computed:{
filtered(){
let mapped= this.orders.map((item,i)=>{
let tmp=item;
tmp.origIndex=i;
return tmp;
});
return this.mapped.filter(order => {
const s =
order.order_number + order.reference + order.name;
const su = s.toUpperCase();
return su.match(this.search.toUpperCase());
});
}
}//end computed
In your template use the origIndex property instead of index
<tbody v-for="(order, index) in filtered" :key="order.id">
<tr>
<td #click="add_events(order, order.origIndex)>{{order.order_number}}</td>
<td>{{order.reference}}</td>
<td>{{order.name}}</td>
...
</tr>
</tbody>

Vue JS use array to filterBy another array?

Yep, it's me again. I'm trying to filter an array based on an array of strings. So while a single string filter is easy with Vue...
<div v-for="item in items | filterBy 'words' in 'property'">
...multiple search strings becomes more complex. There's been several questions about how to do this on StackOverflow already, but very few answers. Currently I'm trying to repurpose the custom filter found here for my needs, but it's not working.
My use case:
I have an array of groups (checkboxes) that the user can select to filter an array of people. Each person is assigned to one or more groups so if any one of their groups is selected by the user, that person should show up.
So my templates look like this:
<template v-for="group in ensemble_groups">
<input name="select_group[]" id="group_#{{ $index }}"
:value="group"
v-model="selected_groups"
type="checkbox">
<label for="group_#{{ $index }}">#{{ group }}</label>
</template>
<template v-for="person in cast | filterBy selectGroups">
<div>#{{ person.actor_name }}</div>
</template>
You see my custom filter selectGroups there? Here's my Vue arrays:
selected_groups: [],
ensemble_groups: ["Leads","Understudies","Children","Ensemble"],
cast: [
{
actor_name: "Dave",
groups: ["Leads"],
},
{
actor_name: "Jill",
groups: ["Leads"],
},
{
actor_name: "Sam",
groups: ["Children","Ensemble"],
},
{
actor_name: "Austin",
groups: ["Understudies","Ensemble"],
},
],
And finally here's the custom filter. I can't tell if it's even being triggered or not, because when I click on a group checkbox, nothing happens.
filters: {
selectGroups: function() {
if (!selected_groups || selected_groups.length === 0) {
return cast;
}
return this.recursiveFilter(cast, selected_groups, 0);
}
},
methods: {
recursiveFilter: function(cast, selected_groups, currentPosition) {
if (currentPosition+1 > selected_groups.length)
return cast;
var new_cast;
new_cast = cast.filter(function(person) {
for (group of person.groups) {
if (group.value == selected_groups[currentPosition])
return true;
}
});
return this.recursiveFilter(new_cast, selected_groups, currentPosition+1);
}
}
So if the user selects Leads only Dave and Jill should appear. If the user then checks Children, Dave, Jill, and Sam should appear. I'm so close!
I would use a computed property instead of a filter and a method.
I'd go through each cast member and if any of their groups is in selected_groups I'd allow it through the filter. I'd so this using Array.some.
results: function() {
var self = this
return self.cast.filter(function(person) {
return person.groups.some(function(group) {
return self.selected_groups.indexOf(group) !== 1
})
})
},
Here's a quick demo I set up, might be useful: http://jsfiddle.net/crswll/df4Lnuw6/8/
Since filters are deprecated (in v-for, see Bill's comment), you should get into the habit of making computeds to do filtery things.
(If you're on IE, you can't use includes without a polyfill; you can use indexOf...>=0 instead.)
new Vue({
el: '#app',
data: {
selected_groups: [],
ensemble_groups: ["Leads", "Understudies", "Children", "Ensemble"],
cast: [{
actor_name: "Dave",
groups: ["Leads"],
}, {
actor_name: "Jill",
groups: ["Leads"],
}, {
actor_name: "Sam",
groups: ["Children", "Ensemble"],
}, {
actor_name: "Austin",
groups: ["Understudies", "Ensemble"],
}, ]
},
computed: {
filteredCast: function() {
const result = [];
for (const c of this.cast) {
if (this.anyMatch(c.groups, this.selected_groups)) {
result.push(c);
}
}
return result;
}
},
methods: {
anyMatch: function(g1, g2) {
for (const g of g1) {
if (g2.includes(g)) {
return true;
}
}
return false;
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<div id="app">
<template v-for="group in ensemble_groups">
<input name="select_group[]" id="group_#{{ $index }}" :value="group" v-model="selected_groups" type="checkbox">
<label for="group_#{{ $index }}">#{{ group }}</label>
</template>
<template v-for="person in filteredCast">
<div>#{{ person.actor_name }}</div>
</template>
</div>
var demo = new Vue({
el: '#demo',
data: {
search: 're',
people: [
{name: 'Koos', age: 30, eyes:'red'},
{name: 'Gert', age: 20, eyes:'blue'},
{name: 'Pieter', age: 12, eyes:'green'},
{name: 'Dawid', age: 67, eyes:'dark green'},
{name: 'Johan', age: 15, eyes:'purple'},
{name: 'Hans', age: 12, eyes:'pink'}
]
},
methods: {
customFilter: function(person) {
return person.name.indexOf(this.search) != -1
|| person.eyes.indexOf(this.search) != -1
;
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="demo">
<input type="text" class="form-control" v-model="search"/>
<br/>
<table class="table">
<thead>
<tr>
<th>name</th>
<th>eyes</th>
<th>age</th>
</tr>
</thead>
<tr v-for="person in people | filterBy customFilter">
<td>{{ person.name }}</td>
<td>{{ person.eyes }}</td>
<td>{{ person.age }}</td>
</tr>
</table>
</div>

Categories