Binding multiple checkboxes of children to one parent - javascript

Is there's any way to bind multiple checkboxes from many children to one parent (f.e. via model)?
Let's say that the child component has something like:
<input
type="checkbox"
:id="'ticket-'+id"
:checked="checked"
/>
Now, is it possible to have a parent prop like selected that would collect the id as values in an array of all checked checkboxes from children?
{
selected: [
5,
8
]
}
The nearest thing to my solution in mind is this. But in my case, I don't want to keep track of unchecked instances.

You can listen to the checkbox change events on the parent and call a method which updates the data model.
Check out the fiddle below demonstrating this. (Go full page to see it properly)
new Vue({
el: "#app",
data: {
list: [
{ id: 1, label: 'Check 1'},
{ id: 2, label: 'Check 2'},
{ id: 3, label: 'Check 3'},
{ id: 4, label: 'Check 4'},
],
selected: []
},
methods: {
handleChange: function(ev) {
let id = ev.target.id;
if (ev.target.checked) {
if (!this.selected.includes(id)) {
this.selected.push(id);
}
} else {
this.selected = this.selected.filter(function (item) {
return item !== id;
});
}
console.log('Updated: ', this.selected);
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<ol #change="handleChange">
<li v-for="check in list">
<label>
<input type="checkbox" :id="check.id" />
<span>{{check.label}}</span>
</label>
</li>
</ol>
</div>

Related

In Vue.js, How this fix, after splicing an array, following its class

First of all, I'm sorry to write in English not well.
I'm looking foward to find the answer to fix this problems.
I'm making a todolist, it had a problem that the class ('centerLine') keeps following next element
after deleting an array to use splice.
Please someone know, let me know how to fix it.
Thank you
https://github.com/seongjin2427/Public
* checked the check box
*after pushing x-box to get rid of checked todo
You can send id to method
#click="deleteTask(todo.id)"
and then filter array
deleteTask(id) {
this.todos = this.todos.filter(t => t.id !== id)
}
let app = new Vue({
el: '#app',
data: {
todos: [{
id: 1,
text: '밥 먹기',
checked: false
},
{
id: 2,
text: '잘 자기',
checked: false
},
{
id: 3,
text: '유튜브 보기',
checked: false
}
],
input_text: ""
},
methods: {
addTodo() {
// 배열 길이 변수 저장
let arrayLength = this.todos[this.todos.length-1].id;
// Add 버튼 눌렀을 때, input_text값 그대로 배열에 push 하기
if (this.input_text != "") {
this.todos.push({
id: arrayLength + 1,
text: this.input_text
});
}
// push후 input 값 초기화
this.input_text = "";
},
change1(e) {
// 할 일 클릭 후 input 창으로 변경
let index = e.target.id.substr(3, 3);
document.querySelector('#vsb' + index).classList.toggle('none');
document.querySelector('#invsb' + index).classList.toggle('none');
},
change2(e) {
// input 창에서 마우스가 out되면 실행할 것
let index = e.target.id.substr(5, 3);
document.querySelector('#vsb' + index).classList.toggle('none');
document.querySelector('#invsb' + index).classList.toggle('none');
},
deleteTask(id) {
this.todos = this.todos.filter(t => t.id !== id)
}
}
});
#app li {
list-style: none;
padding:0;
margin: 0;
}
span.centerLine {
text-decoration: line-through;
color: gray;
}
.x-box {
border-radius: 30%;
opacity: 0%;
}
.x-box:hover {
opacity: 100%;
transition: all 1s;
}
.none {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<body>
<div id="app">
<h1>To Do List</h1>
<hr>
<input type="text" v-model="input_text" #keyup.enter="addTodo">
<input type="button" value="Add" #click="addTodo">
<br>
<div class="todo-box">
<ul>
<li v-for="(todo, idx) in todos" :key="idx">
<input :id="'chk'+(idx+1)" type="checkbox" v-model="todo.checked">
<span :id="'vsb'+(idx+1)" #click="change1" :class="{'centerLine': todo.checked}">{{ todo.text }}</span>
<input :id="'invsb'+(idx+1)" #mouseout="change2" class="none" type="text" v-model="todo.text">
<input :id="'xbox'+(idx+1)" class="x-box" #click="deleteTask(todo.id)" type="button" value="x">
</li>
</ul>
</div>
</div>
</body>
you can send the task in the method
#click="deleteTask(task)"
then splice it from array
deleteTask(task) {
this.todos.splice(this.todos.indexOf(task),1)
}

Vuetify - Search function in table that jumps to the found row

I have 2 Vuetify data tables without pagination. Every row of the second one got exactly one parent in the first one. If I click on one of those entries in the second one, I want to search for the parent and jump to that row. All I found for now is filtering, but I only want to have that row on top of my table.
Is something like this possible?
We can't really help you without code, even if i've seen that you can't, it should be cool if you could modify some parts of your code like variables and datas ...
However, i'll try to do my best to explain
What you have to do is to reorder your data array binded on your table depending on a given id (or other data) to identify it.
I made a similar exemple of your needs, but i repeat i can't really be exhausitve :
Parent component:
<template>
<div id="app">
<listone :list="listOne" :toggled="toggledParent"></listone>
<listtwo :list="listTwo" v-model="toggledParent"></listtwo>
</div>
</template>
<script>
export default {
name: "App",
data: () => ({
toggledParent: 0,
listOne: [
{
id: 1,
title: "parent1",
},
{
id: 2,
title: "parent2",
},
{
id: 3,
title: "parent3",
},
],
listTwo: [
{
title: "title1",
parent: 3,
},
{
title: "title2",
parent: 1,
},
{
title: "title3",
parent: 2,
},
],
}),
components: {
listone: () => import("#/components/listone"),
listtwo: () => import("#/components/listtwo"),
},
};
</script>
<style>
#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;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
}
</style>
ListOne component :
<template>
<div class="list">
<a v-for="item in treatedList" :key="item.title">
{{ item.title }}
</a>
</div>
</template>
<script>
export default {
name: "listone",
props: {
list: Array,
toggled: Number,
},
computed: {
treatedList: function () {
let tmp = this.list;
let $this = this
return tmp.sort(function (a, b) {
return a.id === $this.toggled ? -1 : b.id === $this.toggled ? 1 : 0;
});
},
},
};
</script>
<style>
.list {
display: flex;
flex-flow: column nowrap;
}
.list > a {
border: 1px solid red;
}
</style>
ListTwo component :
<template>
<div class="list">
<a
v-for="item in list"
:key="item.title"
#click="$emit('input', item.parent)">
{{ item.title }}
</a>
</div>
</template>
<script>
export default {
name: "listtwo",
props: {
list: Array,
toggledParent: Number
},
};
</script>
<style scoped>
.list {
display: flex;
flex-flow: column nowrap;
}
.list > a {
border: 1px solid red;
}
</style>
Try it and say me it helps you as wanted
Demo on codesandbox.io : https://codesandbox.io/s/mutable-thunder-zo8vn?fontsize=14&hidenavigation=1&theme=dark
So without knowing what your data looks like i'll use a standard array of objects without meta data.
What you could do, is use the sort function inside of computed property like below, this will re-organize your list every time you match or filter the list.
(Note: This re-arranges your entire list every time)
Here is a really basic example:
new Vue({
el: "#app",
data: {
search: 'Bananas',
tableData: [
{
item: "Cherries",
price: 3.99,
type: "Fruit"
},
{
item: "Chicken",
price: 6.99,
type: "Meat"
},
{
item: "Bananas",
price: 1.99,
type: "Fruit"
},
{
item: "Cola",
price: 0.99,
type: "Drink"
},
{
item: "Coffee",
price: 2.99,
type: "Drink"
},
]
},
computed: {
getTableData: function() {
return this.tableData.sort((x,y) => { return x.item == this.search ? -1 : y.item == this.search ? 1 : 0; });
}
}
})
table {
background: #ccc;
padding: 20px;
width:50%;
}
table tr {
background: #f1f1f1;
}
table tr td {
padding: 10px;
}
.firstRow {
background: green;
color:white;
}
.searchBar {
padding:20px;
width: 40%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="searchBar">
Search <input type="text" v-model="search">
</div>
<table>
<tr class="firstRow"><td>Item</td> <td>Price</td> <td>Type</td></tr>
<tr v-for="(product, index) in getTableData" :key="index">
<td>{{product.item}}</td>
<td>{{product.type}}</td>
<td>{{product.price}}</td>
</tr>
</table>
</div>

Vuejs populate input fields value on a element select

I am not sure if I am terming this correctly. I have a very simple vuejs application (I just started learning vue a couple of days ago, so my knowledge of vue is really limited).
I have a input field which acts as a search box. When we feed some text input, it triggers v-on:blur event to call a function. It then sets the suggestions which are displayed just below the searchbox.
What I am trying to achieve is, when any of those anchor tags are clicked (from the search suggestions), two new input boxes should be automatically populated with the values from the search suggestions.
{name: 'Some Name', state: 'Some State'}
A very simple and stripped version of the code is as https://jsfiddle.net/dfhpj08g/
new Vue({
el: "#app",
data: {
suggestions: [],
showSuggestions: false,
},
methods: {
suggest() {
// this is dynamically generated via ajax
this.suggestions = [{
name: 'A',
state: 'OH'
},
{
name: 'B',
state: 'OR'
},
];
this.showSuggestions = true;
}
}
})
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/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-on:blur="suggest" placeholder="search">
<div v-show="showSuggestions">
<span>Did you mean</span>
<li v-for="s in suggestions">
<a href="#">
{{s.name}} - ({{s.state}})
</a>
</li>
</div>
<input type="text" name="name" placeholder="name">
<input type="text" name="state" placeholder="state">
</div>
If you want to insert the values into your name and state fields, I would suggest using v-model on them and declaring the corresponding data in your component. In that way, you can simply set them using this.name and this.state:
data: {
suggestions: [],
showSuggestions: false,
name: '',
state: ''
},
Use v-model to bind name and state data to your input elements:
<input type="text" name="name" placeholder="name" v-model="name">
<input type="text" name="state" placeholder="state" v-model="state">
You can bind a click handler to each of the <a> elements, so that you will can pass the index of the clicked suggestion. This will allow you to do this.suggestion[i] to retrieve the data:
<li v-for="(s, i) in suggestions" v-bind:key="i">
<a href="#" v-on:click.prevent="suggestionSelected(i)">
{{s.name}} - ({{s.state}})
</a>
</li>
Then, in your methods, you can create a new function suggestionSelected, which accepts the index of the suggestion as i. In that way, you can use the bracket syntax to access the selected suggestion:
suggestionSelected(i) {
this.name = this.suggestions[i].name;
this.state = this.suggestions[i].state;
}
See proof-of-concept example below:
new Vue({
el: "#app",
data: {
suggestions: [],
showSuggestions: false,
name: '',
state: ''
},
methods: {
suggest() {
// this is dynamically generated via ajax
this.suggestions = [{
name: 'A',
state: 'OH'
},
{
name: 'B',
state: 'OR'
},
];
this.showSuggestions = true;
},
suggestionSelected(i) {
this.name = this.suggestions[i].name;
this.state = this.suggestions[i].state;
}
}
})
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/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-on:blur="suggest" placeholder="search">
<div v-show="showSuggestions">
<span>Did you mean</span>
<li v-for="(s, i) in suggestions" v-bind:key="i">
<a href="#" v-on:click.prevent="suggestionSelected(i)">
{{s.name}} - ({{s.state}})
</a>
</li>
</div>
<input type="text" name="name" placeholder="name" v-model="name">
<input type="text" name="state" placeholder="state" v-model="state">
</div>

Vue js data binding doesn't work

I'm trying to create a vue component but whenever I want to hide some elements with v-show it doesn't work.
When you click any element on the list I want to hide it and on the click event element.visible is set to false, so in the component template I bind that value to v-show but it wont hide.
I guess it's because element.visible it's kind of nested attribute but I'm not really sure.
var collection = [
{ id: 1, name: 'element 1' },
{ id: 2, name: 'element 2' },
{ id: 3, name: 'element 3' },
{ id: 4, name: 'element 4' },
{ id: 5, name: 'element 5' },
{ id: 6, name: 'element 6' },
{ id: 7, name: 'element 7' },
{ id: 8, name: 'element 8' },
];
var multiselect = {
props: ['collection'],
data: function() {
return {
subscribed: [],
toSubscribe: [],
toUnsubscribe: [],
dataset: []
}
},
mounted: function(){
this.dataset = _.map(this.collection, function(element){
element.visible = true;
return element;
});
},
methods: {
subscribe: function(element){
element.visible = false;
}
}
}
new Vue({
el: '#app',
components: {
'multiselect': multiselect
},
data: {
elements: collection
}
})
.multiselect .list {
border: 1px solid #000;
height: 215px;
max-height: 215px;
overflow: scroll;
}
.multiselect .list .list-element {
text-align: center;
padding: 0.2em;
cursor: pointer;
}
.multiselect .list .list-element:hover {
background-color: #d6dbdf;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<multiselect inline-template :collection="elements">
<div class="col-sm-12 multiselect">
<div class="col-sm-5 list">
<div class="col-sm-12">
<div v-for="element in dataset" class="list-element" #click="subscribe(element)" v-show="element.visible">
{{element.name}}
</div>
</div>
</div>
<div class="col-sm-2">
<button class="btn btn-primary btn-fill">
<i class="fa fa-arrow-right" aria-hidden="true"></i>
</button>
<button class="btn btn-primary btn-fill">
<i class="fa fa-arrow-left" aria-hidden="true"></i>
</button>
</div>
<div class="col-sm-5 list">
</div>
</div>
</multiselect>
</div>
As an interesting variation, you don't need to clone the collection elements or set a property on them.
It is enough to have a parallel array of flag, but you have to be careful of the syntax to update them and the flag must be contained in an object in order to be observable.
i.e an array of { visible: true } rather than an array of true.
Ref: Mutation-Methods
var collection = [
{ id: 1, name: 'element 1' },
{ id: 2, name: 'element 2' },
{ id: 3, name: 'element 3' },
{ id: 4, name: 'element 4' },
];
var multiselect = {
props: ['collection'],
data: function() {
return {
visibleFlags: []
}
},
created: function(){
this.collection.forEach(x => {
this.visibleFlags.push({visible: true}); // Vue mutation method
})
},
methods: {
subscribe: function(index){
this.$set(this.visibleFlags, index, false)
}
}
}
new Vue({
el: '#app',
components: {
'multiselect': multiselect
},
data: {
elements: collection
}
})
.multiselect .list {
border: 1px solid #000;
height: 125px;
max-height: 215px;
overflow: scroll;
}
.multiselect .list .list-element {
text-align: center;
padding: 0.2em;
cursor: pointer;
}
.multiselect .list .list-element:hover {
background-color: #d6dbdf;
}
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<multiselect inline-template :collection="elements">
<div class="col-sm-12 multiselect">
<div class="col-sm-5 list">
<div class="col-sm-12">
<div v-for="(element, index) in collection"
class="list-element" v-show="visibleFlags[index].visible"
#click="subscribe(index)">
{{element.name}}
</div>
</div>
</div>
</div>
</multiselect>
</div>
The problem is that you are modifying an already-responsive object. Vue cannot detect property additions.
It's obscured by the fact that you're copying via map, and assigning it to a new array, but it's an array of references to responsive objects, to each of which you have added the visible property. If you examine the data items in the parent, you'll see that it gets visible added, too.
The minimal fix is to use Object.assign to create a new object and copy properties into it. This way all properties are inserted into a non-responsive object, which is then made responsive during assignment.
mounted: function(){
this.dataset = _.map(this.collection, function(element){
return Object.assign({}, element, {visible: true});
});
},
You could do this in created, since you don't need the DOM element.

How to show an elements value in a vue modal?

I have several elements that are displayed as <li> elements in a loop. For each element I want behavior such that when the element is clicked a modal box opens up. Inside the modal box I want contents that are specific to the element that was clicked.
The data below shows all the elements:
{value: 10, name: "foo"},
{value: 12, name: "bar"},
{value: 14, name: "foobar"},
{value: 22, name: "test"},
{value: 1, name: "testtooo"},
{value: 8, name: "something"}
When I click on an element, I want to see the value property for it inside the modal box.
I've created a fiddle for this: https://jsfiddle.net/hvb9hvog/14/
Question
I've gotten the modal working, however, how can I show each elements value property inside the modal?
I am sure there are multiple ways to go about this, but one way would be to create a new data property, let's call it value. When you #click the li you get it's value, set it to the value property and display that value property in the body of the modal ({{this.value}}).
You can have two #click methods, so create another one that updates the data property you just created, called value.
Here's a fiddle
Relevant code changes:
In your li element:
<li v-for="request in filteredRequests">
{{request.name}}
</li>
In your modal markup:
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<h3 slot="header">custom header</h3>
<div slot="body">
{{this.value}}
</div>
</modal>
In vue data:
data: {
requests: [
{value: 10, name: "foo"},
{value: 12, name: "bar"},
{value: 14, name: "foobar"},
{value: 22, name: "test"},
{value: 1, name: "testtooo"},
{value: 8, name: "something"}
],
num: 0,
showModal: false,
value: 9999999999
},
In vue methods:
methods: {
setVal(val) {
this.value = val;
}
},
Vue.component('modal', {
template: '#modal-template'
})
var vm = new Vue({
el: "#app",
data: {
requests: [{
value: 10,
name: "foo"
},
{
value: 12,
name: "bar"
},
{
value: 14,
name: "foobar"
},
{
value: 22,
name: "test"
},
{
value: 1,
name: "testtooo"
},
{
value: 8,
name: "something"
}
],
num: 0,
showModal: false,
value: 9999999999
},
methods: {
setVal(val) {
this.value = val;
}
},
computed: {
c: function() {
return `Slider Number: (${this.num})`
},
filteredRequests() {
return this.requests.filter(r => r.value > this.num)
}
},
});
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .5);
display: table;
transition: opacity .3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
font-family: Helvetica, Arial, sans-serif;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
}
.modal-default-button {
float: right;
}
/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-enter {
opacity: 0;
}
.modal-leave-active {
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://unpkg.com/vue#2.3.4/dist/vue.js"></script>
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
<div id="app">
<div class="form-horizontal">
<div class="form-group">
<label class="col-md-2 control-label">色</label>
<div class="col-md-10">
<input class="form-control" v-model="c" :style="{backgroundColor: c}" />
<div class="help-block">
<input type="range" min="0" max="360" v-model.number="num" />
<ul class="ml-thumbs">
<li v-for="request in filteredRequests">
{{request.name}}
</li>
</ul>
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<h3 slot="header">custom header</h3>
<div slot="body">
{{this.value}}
</div>
</modal>
</div>
</div>
</div>
</div>
</div>
Add "req" property to data
data() {
return {
...
req: {},
...
}
}
set click event:
{{request.name}}
add body slot
...
<h3 slot="header">custom header</h3>
<div slot="body">
{{req.value}}
</div>
...
https://jsfiddle.net/w4e6hr86/
I'm not sure if you are asking this using Vue.js or just JS. So, here are my answers (basic examples). I recommend you to read about event delegation + events on vuejs.
Vue Js
<template>
<div class="content">
<ul>
<li v-for="item in items" #click.prevent="showModal(item)">{{ item }}</li>
</ul>
<div class="modal" v-show="isModalVisible">
{{ JSON.stringify(selectedItem) }}
close modal
</div>
</div>
</template>
<script>
export default {
name: 'something',
data () {
return {
selectedItem: item,
items: [{
id: 1,
name: 'something'
}, {
id: 2,
name: 'something #2'
}]
}
},
computed: {
isModalVisible () {
return this.selectedItem !== null
}
}
methods: {
showModal (item) {
this.selectedItem = item
}
}
}
</script>
Plain javascript
const toggleModal = content => {
const $body = document.querySelector('body')
const $modal = $body.querySelector('.modal')
$modal && $modal.remove()
$body.insertAdjacentHTML('beforeend',`<div class="modal">${content}</div>`)
}
document.querySelector('ul').addEventListener('click', e => {
if (! e.target.matches('li')) {
return
}
toggleModal(e.target.innerText)
});
About Event delegation.
About insertAdjacentHtml.
About Vuejs Event handling

Categories