Vue-draggable limit list to 1 element - vue.js

Here I am trying to implement cloning an element from rankings list and put it in either of the two lists (list1 & list2). Everything seems to be working, I am able to drag and put but it looks like binding does not work as the two lists are not affected, because the watchers do not run when I drag an element to a list. Also, the clone function does not print the message to the console. I was using this example as a reference.
<template>
<div>
<div>
<div>
<draggable
#change="handleChange"
:list="list1"
:group="{ name: 'fighter', pull: false, put: true }"
></draggable>
</div>
<div>
<draggable
#change="handleChange"
:list="list2"
:group="{ name: 'fighter', pull: false, put: true }
></draggable>
</div>
</div>
<div>
<div v-for="wc in rankings" :key="wc.wclass">
<Card>
<draggable :clone="clone"
:group="{ name: 'fighter', pull: 'clone', put: false }"
>
<div class="cell" v-for="(fighter, idx) in wc.fighters" :key="fighter[0]">
<div class="ranking">
{{ idx + 1 }}
</div>
<div class="name">
{{ fighter[0] }}
</div>
</div>
</draggable>
</Card>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import draggable from "vuedraggable";
export default {
components: {
draggable
},
data() {
return {
rankings: [],
list1: [],
list2: []
};
},
methods: {
getRankingLabel(label) {
if (!label || label == "NR") return 0;
if (label.split(" ").indexOf("increased") !== -1) return 1;
if (label.split(" ").indexOf("decreased") !== -1) return -1;
},
clone({ id }) {
console.log("cloning");
return {
id: id + "-clone",
name: id
};
},
handleChange(event) {
console.log(event);
}
},
watch: {
// here I am keeping the length of both lists at 1
list1: function(val) {
console.log(val); // nothing prints, as the watcher does not run
if (val.length > 1) {
this.fighter_one.pop();
}
},
list2: function(val) {
console.log(val); // nothing prints, as the watcher does not run
if (val.length > 1) {
this.fighter_two.pop();
}
}
},
created() {
axios
.get("http://localhost:3000")
.then(res => {
this.rankings = res.data;
})
.catch(err => {
console.log(err);
});
}
};
</script>
<style>
</style>

As others have noted in the comments, your problem is likely related the <draggable> tag not containing either a :list prop or v-model.
With that said, you can limit the size of a list to 1 by calling the splice(1) method on the list in the #change event.
<draggable :list="list1" group="fighter" #change="list1.splice(1)">
{{ list1.length }}
</draggable>

Related

v-on does not working why not receive $emit in custom component

I'm create a an component which represents my money field.
My target is on add element in list, set zero on money field to add next element in list...
But, my problem is that not working when send using $emit event to clear input to improve usability.
$emit works as described on image bellow
My money field:
<template>
<div class="input-group" #clear="clearInputField()">
<span>{{ title }}</span>
<input ref="displayMoney" type="text" v-model="displayMoney" #focus="isActive = true" #blur="isActive = false" />
</div>
</template>
<script>
export default {
props: {
title: String,
},
data() {
return {
money: 0,
isActive: false,
};
},
methods: {
clearInputField() {
console.log("Its work event");
this.money = 0;
this.displayMoney = "";
},
},
computed: {
displayMoney: {
get: function () {
if (this.isActive) {
return this.money;
} else {
return this.money.toLocaleString("pt-br", { style: "currency", currency: "BRL" });
}
},
set: function (modifiedMoney) {
let newMoney = parseFloat(modifiedMoney.replace(/[^\d.]/g, "."));
if (isNaN(newMoney) || newMoney.length == 0) {
newMoney = 0;
}
this.$emit("input", newMoney);
return (this.money = parseFloat(newMoney));
},
},
},
};
</script>
My principal component
<template>
<div class="wish-list">
<div class="row">
<div class="input-group">
<span>Digite sua meta: </span>
<input ref="descriptionWish" type="text" v-model="descriptionWish" />
</div>
<MoneyField title="Valor (R$): " v-model="valueWish" #keyup.native.enter="addWish" />
<button id="btnCalculate" #click="addWish()">Adicionar a lista de desejos</button>
</div>
<div class="list-items">
<ul>
<li v-for="wish in wishes" :key="wish">{{ wish }}</li>
</ul>
</div>
</div>
</template>
<script>
import MoneyField from "./Fields/MoneyField";
export default {
components: {
MoneyField,
},
data() {
return {
wishes: [],
valueWish: 0,
descriptionWish: "",
};
},
methods: {
addWish() {
if (!isNaN(this.valueWish) && this.valueWish > 0 && this.descriptionWish.length > 0) {
this.wishes.push(
`${this.descriptionWish} => ${this.valueWish.toLocaleString("pt-BR", { currency: "BRl", style: "currency" })}`
);
this.descriptionWish = "";
console.log("addWish");
this.valueWish = 0;
this.$emit("clear");
this.$refs.descriptionWish.focus();
}
this.valueWish = 0;
},
},
};
</script>
I still don't understand much about vueJS, but I believe it's something related to parent and child elements, but I've done numerous and I can't get my answer.
sorry for my bad english .
The emit sends an event from the child to the parent component not as you've done, to run a method from the child component you could add a ref in the component inside the parent one like :
<MoneyField title="Valor (R$): "
ref="moneyField" v-model="valueWish" #keyup.native.enter="addWish" />
then run this.$refs.moneyField.clearInputField() instead this.$emit("clear")

