Vue.js: Collapse/expand all elements from parent - javascript

I need to add "expand/collapse all" functionality for my Vue component(some collapsible panel).
If user clicks collapse button then clicks on some panel and expand it then clicking on collapse button will do nothing because watched parameter will not change.
So how to implement this functionality properly (buttons must collapse and expand components always)?
I prepared simple example(sorry for bad formatting, it looks nice in editor :( ):
var collapsible = {
template: "#collapsible",
props: ["collapseAll"],
data: function () {
return {
collapsed: true
}
},
watch: {
collapseAll: function(value) {
this.collapsed = value
}
}
}
var app = new Vue({
template: "#app",
el: "#foo",
data: {
collapseAll: true
},
components: {
collapsible: collapsible
}
});
.wrapper {
width: 100%;
}
.wrapper + .wrapper {
margin-top: 10px;
}
.header {
height: 20px;
width: 100%;
background: #ccc;
}
.collapsible {
height: 100px;
width: 100%;
background: #aaa;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="foo"></div>
<script type="text/x-template" id="collapsible">
<div class="wrapper">
<div class="header" v-on:click="collapsed = !collapsed"></div>
<div class="collapsible" v-show="!collapsed"></div>
</div>
</script>
<script type="text/x-template" id="app">
<div>
<button v-on:click="collapseAll = true">Collapse All</button>
<button v-on:click="collapseAll = false">Expand All</button>
<collapsible v-for="a in 10" v-bind:collapseAll="collapseAll" v-bind:key="a"></collapsible>
</div>
</script>
Thanks!

This is a case where I might use a ref.
<button v-on:click="collapseAll">Collapse All</button>
<button v-on:click="expandAll">Expand All</button>
<collapsible ref="collapsible" v-for="a in 10" v-bind:key="a"></collapsible>
And add methods to your Vue.
var app = new Vue({
template: "#app",
el: "#foo",
methods:{
collapseAll(){
this.$refs.collapsible.map(c => c.collapsed = true)
},
expandAll(){
this.$refs.collapsible.map(c => c.collapsed = false)
}
},
components: {
collapsible: collapsible
}
});
Example.

Related

How can I duplicate my component in vue.js?

<template>
<!--This is my main file -->
<div id="inputs">
<h1>언어 관리</h1>
<v-btn color="primary" elevation="10" large #click="duplicateEl"
>Add row</v-btn
>
<Contents />
</div>
</template>
<script>
import Contents from "./Contents.vue";
export default {
name: "LanguageMainMenu",
components: { Contents },
methods: {
duplicateEl() {
alert("You can duplicate buttons");
},
},
};
</script>
<style>
h1 {
text-align: center;
font-size: 38px;
padding-top: 20px;
margin: auto;
}
</style>
The best apprach is to use the component inside v-for.
Increment the index when the button is clicked.
Dont forget to use key inside the v-for
Working Fiddle
var example1 = new Vue({
el: '#app',
name: "LanguageMainMenu",
components: {
Contents: {
template: `<div>Contents Component</div>`,
}
},
data() {
return {
totalCount: 1,
}
},
methods: {
duplicateEl() {
this.totalCount++;
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.4/vue.js"></script>
<div id="app">
<!--This is my main file -->
<div id="inputs">
<h1>언어 관리</h1>
<button #click="duplicateEl">Add row</button>
<Contents v-for="count in totalCount" :key="`component-${count}`" />
</div>
</div>
You can add a property in data object and use v-for for render buttons.
Let method duplicateEl to change the property value.
<v-btn v-for="item in btnNumber" ....>
duplicateEl(){
this.btnNumber++
}

Set up a v-on:click directive inside v-for

I have displayed a list of images with some information. I want those images to be clickable. And when clicked it should show a div with saying "HI!!". I have been trying to add a variable as show:true in Vue data and tried to build some logic that show becomes false when clicked. But I have not been able to achieve it.
Below is the sample code:
template>
<div>
<h1>SpaceX</h1>
<div v-for="launch in launches" :key="launch.id" class="list" #click="iclickthis(launch)">
<div ><img :src="launch.links.patch.small" alt="No Image" title={{launch.name}} /></div>
<div>ROCKET NAME: {{launch.name}} </div>
<div>DATE: {{ launch.date_utc}} </div>
<div>SUCCESS: {{ launch.success}} </div>
<div>COMPLETENESS: {{ launch.landing_success}} </div>
</div>
<!-- <v-model :start="openModel" #close="closeModel" /> -->
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'SpaceXTimeline',
components: {
},
data: () => ({
launches : [],
openModel : false,
show : true,
}),
methods: {
iclickthis(launch) {
// this should load a model search v-model / bootstrap on vue npm install v-model
console.log(launch.name + " is launched");
console.log("DETAILS: "+ launch.details);
console.log("ROCKET INFO: "+ launch.links.wikipedia);
console.log("CREW DETAILS: "+ launch.crew);
console.log("Launchpad Name: "+ launch.launchpad);
this.openModel = true;
},
closeModel () {
this.openModel = false;
}
},
async created() {
const {data} = await axios.get('https://api.spacexdata.com/v4/launches');
data.forEach(launch => {
this.launches.push(launch);
});
}
};
</script>
<style scoped>
.list {
border: 1px solid black;
}
</style>
Thanks, and appreciate a lot.
v-model is a binding and not an element, unless you've named a component that? Is it a misspelling of "modal"?
Either way, sounds like you want a v-if:
<v-model v-if="openModel" #close="closeModel" />
Example:
new Vue({
el: '#app',
components: {},
data: () => ({
launches: [],
openModel: false,
show: true,
}),
methods: {
iclickthis(launch) {
// this should load a model search v-model / bootstrap on vue npm install v-model
console.log(launch.name + ' is launched');
console.log('DETAILS: ' + launch.details);
console.log('ROCKET INFO: ' + launch.links.wikipedia);
console.log('CREW DETAILS: ' + launch.crew);
console.log('Launchpad Name: ' + launch.launchpad);
this.openModel = true;
},
closeModel() {
this.openModel = false;
},
},
async created() {
const {
data
} = await axios.get('https://api.spacexdata.com/v4/launches');
data.forEach(launch => {
this.launches.push(launch);
});
},
})
Vue.config.productionTip = false;
Vue.config.devtools = false;
.modal {
cursor: pointer;
display: flex;
justify-content: center;
position: fixed;
top: 0;
width: 100%;
height: 100vh;
padding: 20px 0;
background: rgba(255, 255, 255, 0.5);
}
img {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<div id="app">
<h1>SpaceX</h1>
<div v-for="launch in launches" :key="launch.id" class="list" #click="iclickthis(launch)">
<div>
<img :src="launch.links.patch.small" alt="No Image" :title="launch.name" />
</div>
<div>ROCKET NAME: {{ launch.name }}</div>
<div>DATE: {{ launch.date_utc }}</div>
<div>SUCCESS: {{ launch.success }}</div>
<div>COMPLETENESS: {{ launch.landing_success }}</div>
</div>
<div v-if="openModel" #click="closeModel" class="modal">
MODAL
</div>
</div>

Vue 2 event listener on component root

I'm trying to capture an event on the component root node, but the following does not work. I don't want to just listen on a node in the component. I want to be able to click on any element and then hit backspace to remove it. The code below is a basic example of how I setup my code.
<template>
<div v-on:keydown.delete="delete()">
<img id="image" src="..." v-on:click="set_active()">
</div>
</template>
<script>
export default {
return {
data() {
active: ''
},
methods: {
delete(){
delete this.$refs[this.active][0];
},
set_active() {
this.active = event.target.getAttribute('id');
}
}
}
}
</script>
After doing some tests, here is what I discovered:
Having a method called delete won't work. I don't know why, the question remains unanswered here. Rename it to remove, for example.
When trying to catch keyboard events on a div, you may need to add a tabindex attribute for it to work. (See here)
Interactive demo
Vue.component('my-component', {
template: '#my-component',
data() {
return {
images: [
"https://media.giphy.com/media/3ohs7KtxtOEsDwO3GU/giphy.gif",
"https://media.giphy.com/media/3ohhwoWSCtJzznXbuo/giphy.gif",
"https://media.giphy.com/media/8L0xFP1XEEgwfzByQk/giphy.gif"
],
active: null
};
},
methods: {
set_active(i) {
this.active = i;
},
remove() {
if (this.active !== null) {
this.images = this.images.filter((_, i) => i !== this.active);
this.active = null;
}
}
}
});
var vm = new Vue({
el: '#app'
});
div {
outline: none; /* Avoid the outline caused by tabindex */
border: 1px solid #eee;
}
img {
height: 80px;
border: 4px solid #eee;
margin: .5em;
}
img:hover {
border: 4px solid #ffcda9;
}
img.active {
border: 4px solid #ff7c1f;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<div id="app">
<my-component></my-component>
</div>
<template id="my-component">
<div #keydown.delete="remove" tabindex="0">
<img
v-for="(img, i) in images"
:key="i"
:src="img"
:class="{ active: active === i }"
#click="set_active(i)"
/>
</div>
</template>

Vue: How do I generate a new instance of a div with a buttonclick?

I need to create a function that generates a new instance of a specific div (.container in the template) when a button (#addDiv) is clicked. The button should be the only thing visible on the webpage before that. How do I do?
I know that it may be possible to do this with document.appendChild. Is there a better way?
I am using toggle-function that works great, I have included it in the code to give you the whole picture.
Vue
Vue.component('dynamicdDvs', {
template: `
<div>
<div class="headerDiv">
<button class="addDiv" type="button" v-on:click="createDiv">Create</button>
</div>
<div class="container">
<div class="createdDiv">
<h2>I am dynamic!</h2>
<button class="minimize" v-on:click="expand">Make me smal</button>
</div>
<div class="createdDivMinimized" v-if="!displayDiv">
<p>I am a smal version of the created div!</p>
<button class="maximize" v-on:click="expand">Expand me</button>
</div>
</div>
</div>
`,
data:function () {
return {
displayDiv: false
}
},
methods: {
expand: function () {
this.displayDiv = !this.displayDiv;
},
createDiv: function() {
//The function that creates a new div, with the same code as
//.createdDiv and .createDivMinimized may be placed here
}
}
});
CSS
.headerDiv {
height: 100px;
width: 400px;
background-color: blue;
color: white
}
.createdDiv {
height: 300px;
width: 400px;
background-color: black;
color: white
}
.createdDivMinimized {
height: 300px;
width: 400px;
background-color: red;
color: white
}
HTML
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="parent">
<dynamicDivs></dynamicDivs>
</div>
So what you can do is set a number in data for the number of <div> elements and use v-for with a range. For example
data () {
return {
displayDiv: [false],
divs: 1
}
}
And in your template
<template v-for="n in divs">
<div class="createdDiv">
<!-- snip -->
<button class="minimize" v-on:click="displayDiv[n-1] = !displayDiv[n-1]">Make me small</button>
</div>
<div class="createdDivMinimized" v-if="!displayDiv[n-1]">
<!-- snip -->
</div>
</template>
and in your methods...
createDiv () {
this.divs++
this.displayDiv.push(false)
}

VueJS - input file repeater

I want delete item from array but there are the same item and js delete last one !
let app = new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem() {
this.items.push('');
},
removeItem(index) {
this.items.splice(index, 1);
}
}
});
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="app">
<ul class="list-group">
<li class="list-group-item" v-for="(item , index) in items">
remove
<input name="form[]" type='file'>
</li>
</ul>
<button #click='addItem'>new item</button>
</div>
JSFiddle: https://jsfiddle.net/6hvbqju2/
Vue uses an "in-place patch strategy" when dealing with list of elements. This strategy is not suitable when relying on form input values.
When using v-for directive it is better to define a v-bind:key to give Vue a hint to track each node.
We'll store numbers in the items array and use them as a key. In your case you should use an item's property that can serve as a unique key.
let app = new Vue({
el: '#app',
data: {
counter: 0,
items: []
},
methods: {
addItem() {
this.items.push(this.counter++);
},
removeItem(index) {
this.items.splice(index, 1);
}
}
});
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="app">
<ul class="list-group">
<li class="list-group-item" v-for="(item , index) in items" :key="item">
remove
<input name="form[]" type='file'>
</li>
</ul>
<button #click='addItem'>new item</button>
</div>
Your codes working fine but,
This is because of file input auto complete behaviour
See this example
let app = new Vue({
el : '#app',
data : {
items: [],
},
methods : {
addItem() {
this.items.push({file: null});
console.log(this.items)
},
removeItem(index) {
this.items.splice(index,1);
},
handleChange(item, event){
item.file = event.target.files["0"];
}
}
});
.upload-btn-wrapper {
position: relative;
overflow: hidden;
display: inline-block;
vertical-align: middle;
}
.btn {
border: 1px solid gray;
color: gray;
background-color: white;
padding: 4px 10px;
border-radius: 4px;
font-size: 15px;
font-weight: bold;
}
.upload-btn-wrapper input[type=file] {
font-size: 100px;
position: absolute;
left: 0;
top: 0;
opacity: 0;
}
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="app">
<ul class="list-group">
<li class="list-group-item" v-for="(item , index) in items">
remove
<div type="button" class="upload-btn-wrapper">
<button class="btn">{{ item.file ? item.file.name : 'Choose File' }}</button>
<input name="form[]" type="file" #change="handleChange(item, $event)">
</div>
</li>
</ul>
<button #click='addItem'>new item</button>
</div>

Categories