VueJS Custom dropdown buttons not working independently of each other - vue.js

So I have a button dropdown which is working as expected but I have a bug where I can't reuse the same component as they both dont work independently of each other but instead when one is clicked the other changes too.
Please find a JSFiddle below and my code.
Thanks
<div id="app">
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
</div>
new Vue({
el: '#app',
data() {
return {
menuVisible: false,
btnTitle: false,
reasons: [{
title: 'family fun',
value: 1
},
{
title: 'relaxing',
value: 2
},
{
title: 'dining',
value: 3
},
{
title: 'meetings & events',
value: 4
},
{
title: 'what\'s on',
value: 5
},
{
title: 'gold',
value: 6
}]
}
},
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})

So, for one, you haven't actually made a reusable component for the dropdown. You need to define that using Vue.component. Then you can use the tag for the custom component in the root template.
Secondly, if you are binding to the same data, then that is what will be reflected in both templates. You'll still need to pass separate data to the two separate dropdown components.
Vue.component('dropdown', {
template: `
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
`,
props: ['menuVisible', 'btnTitle', 'reasons'],
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})
new Vue({
el: '#app',
data() {
return {
fooReasons: [
{ title: 'family fun', value: 1 },
{ title: 'relaxing', value: 2 },
{ title: 'dining', value: 3 },
{ title: 'meetings & events', value: 4 },
{ title: 'what\'s on', value: 5 },
{ title: 'gold', value: 6 }
],
barReasons: [
{ title: 'family bar', value: 1 },
{ title: 'bar relaxing', value: 2 },
{ title: 'bar dining', value: 3 },
{ title: 'meetings & bars', value: 4 },
{ title: 'bar\'s on', value: 5 },
{ title: 'gold bar', value: 6 }
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<dropdown :menu-visible="false" :btn-title="false" :reasons="fooReasons"></dropdown>
<dropdown :menu-visible="false" :btn-title="false" :reasons="barReasons"></dropdown>
</div>

Related

Is it possible to make v-for variables dynamic in vue?

<div class="col-3" v-for="n in 5" :key="n">
<h3>Table {{n}}</h3>
<draggable class="list-group" :list="`list${n}`" group="people" #change="log">
<div
class="list-group-item"
v-for="`(element, index) in list${n}`"
:key="element.name"
>
{{ element.name }} {{ index }}
</div>
</draggable>
</div>
Why can't I set the v-for or :list as a concatenated string? Is there any way around this?
Full code:
<template>
<div class="row">
<component
v-for="(component, index) in components"
:key="index"
:is="component"
/>
<div class="col-3" v-for="n in listNumber" :key="n">
<h3>Table {{n}}</h3>
<draggable class="list-group" :list="list${n}" group="people" #change="log">
<div
class="list-group-item"
v-for="(element, index) in list${n}"
:key="element.name"
>
{{ element.name }} {{ index }}
</div>
</draggable>
</div>
</div>
</template>
<script>
import draggable from "vuedraggable";
let id = 1;
export default {
name: "two-lists",
display: "Two Lists",
order: 1,
components: {
draggable,
list:[],
},
data() {
return {
list1: [
{ name: "John", id: 1 },
{ name: "Joao", id: 2 },
{ name: "Jean", id: 3 },
{ name: "Gerard", id: 4 }
]
},
{
list2: [
{ name: "Juan", id: 5 },
{ name: "Edgard", id: 6 },
{ name: "Johnson", id: 7 }
],
listNumber: 3,
}
},
created(){
console.log(this.list);
},
methods: {
add: function() {
this.list.push({ name: "Juan" });
},
replace: function() {
this.list = [{ name: "Edgard" }];
},
clone: function(el) {
return {
name: el.name + " cloned"
};
},
}
};
</script>
It's a bit difficult to understand exactly what you're trying to do but it looks like you're trying to get a specific list in the v-for loop.
One way to fix your issue would be to nest your lists inside of an object in your data like this:
data() {
return {
listNumber: 3,
lists: {
list1: [
{ name: "John", id: 1 },
{ name: "Joao", id: 2 },
{ name: "Jean", id: 3 },
{ name: "Gerard", id: 4 }
],
list2: [
{ name: "Juan", id: 5 },
{ name: "Edgard", id: 6 },
{ name: "Johnson", id: 7 }
],
},
};
And then in your code you can just do lists[`list${n}`] like this:
<div class="col-3" v-for="n in listNumber" :key="n">
<h3>Table {{n}}</h3>
<draggable class="list-group" :list="lists[`list${n}`]" group="people" #change="log">
<div
class="list-group-item"
v-for="(element, index) in lists[`list${n}`]"
:key="element.name"
>
{{ element.name }} {{ index }}
</div>
There is a lot more refactoring and other cleanup you could (and should) probably do but this should at least get you over this hurdle.

How should I define a concrete object name in vue components

Vue.component("blog-post", {
props: ["post"],
template: `
<div>
<h3>{{ post.title }}</h3>
<p> #####: {{ post.content }} </p>
</div>
`
});
new Vue({
el: "#blog-post-demo",
data: {
posts: [
{ id: 1, title: "My journey with Vue" },
{ id: 2, title: "Blogging with Vue" },
{ id: 3, title: "Why Vue is so fun" }
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.0/vue.js"></script>
<div id="blog-post-demo" class="demo">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.id"
></blog-post>
</div>
The above example is not working. But I am able to make the below one work.
Vue.component("blog-post", {
props: ["content", "title"],
template: `
<div>
<h3>{{ title }}</h3>
<p> #####: {{ content }} </p>
</div>
`
});
new Vue({
el: "#blog-post-demo",
data: {
posts: [
{ id: 1, title: "My journey with Vue" },
{ id: 2, title: "Blogging with Vue" },
{ id: 3, title: "Why Vue is so fun" }
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.0/vue.js"></script>
<div id="blog-post-demo" class="demo">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.id"
></blog-post>
</div>
Could someone explain what am I missing here?
In the first case, your component accept post as the prop, which is what you should pass from the parent component.
Vue.component("blog-post", {
props: ["post"],
template: `
<div>
<h3>{{ post.title }}</h3>
<p> #####: {{ post.content }} </p>
</div>
`
});
new Vue({
el: "#blog-post-demo",
data: {
posts: [
{ id: 1, title: "My journey with Vue" },
{ id: 2, title: "Blogging with Vue" },
{ id: 3, title: "Why Vue is so fun" }
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.0/vue.js"></script>
<div id="blog-post-demo" class="demo">
<blog-post
v-for="post in posts"
v-bind:post="post"
v-bind:key="post.id"
></blog-post>
</div>

Change component state form another component rendered in a v-for

I have a list of components rendered in a a v-for. I want to set the "show" Boolean property as false in the other components when one of them is set to true:
To simplify I am only adding two components
Main component code:
<template>
<aside class="main-sidebar">
<section class="sidebar">
<ul class="sidebar-menu" data-widget="tree">
<nav-bar-user-profile-item></nav-bar-user-profile-item>
<nav-bar-item></nav-bar-item>
<nav-bar-item></nav-bar-item>
</ul>
</section>
</aside>
</template>
<script>
import NavBarUserProfileItem from '#/components/NavBar/NavBarUserProfileItem';
import NavBarItem from '#/components/NavBar/NavBarItem';
export default {
name: 'NavBar',
components: {
NavBarUserProfileItem,
NavBarItem
},
methods: {
MenuHasBeenToggled(event) {
console.log(event);
}
}
}
NavBarItemComponent
<template>
<li class="treeview2 item" :class="{'menu-open': isOpen, 'active': menu.active}" #click="ToggleState">
<a href="#">
<i class="fa fa-th"></i>
<span>{{ menu.title }}</span>
<span class="pull-right-container">
<i class="fa fa-angle-right pull-right"></i>
</span>
</a>
<collapse-transition>
<ul class="treeview-menu" v-show="isOpen">
<li v-for="submenu in menu.submenus" :key="submenu.title" :class="{'active': (('active' in submenu) ? submenu.active : false)}">
<b-link :href="submenu.link">
<i class="fa fa-circle-thin"></i>
{{ submenu.title }}
</b-link>
</li>
</ul>
</collapse-transition>
</li>
</template>
<script>
export default {
name: 'NavBarItem',
data: function () {
return {
isOpen: false
}
},
computed: {
},
methods: {
ToggleState() {
this.isOpen = !this.isOpen;
this.$emit("toggle-state");
}
},
props: {
menu: {
type: Object,
default: function() {
return {
link: "#",
title: "Main menu",
active: true,
submenus: [
{
link: "#",
title: "Submenu 1",
},
{
link: "#",
title: "Submenu 2",
active: true
},
{
link: "#",
title: "Submenu 3",
},
]
}
}
}
}
}
</script>
<style scoped>
</style>
The goal is to click on one of the and show the menu contents while at the same time collapse the other components.
I thought about using an array of variables and bind it to the "show" prop and with an event listen to it and set every variable to false except the one form the component that sent the event.
How can I know which component sent the event?
Any better idea on how to accomplish this task?
I think, the best way to do it is to add a uniuque identifier property to each NavBarItem and a property for a selected NavBarItem. Then in the main component you can on click on NavBarItem set selected NavBarItem and in NavBarItem make the isOpen computed on the basis if current NavBarItem identifier equals the clicked NavBarItem. Something like this:
<template>
<aside class="main-sidebar">
<section class="sidebar">
<ul class="sidebar-menu" data-widget="tree">
<nav-bar-user-profile-item></nav-bar-user-profile-item>
<nav-bar-item item-id="1" :selected-item-id="selectedNavbarItemId" #click="selectedNavBarItemId = 1"></nav-bar-item>
<nav-bar-item item-id="2" :selected-item-id="selectedNavbarItemId" #click="selectedNavBarItemId = 2"></nav-bar-item>
</ul>
</section>
</aside>
</template>
<script>
import NavBarUserProfileItem from '#/components/NavBar/NavBarUserProfileItem';
import NavBarItem from '#/components/NavBar/NavBarItem';
export default {
name: 'NavBar',
components: {
NavBarUserProfileItem,
NavBarItem
},
data: function(){
return {
selectedNavBarItemId: 0
}
},
methods: {
MenuHasBeenToggled(event) {
console.log(event);
}
}
}
And in NavBarItem
<template>
<li class="treeview2 item" :class="{'menu-open': isOpen, 'active': menu.active}" #click="ToggleState">
<a href="#">
<i class="fa fa-th"></i>
<span>{{ menu.title }}</span>
<span class="pull-right-container">
<i class="fa fa-angle-right pull-right"></i>
</span>
</a>
<collapse-transition>
<ul class="treeview-menu" v-show="isOpen">
<li v-for="submenu in menu.submenus" :key="submenu.title" :class="{'active': (('active' in submenu) ? submenu.active : false)}">
<b-link :href="submenu.link">
<i class="fa fa-circle-thin"></i>
{{ submenu.title }}
</b-link>
</li>
</ul>
</collapse-transition>
</li>
</template>
<script>
export default {
name: 'NavBarItem',
data: function () {
return {
}
},
computed: {
isOpen:function(){
return itemId == selectedItemId;
}
},
methods: {
},
props: {
itemId:Number,
selectedItemId:Number,
menu: {
type: Object,
default: function() {
return {
link: "#",
title: "Main menu",
active: true,
submenus: [
{
link: "#",
title: "Submenu 1",
},
{
link: "#",
title: "Submenu 2",
active: true
},
{
link: "#",
title: "Submenu 3",
},
]
}
}
}
}
}
</script>
<style scoped>
</style>

how to add edit button on vue-good-table in vue

I m new to Vue and stuck in a situation and don't know how to do that if anybody suggests me how I can do this let me show my code first
<div class="table-responsive-sm">
<vue-good-table
title="Shop List Table"
:columns="columns"
:rows="rows"
:paginate="true"
:lineNumbers="true"
:globalSearch="true" >
<template slot="table-row" slot-scope="props" ><a class="btn btn-sm primary" #on-row-click="onRowClick">save</a></template>
</vue-good-table>
and in script
data(){
return{
columns: [
{
label: 'Brand Name',
field: 'brand_name',
},
{
label: 'Brand Desc',
field: 'brand_desc',
},
{
label: 'Action',
field: 'before',
},
],
rows:[],
}
}
getTotals(){
var self = this;
var new1=[];
this.$http.get('/api/brands')
.then(function (response) {
self.rows=response.data
})
},
now my problem is that if I use
<span v-if="props.column.field == 'before'">
before
</span>
as suggested in this https://jsfiddle.net/aks9800/hsf0sqf8/ it throws an error like field not defined I just want to add an extra action button for edit this is vue-good table one more thing none of the action as suggested in this link for eg:- #on-row-click="onRowClick" not working
Try this
<div class="table-responsive-sm">
<vue-good-table
title="Shop List Table"
:columns="columns"
:rows="rows"
:paginate="true"
:lineNumbers="true"
:globalSearch="true" >
<template slot="table-row" slot-scope="props" >
<a class="btn btn-sm primary" #on-row-click="onRowClick">save</a>
</template>
<template slot="table-row" slot-scope="props">
<span v-if="props.column.field == 'actions'">
<a class="btn btn-sm primary" #on-row-click="onRowClick">save</a>
</span>
<span v-else>
{{props.formattedRow[props.column.field]}}
</span>
</template>
</vue-good-table>
</div>
data(){
return{
columns: [
{
label: 'Brand Name',
field: 'brand_name',
},
{
label: 'Brand Desc',
field: 'brand_desc',
},
{
label: 'Actions',
field: 'actions',
sortable: false,
}
],
rows:[],
}
}
getTotals(){
var self = this;
var new1=[];
this.$http.get('/api/brands')
.then(function (response) {
self.rows=response.data
})
},
Here is very good example how to add "edit" button in a row by marekfilip
https://jsfiddle.net/marekfilip/jm4ywzor/
html:
<div id="app">
<vue-good-table
:columns="columns"
:rows="rows" >
<template slot="table-row" slot-scope="props">
<span v-if="props.column.field == 'before'">
<button #click="editRow(props.row.id)">Edit</button>
<button #click="deleteRow(props.row.id)">Delete</button>
</span>
<span v-else>
{{props.formattedRow[props.column.field]}}
</span>
</template>
</vue-good-table>
<span>{{ text }}</span>
</div>
javascript
new Vue({
el: '#app',
data() {
return {
columns: [
{
label: 'Before',
field: 'before'
},
{
label: 'ID',
field: 'id',
sortable: true,
},
{
label: 'Text',
field: 'text',
type: 'number',
sortable: true,
},
],
rows: [
{ text: 'A', id: 1 },
{ text: 'B', id: 2 },
{ text: 'C', id: 3 },
{ text: 'D', id: 5 },
],
text: ''
};
},
methods: {
editRow(id) {
this.showAlert(id, 'EDIT')
},
deleteRow(id) {
this.showAlert(id, 'DELETE')
},
showAlert(id, type) {
this.text = `You clicked ${type} on row ID ${id}`
}
}
});

Vue.js nested for loop input field model binding

In this example I am allowing the user to create their own typed list of sections. Each type has it's own form fields. The form fields render properly however, if I enter data into one of the fields after I've created two duplicate sections, both inputs are updated with the typed in text. This is not the intended result.
Instead each section should update its form field data content individually and it should reflect back to the value stored within the data.section related to it.
What am I missing?
Laravel View
{{Form::open(['route' => 'api.post.store', 'class' => 'form-horizontal'])}}
<fieldset>
<div id="legend">
<legend class="">Register</legend>
</div>
<div :key="section.id" v-for="(index,section) in sections" class="control-group form-group-lg">
<div class="form-header">
<h3>#{{ section.label }}</h3>
</div>
<pre>#{{ section | json }}</pre>
<div v-for="field in section.fields" :key="field.id">
<div class="text-field" v-show="field.inputType == 'text'">
<label class="control-label" :for="section.name">#{{ field.label }}</label>
<div class="controls">
<input v-model="field.data.content" class="input-xlarge form-control">
<p class="help-block">#{{ field.helpText }}</p>
</div>
</div>
<div class="text-area-field" v-show="field.inputType == 'text-area'">
<label class="control-label" :for="section.name">#{{ field.label }}</label>
<div class="controls">
<textarea :v-bind="field.data.content" class="input xlarge form-control" :placeholder="field.placeholder">
#{{ field.data.content }}
</textarea>
</div>
</div>
<div class="text-area-field" v-show="field.inputType == 'data-map'">
<label class="control-label" :for="section.name">#{{ field.label }}</label>
<div class="controls">
<textarea :v-bind="field.data.content" class="input xlarge form-control" :placeholder="field.placeholder">
#{{ field.data.content }}
</textarea>
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div class="dropdown">
<a data-target="#" href="page.html" data-toggle="dropdown" class="dropdown-toggle">Dropdown <b class="caret"></b></a>
<ul class="dropdown-menu">
<li v-for="sectionType in sectionTypes">
<a #click="setSectionCreateType(sectionType)" href="#">#{{ sectionType.label }}</a>
</li>
</ul>
</div>
</div>
<div class="controls">
<div #click="addSection()" class="btn bdn-success" class="btn btn-success">Add Section</div>
<div #click="savePost()" class="btn bdn-success" class="btn btn-success">Save</div>
</div>
</div>
</fieldset>
{{Form::close()}}
Vuefile
<script type="text/javascript">
import Vue from 'vue';
import FormField from './create/FormField.vue';
export default {
components: {
FormField,
},
ready: function () {
},
filters: {},
data(){
return {
messages: [],
sections: [],
saveSections: [],
sectionCreateType: false,
sectionTypes: [
{
label: 'Company',
map: 'company',
fields: [
{
label: 'name',
name: 'name',
inputType: 'text',
placeholder: 'Company Name',
data: {
content: '',
},
},
{
label: 'symbol',
name: 'symbol',
inputType: 'text',
placeholder: 'stock symbol',
data: {
content: '',
},
}
]
},
{
label: 'Link',
map: 'link',
inputType: 'text',
data: {},
fields: [
{
label: 'url',
name: 'url',
inputType: 'text',
placeholder: 'Url',
data: {
content: '',
},
},
]
},
{
label: 'Paragraph',
map: 'paragraph',
data: {},
fields: [
{
label: 'content',
name: 'content',
inputType: 'text-area',
placeholder: 'Content',
data: {
content: '',
},
},
]
},
{
label: 'Person',
map: 'person',
data: {},
inputType: 'data-map',
'fields': [
{
label: 'first_name',
name: 'name',
placeholder: 'Person Name',
data: {
content: '',
},
},
{
label: 'last_name',
name: 'name',
placeholder: 'Person Name',
data: {
content: '',
},
}
]
},
],
}
},
directives: {},
events: {},
methods: {
setSectionCreateType(type)
{
console.log('setting sectionCreateType: ' + type.label)
this.sectionCreateType = type;
},
addSection()
{
if (!this.sectionCreateType) {
this.sectionCreateType = this.sectionTypes[0];
}
this.createSection(this.sectionCreateType);
},
createSection(type)
{
this.sections.push(Vue.util.extend({}, type))
},
previewPost(){
},
savePost: function(){
var view = this;
var saveObject = [];
var sectionObject = [];
this.sections.forEach(function (section) {
if(!sectionObject[section.type.map])
{
sectionObject[section.type.map] = [];
}
for (var key in section.type.fields) {
var field = section.type.fields[key];
var saveKey = [];
saveKey[field.name] = field.data.content;
}
sectionObject[section.type.map].push(saveKey);
});
saveObject.push(sectionObject);
console.log(saveObject);
},
}
}
</script>
You are using the same v-model so VueJS does what it should do.
You have to create e.g. list of models and somehow handle index (e.g. take it from v-for for every section/subsection and use v-model='list[index].field