vue js filtering with search bar

I have created an app that requests from an API the data and creates flexboxes. Now I added a search box and I would like to filter the articles by their contact and/or title.
I've also created a computed property to filter the returned list of items but when I replace in line 11 the paginated('items') with paginated('filteredArticles') that returns nothing.
What did I do wrong?
<template>
<div id="app">
<div class="search-wrapper">
<input type="text"
class="search-bar"
v-model="search"
placeholder="Search in the articles"/>
</div>
<paginate ref="paginator" class="flex-container" name="items" :list="items">
<li v-for="(item, index) in paginated('items')" :key="index" class="flex-item">
<div id="image"><img :src="item.image && item.image.file" /></div>
<div id="date">{{ item.pub_date }}</div>
<div id="title"> {{ item.title }}</div>
<div class="article">{{item.details_en}}</div>
</li>
</paginate>
<paginate-links for="items" :limit="2" :show-step-links="true"></paginate-links>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
items: [],
paginate: ["items"],
search:'',
};
},
created() {
this.loadPressRelease();
},
methods: {
loadPressRelease() {
axios
.get(`https://zbeta2.mykuwaitnet.net/backend/en/api/v2/media-center/press-release/?page_size=61&type=5`)
.then((response) => {
this.items = response.data.results;
});
},
},
computed:{
filteredArticles() {
return this.items.filter(item=>item.includes(this.search))
}
}
};
</script>
You need fields you want to search and connvert search string and fields with toLowerCase() or toUpperCase():
computed : {
filteredArticles() {
if (!this.search) return this.items
return this.items.filter(item => {
return (item.title.toLowerCase().includes(this.search.toLowerCase()) || item.contact.toLowerCase().includes(this.search.toLowerCase()));
})
}
}
Your computed doesn't seem correct. Since items is an array of objects, you'd need to do this:
filteredArticles() {
if (!this.search) {
return this.items;
}
return this.items.filter(item => {
return item.title.includes(this.search);
})
}
Note that this will only search the title field, and it's case sensitive.

How to pass and change index of array in vue?

I have the gist of how to do this, but I'm a beginner in vue, and I'm struggling with how to put it together. I need Control.vue to update the index in Exhibitor.vue. I know I'll have an $emit event happening in Control when I click on the button to pass the index data to the parent, and I'd have to use props to pass data from Exhibitor to its children, but how? I can't understand how to pass the index of an array with my code.
Exhibitor.vue
<template>
<div id="exhibitor">
<section class="exhibitor_info">
<h1 class="exhibitor_name">{{ exhibitors[index].firstName }} {{ exhibitors[index].lastName }}</h1>
<h2>Tag Number: {{ exhibitors[index].tagNum }} <em>{{ exhibitors[index].species }}</em></h2>
</section>
<div class="frame"><img :src="getImgUrl(exhibitors[index].picture)" alt="Exhibitor-Picture" class="image"></div>
</div>
</template>
<script>
export default {
name: 'Exhibitor',
data() {
return {
exhibitors: [],
index: 0
}
},
created: function() {
this.fetchExhibitors();
},
methods: {
fetchExhibitors() {
let uri = 'http://localhost:8081/exhibitor'
this.axios.get(uri).then(response => {
this.exhibitors = response.data
})
},
getImgUrl: function(pic) {
return require('../assets/' + pic)
}
}
}
</script>
Display.vue
<template>
<div id="display">
<exhibitor></exhibitor>
<buyer></buyer>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Buyer from './Buyer.vue';
export default {
components: {
'exhibitor': Exhibitor,
'buyer': Buyer
}
}
</script>
Control.vue
<template>
<div id="control">
<display></display>
<button v-on:click="incrementLeft">Left</button>
<button v-on:click="incrementRight">Right</button>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Display from './Display.vue';
export default{
props: ['exhibitors', 'buyers', 'index'],
data() {
return {
index: 0
}
},
methods: {
incrementRight: function() {
// Note that '%' operator in JS is remainder and NOT modulo
this.index = ++this.index % this.exhibitors.length
},
incrementLeft: function() {
// Note that '%' operator in JS is remainder and NOT modulo
if (this.index === 0) {
this.index = this.exhibitors.length - 1
} else {
this.index = --this.index % this.exhibitors.length
}
}
},
components: {
'display': Display
}
}
</script>
So you can get what you want to happen and there are two ways of making it happen that I can think of. First I will just clarify the terms relating to this because you seem to have them the wrong way around. Let's look at you tier structure which is like this:
Control.vue
contains: Display.vue
contains: Exhibitors.vue & Buyers.vue.
Therefore Control.vue is the parent of Display.vue which is the parent of Buyers.vue and Exhibitors.vue.
Anyway, What we need to do is control the array of exhibitors (and I guess buyers but you didn't include them in your code so I'll do likewise) which is in Exhibitors.vue from Control.Vue even though they don't have a direct parent child relationship. What I've done is set a prop that is passed to Display.vue which uses scoped slots to render the exhibitors from Exhibitors.Vue. Because the left and right buttons need to know when to stop going I have emitted the array length from Exhibitors.vue to Display.vue and again to Control.vue. It all works so heres some code.
//Control.vue
<template>
<div class="content">
<display v-on:finalLength="setIndexLimit" :i="index"></display>
<button #click="changeDown">Down</button>
<button #click="changeUp">Up</button>
<p>{{indLimit}}</p>
</div>
</template>
<script>
import Display from '#/components/Display'
export default {
components: {
Display
},
data: () => ({
index: 0,
indLimit: 0
}),
methods: {
changeUp() {
if (this.indLimit === this.index+1) {
this.index=0
}
else {
this.index ++
}
},
changeDown() {
if (this.index === 0) {
this.index = this.indLimit - 1
}
else {
this.index --
}
},
setIndexLimit(e) {
this.indLimit = e
}
}
}
</script>
and
//Display.vue
<template>
<div id="display">
<p>From Display</p>
<exhibitors v-on:ExLength="setLength">
<p>{{i}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].firstName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].lastName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].tagNum}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].species}}</p>
</exhibitors>
</div>
</template>
<script>
import Child from '#/components/admin/Exhibitors'
export default {
components: {
Exhibitors
},
props: [
'i'
],
data: () => ({
exhibitLength: null
}),
methods: {
setLength(e) {
this.exhibitLength = e
this.$emit('finalLength',e)
}
}
}
</script>
And finally:
//Exhibitors.vue
<template>
<div>
<slot :exhibitors="exhibitors"><p>I'm the child component!</p></slot>
</div>
</template>
<script>
export default {
data: () => ({
exhibitors: [
{
firstName: 'Joe',
lastName: 'Burns',
tagNum: 1,
species: 'ant'
},
{
firstName: 'Tom',
lastName: 'Yorke',
tagNum: 2,
species: 'bee'
},
{
firstName: 'Flit',
lastName: 'Argmeno',
tagNum: 3,
species: 'giraffe'
}
],
}),
mounted: function () {
this.$nextTick(function () {
let length = this.exhibitors.length
this.$emit('ExLength', length)
})
}
}
</script>
So as I said all that works, you can click the buttons and it will loop through the array contents. You can style it how you want wherever you like. However, it is a bit messy with props and slots and emits and whatnot just to relay a single index number and it would be far easier in my opinion to have a vuex store where 'index' is stored as a state item and can be used and changed anywhere without all the above carry on.

Vuejs v-on click doesn't work inside component

I use VueJs and I create the following component with it.
var ComponentTest = {
props: ['list', 'symbole'],
data: function(){
return {
regexSymbole: new RegExp(this.symbole),
}
},
template: `
<div>
<ul>
<li v-for="item in list"
v-html="replaceSymbole(item.name)">
</li>
</ul>
</div>
`,
methods: {
replaceSymbole: function(name){
return name.replace(this.regexSymbole, '<span v-on:click="test">---</span>');
},
test: function(event){
console.log('Test ...');
console.log(this.$el);
},
}
};
var app = new Vue({
el: '#app',
components: {
'component-test': ComponentTest,
},
data: {
list: [{"id":1,"name":"# name1"},{"id":2,"name":"# name2"},{"id":3,"name":"# name3"}],
symbole: '#'
},
});
and this my html code
<div id="app">
<component-test :list="list" :symbole="symbole"></component-test>
</div>
When I click on the "span" tag inside "li" tag, nothing append.
I don't have any warnings and any errors.
How I can call my component method "test" when I click in the "span" tag.
How implement click event for this case.
You cannot use vue directives in strings that you feed to v-html. They are not interpreted, and instead end up as actual attributes. You have several options:
Prepare your data better, so you can use normal templates. You would, for example, prepare your data as an object: { linkText: '---', position: 'before', name: 'name1' }, then render it based on position. I think this is by far the nicest solution.
<template>
<div>
<ul>
<li v-for="(item, index) in preparedList" :key="index">
<template v-if="item.position === 'before'">
<span v-on:click="test">{{ item.linkText }}</span>
{{ item.name }}
</template>
<template v-else-if="item.position === 'after'">
{{ item.name }}
<span v-on:click="test">{{ item.linkText }}</span>
</template>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["list", "symbole"],
computed: {
preparedList() {
return this.list.map(item => this.replaceSymbole(item.name));
}
},
methods: {
replaceSymbole: function(question) {
if (question.indexOf("#") === 0) {
return {
linkText: "---",
position: "before",
name: question.replace("#", "").trim()
};
} else {
return {
linkText: "---",
position: "after",
name: question.replace("#", "").trim()
};
}
},
test: function(event) {
console.log("Test ...");
console.log(this.$el);
}
}
};
</script>
You can put the click handler on the surrounding li, and filter the event. The first argument to your click handler is the MouseEvent that was fired.
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id" v-on:click="clickHandler"
v-html="replaceSymbole(item.name)">
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["list", "symbole"],
data() {
return {
regexSymbole: new RegExp(this.symbole)
};
},
computed: {
preparedList() {
return this.list.map(item => this.replaceSymbole(item.name));
}
},
methods: {
replaceSymbole: function(name) {
return name.replace(
this.regexSymbole,
'<span class="clickable-area">---</span>'
);
},
test: function(event) {
console.log("Test ...");
console.log(this.$el);
},
clickHandler(event) {
const classes = event.srcElement.className.split(" ");
// Not something you do not want to trigger the event on
if (classes.indexOf("clickable-area") === -1) {
return;
}
// Here we can call test
this.test(event);
}
}
};
</script>
Your last option is to manually add event handlers to your spans. I do not!!! recommend this. You must also remove these event handlers when you destroy the component or when the list changes, or you will create a memory leak.

Vue: data from grandparent to grandchild (search component)

In my Vue app I have a view ('projects.vue') that gets some .json and which has a child component ('subheader.vue') which imports a search/filter mixin. I had this working but I wanted to split out the search elements from the subheader component to its own component, so subheader would hold only the headings and then import the search component. Despite adapting the props and bindings from the working version, my new three-component setup is throwing an error. Here's the setup:
the searchMixin.js:
export default {
computed: {
filteredProjects: function() {
const searchTerm = this.search.toLowerCase();
if (!searchTerm) {
return false;
}
return this.projects.filter((project) => {
return (project.client.toLowerCase().match(searchTerm)) ||
(project.contacts.filter((el) => {
return el.name.toLowerCase().match(searchTerm);
}).length > 0) ||
(project.projectReference.toLowerCase().match(searchTerm));
});
}
}
}
Projects.vue:
<template>
<div class="container" id="projects">
<!-- app-subheader is global component, imported & registered in main.js -->
<app-subheader v-bind:title="title" v-bind:subtitle="subtitle" />
[ snip irrelevant stuff ]
</div>
</template>
<script>
export default {
data () {
return {
title: "Projects",
subtitle: "",
projects: []
}
},
created: function() {
this.$http.get('https://xyz.firebaseio.com/projects.json')
.then(function(data){
return data.json();
})
.then(function(data){
var projectsArray = [];
for (let key in data) {
data[key].id = key;
this.projectID = key;
projectsArray.push(data[key]);
}
this.projects = projectsArray;
})
},
} // export default
</script>
subheader.vue:
<template>
<div class="subheader">
<div class="headings">
<h1 v-html="title"></h1>
<h2 v-html="subtitle"></h2>
<!-- I want to conditionally include <app-search /> according to an ID on an element in the grandparent (e.g., projects.vue) -->
<app-search v-bind:projects="projects" />
</div>
</div>
</template>
<script>
import Search from './search.vue';
export default {
components: {
'app-search': Search
},
props: [ "title", "subtitle", "projects" ],
data() {
return {
search: "",
projects: []
}
},
created: function() {
console.log("created; log projects", this.projects);
},
methods: {},
computed: {}
}
</script>
search.vue:
<template>
<div class="search-wrapper">
<div class="search">
<input type="text" v-model="search" placeholder="search by client, contact name, description, project, source" />
</div>
<div class="search-results-wrapper">
<h3>search-results:</h3>
<span class="results-count" v-if="filteredProjects.length == 0">
no results matching search "{{ search }}":
</span>
<span class="results-count" v-if="filteredProjects.length > 0">
{{ filteredProjects.length }} result<span v-if="filteredProjects.length > 1">s</span> matching search "{{ search }}":
</span>
<ul class="search-results" v-bind:class="{ open: filteredProjects.length > 0 }">
<li v-for="(project, ix) in filteredProjects" :key="ix">
{{ ix + 1 }}:
<router-link v-bind:to="'/project-detail/' + project.id">
{{ project.client }} ({{ project.projectReference }})
</router-link>
</li>
</ul>
</div>
</div><!-- END .search-wrapper -->
</template>
<script>
import searchMixin from '../mixins/searchMixin.js';
export default {
props: [ "projects" ],
data() {
return {
search: "",
}
},
created: function() {
},
mixins: [ searchMixin ],
}
When the search function is invoked, this error is thrown:
[Vue warn]: Error in render: "TypeError: Cannot read property 'filter' of undefined"
found in --->
<AppSearch> at src/components/search.vue
<AppSubheader> at src/components/subheader.vue
<Projects> at src/components/projects.vue
<App> at src/App.vue
... which seems to suggest the search mixin is not getting 'projects'.
Also, in subheader.vue, I get various errors whether I have 'projects' as a prop or 'projects: []' as a data key, and in once case or another I either get no results from the search function or an error, "Property or method "projects" is not defined on the instance but referenced during render".
Obviously I'm lacking clarity on the docs re; grandparent-parent-child data flow. Any help is greatly appreciated.
Whiskey T.