I'm trying to show b-overlay inside a loop. i.e. there are multiple rows and each row is having a button(trigger) with an overlay. The issue that I'm facing is that I don't see a way to trigger a overlay that is specific to a row when the relevant button is clicked. If I set showOverlay = true, it triggers all overlays at once.
Is there a way to achieve this? e.g. can I trigger the overlay using its id?
<template>
<b-row
v-for="(subOrder, index) in subOrders"
:key="subOrder.subOrderId"
>
<b-overlay show:showOverlay rounded="sm">
<b-col md=6>
{{ subOrder.orderNumber }}
<b-button
size="lg"
variant="primary"
#click="onSubmit(subOrder.subOrderId)"
>
Submit
</b-button>
</b-col>
</b-overlay>
</b-row>
</template>
<script>
import { BRow, BCol, BButton, BOverlay,} from 'bootstrap-vue'
export default {
components: {
BRow,
BCol,
BButton,
BOverlay,
},
data() {
return {
showOverlay: false,
}
},
methods: {
onSubmit(id) {
this.showOverlay = true;
}
},
}
</script>
you should create a variable inside your array using a map like subOrders example subOrders.map(s => ({...s, showOverlay : false}))
and them in your <b-overlay show:subOrder.showOverlay rounded="sm">
and in the buttom to toogle overlay = #click="subOrder.showOverlay = !subOrder.showOverlay"
Related
I have a Vue application with many child components. In my case, I have some parent-child components like this. The problem is that in some child components, I have a section to edit information. In case the user has entered some information and router to another page but has not saved it, a modal will be displayed to warn the user. I followed the instructions on beforeRouteLeave and it work well but I got a problem. When I click the Yes button from the modal, I'll emit a function #yes='confirm' to the parent component. In the confirm function, I'll set this.isConfirm = true. Then I check this variable inside beforeRouteLeave to confirm navigate. But in fact, when I press the Yes button in modal, the screen doesn't redirect immediately. I have to click one more time to redirect. Help me with this case
You can create a base component like the following one - and then inherit (extend) from it all your page/route-level components where you want to implement the functionality (warning about unsaved data):
<template>
<div />
</template>
<script>
import events, { PAGE_LEAVE } from '#/events';
export default
{
name: 'BasePageLeave',
beforeRouteLeave(to, from, next)
{
events.$emit(PAGE_LEAVE, to, from, next);
}
};
</script>
events.js is simply a global event bus.
Then, in your page-level component you will do something like this:
<template>
<div>
.... your template ....
<!-- Unsaved changes -->
<ConfirmPageLeave :value="modified" />
</div>
</template>
<script>
import BasePage from 'src/components/BasePageLeave';
import ConfirmPageLeave from 'src/components/dialogs/ConfirmPageLeave';
export default
{
name: 'MyRouteName',
components:
{
ConfirmPageLeave,
},
extends: BasePage,
data()
{
return {
modified: false,
myData:
{
... the data that you want to track and show a warning
}
};
}.
watch:
{
myData:
{
deep: true,
handler()
{
this.modified = true;
}
}
},
The ConfirmPageLeave component is the modal dialog which will be shown when the data is modified AND the user tries to navigate away:
<template>
<v-dialog v-model="showUnsavedWarning" persistent>
<v-card flat>
<v-card-title class="pa-2">
<v-spacer />
<v-btn icon #click="showUnsavedWarning = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text class="pt-2 pb-3 text-h6">
<div class="text-h4 pb-4">{{ $t('confirm_page_leave') }}</div>
<div>{{ $t('unsaved_changes') }}</div>
</v-card-text>
<v-card-actions class="justify-center px-3 pb-3">
<v-btn class="mr-4 px-4" outlined large tile #click="showUnsavedWarning = false">{{ $t('go_back') }}</v-btn>
<v-btn class="ml-4 px-4" large tile depressed color="error" #click="ignoreUnsaved">{{ $t('ignore_changes') }}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import events, { PAGE_LEAVE } from '#/events';
export default
{
name: 'ConfirmPageLeave',
props:
{
value:
{
// whether the monitored data has been modified
type: Boolean,
default: false
}
},
data()
{
return {
showUnsavedWarning: false,
nextRoute: null,
};
},
watch:
{
showUnsavedWarning(newVal)
{
if (!newVal)
{
this.nextRoute = null;
}
},
},
created()
{
events.$on(PAGE_LEAVE, this.discard);
window.addEventListener('beforeunload', this.pageLeave);
},
beforeDestroy()
{
events.$off(PAGE_LEAVE, this.discard);
window.removeEventListener('beforeunload', this.pageLeave);
},
methods:
{
discard(to, from, next)
{
if (this.value)
{
this.nextRoute = next;
this.showUnsavedWarning = true;
}
else next();
},
pageLeave(e)
{
if (this.value)
{
const confirmationMsg = this.$t('leave_page');
(e || window.event).returnValue = confirmationMsg;
return confirmationMsg;
}
},
ignoreUnsaved()
{
this.showUnsavedWarning = false;
if (this.nextRoute) this.nextRoute();
},
}
};
</script>
<i18n>
{
"en": {
"confirm_page_leave": "Unsaved changes",
"unsaved_changes": "If you leave this page, any unsaved changes will be lost.",
"ignore_changes": "Leave page",
"go_back": "Cancel",
"leave_page": "You're leaving the page but there are unsaved changes.\nPress OK to ignore changes and leave the page or CANCEL to stay on the page."
}
}
</i18n>
Hey there I am new to vue js and vuetify.In my editProductSave I want to pass another variable which is the index of the row in the table. This is my current code and how would i achieve that? The table is plotted using the vuetify v-data-table
Whole code
<template>
<v-card>
<v-data-table
:headers="tableFields"
:items="programs"
:items-per-page="5">
<template v-slot:[`item._id.$oid`]="{ item }">
{{item._id.$oid}}
</template>
<template v-slot:[`item.tags`]="props">
<v-edit-dialog
:return-value.sync="props.item.tags"
large
persistent
#save="editProductSave(props.item)">
<div>{{props.item.tags.length === 0 ? '' : props.item.tags}}</div>
<template v-slot:input>
<div class="mt-1 text-h2">
Update Tag
</div>
<v-text-field
v-model="props.item.tags"
label="Edit"
single-line
counter
autofocus
></v-text-field>
</template>
</v-edit-dialog>
</template>
<script>
import tdmApi from "../services/api/Database";
export default {
props: ["DatabaseList"],
computed: {
totalRows() {
return this.programs.length;
},
},
created () {
this.viewTdmDatabase();
},
data () {
return {
tableFields: [
{text:'ID',value:'_id.$oid'},
{text:'Tag',value:'tags'},
],
programs: [],
}
},
</script>
<template v-slot:item.tags="{item,index}">
{{index}} //Output index
</template>
The code above should work, make sure to cover it with object.
Try the below code:
<template v-slot:[`item.tags`]="{props, index}">
<v-edit-dialog
:return-value.sync="props.item.tags"
large persistent
#save="editProductSave(props.item, index)">
// ...
</v-edit-dialog>
</template>
And in script the method would be
methods: {
editProductSave(item, index) {
// ...
}
}
It seems that vuetify does not have in the v-data-table api the index field, so in order to get it you can change the structure of the v-data-table.
This is an example of how to get the index of each row.
https://www.codegrepper.com/code-examples/whatever/vuetify+v-data-table+get+row+index
You can simply add the index to the programs in a computed property and import it in the data table like so:
template
...
<v-data-table
:headers="tableFields"
:items="programsComputed"
...
script
export default {
...
computed: {
totalRows() {
return this.programs.length;
},
programsComputed () {
return this.programs.map((program, index) => {
program.index = index;
return program;
})
}
},
...
data () {
return {
tableFields: [
{text:'ID',value:'_id.$oid'},
{text:'Tag',value:'tags'},
],
programs: [],
}
},
In your editProductSave(item) you would just have to call item.index
I'm new to vue and decided to try out buefy for some useful components.
To try and keep my code organized I'm trying to make a custom component using the b-taginput.
I have it so that the component loads with the tags in someArrayofTags, but when I'm typing into the b-taginput, it does not add new tags into someArrayofTags. Hence I lose the two-way binding / updating. I would like to know where I am going wrong and how I could adjust this.
I'm not too well versed to understand how they have implemented it, but i do see that it is composed of autocomplete and b-tags https://github.com/buefy/buefy/blob/dev/src/components/taginput/Taginput.vue
I'm trying to use the custom component as such
<mytaglist v-model="someArrayofTags"></mytaglist>
I know v-model is just v-bind on value and #input events. My code is below.
<template>
<b-field label="tag inputs">
<b-taginput
:value="value"
#input=someMethod($event.target.value)
size="is-small"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear"]
};
},
methods: {
someMethod(tags) {
alert(tags)
this.$emit("input", tags)
},
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
};
</script>
Thanks
After going through the source for buefy, I found that I could watch and update the values based on a v-model within the new component.
The code below works for me, but if anyone could provide a better solution I will leave it open.
<template>
<b-field label="tag inputs">
<b-taginput
v-model="newValue"
size="is-large"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear","deep & quiet"],
newValue: this.value
};
},
methods: {
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
watch: {
newValue(value) {
this.$emit('input', value)
},
value(value) {
this.newValue = value
}
}
};
</script>
<style>
</style>
I have an vue component which uses Vue Draggable .
<template>
<div class="row my-5">
<div v-for="column in columns" :key="column.title" class="col">
<p class="font-weight-bold text-uppercase">{{column.title}}</p>
<!-- Draggable component comes from vuedraggable. It provides drag & drop functionality -->
<draggable :list="column.tasks" :animation="200" ghost-class="ghost-card" group="tasks" :move="checkMove">
<transition-group>
<task-card
v-for="(task) in column.tasks"
:key="task.id"
:task="task"
class="mt-3 cursor-move"
></task-card>
<!-- </transition-group> -->
</transition-group>
</draggable>
</div>
</div>
</template>
<script>
import draggable from "vuedraggable";
import TaskCard from "../board/TaskCard";
export default {
name: "App",
components: {
TaskCard,
draggable,
},
data() {
return {
columns: [
.....
],
};
},
methods: {
checkMove: function(evt){
console.log('moved');
}
},
};
</script>
In TaskCard Component -
<template>
<div class="bg-white shadow rounded p-3 border border-white">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>{{task.id}}</h2>
<span>{{task.date}}</span>
</div>
<p class="font-weight-bold">{{task.title}}</p>
</div>
</template>
<script>
export default {
props: {
task: {
type: Object,
default: () => ({}),
},
},
};
</script>
When I move an item, I want a modal that confirms the change and only then move the item.
(ie. if I click on the cancel button inside the modal, the item should not be moved.)
How can this be achieved using the checkMove() function provided?
I don't think you can achieve this by using onMove event. The onEnd event it seems more suitable but unfortunately it doesn't have any cancel drop functionality.
So I think the only solution here is revert it back if the user decides to cancel.
You can listen on change event (See more in documentation)
<draggable
group="tasks"
v-model="column.tasks"
#change="handleChange($event, column.tasks)">
...
</draggable>
...
<button #click="revertChanges">Cancel</button>
<button #click="clearChanges">Yes</button>
And
...
handleChange(event, list) {
this.changes.push({ event, list })
this.modal = true
},
clearChanges() {
this.changes = []
this.modal = false
},
revertChanges() {
this.changes.forEach(({ event, list }) => {
if (event.added) {
let { newIndex } = event.added
list.splice(newIndex, 1)
}
if (event.removed) {
let { oldIndex, element } = event.removed
list.splice(oldIndex, 0, element)
}
if (event.moved) {
let { newIndex, oldIndex, element } = event.moved
list[newIndex] = list[oldIndex]
list[oldIndex] = element
}
})
this.changes = []
this.modal = false
}
...
JSFiddle example
Is possible to generate vue component using for loop. I am trying to generate and able to get but it's override by new component dynamically it's override component one schema also with component second with is at last generated.
https://jsfiddle.net/3ordn7sj/5/
https://jsfiddle.net/bt5dhqtf/973/
for (var key in app.html) {
Vue.component(key, {
template: `<div><vue-form-generator :model="model"
:schema="schema"
:options="formOptions"
ref="key"></vue-form-generator>{{ key }}</div>`,
mounted() {
this.schema = app.html[key]
},
data: function () {
return {
schema: app.html[key],
key: '',
formOptions: this.formOptions,
model: this.model,
}
},
}
)
}
Is possible to generate vue component using for loop. I am trying to generate and able to get but it's override by new component dynamically it's override component one schema also with component second with is at last generated. In above jsfiddel link my data is there inside created.
I am trying to generate vue component base on this data and I am using vue form generator.In above code what exactly I am trying to do is while my loop running form generated but I don't know how it's first component also getting second component schema and it;s showing on first step also overrides schema data.
I am very confused why this is happening I tried a lot but I am not getting any solution if you have please suggest what I can do for generate component using for loop inside function.
Please try to solve this issue or tell me id it;s not possible.
I did like this
<form-wizard #on-complete="onComplete"
#on-change="handleChange"
validate-on-back
ref="wizard"
:start-index.sync="activeTabIndex"
shape="circle" color="#20a0ff" error-color="#ff4949" v-if="html != 0">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component"></component>
</tab-content>
</form-wizard>
Inside Data I have added for now this tabs option
tabs: [{title: 'Personal details', icon: 'ti-user', component: 'firstTabSchema'},
{title: 'Is Logged In?', icon: 'ti-settings', component: 'secondTabSchema', hide: false},
],
generateNewForm.vue
<template>
<div class="app animated fadeIn">
<loading :active.sync="this.$store.state.isLoading"
:can-cancel="true"
:is-full-page="this.$store.state.fullPage"></loading>
<b-row>
<b-col cols="12" xl="12">
<transition name="slide">
<b-card>
<div slot="header">
<b-button variant="primary" #click="goBack"><i class="icon-arrow-left icons font-1xl"></i>Back</b-button>
</div>
<formcomponent :tree="this.$store.state.formData" />
</b-card>
</transition>
</b-col>
</b-row>
</div>
</template>
<script>
import {store} from '#/components/store'
import formcomponent from '#/components/formcomponent';
import Vue from 'vue'
import Loading from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/vue-loading.css';
import {FormWizard, TabContent} from 'vue-form-wizard'
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
import VueFormGenerator from "vue-form-generator";
/*import VeeValidate from 'vee-validate';*/
Vue.use(VueFormGenerator);
Vue.use(Loading);
export default {
name: 'tables',
store: store,
data: () => {
return {
finalModel: {},
activeTabIndex: 0,
model: {},
count: 0,
}
},
components: {
'loading': Loading,
FormWizard,
TabContent,
formcomponent: formcomponent
},
created() {
},
beforeMount() {
this.$store.dispatch('loadFormData', this.$route.params.id);
},
methods: {
onComplete: function(){
alert('Yay. Done!');
},
goBack() {
this.$router.go(-1)
}
}
}
</script>
formcomponent.vue
<template>
<div>
<form-wizard #on-complete="onComplete"
#on-change="handleChange"
validate-on-back
ref="wizard"
:start-index.sync="activeTabIndex"
shape="circle" color="#20a0ff" error-color="#ff4949" v-if="html != 0">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component"></component>
</tab-content>
</form-wizard>
</div>
</template>
<script>
import Vue from 'vue'
import {FormWizard, TabContent} from 'vue-form-wizard'
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
import VueFormGenerator from "vue-form-generator";
//console.log(Vue.options);
Vue.use(VueFormGenerator);
export default {
components: {
FormWizard,
TabContent,
},
data() {
return {
loadingWizard: false,
error: null,
count: 0,
dash: '-',
firstTime: 0,
model: {},
html: '',
index: '',
activeTabIndex: 0,
tabs: [{title: 'Personal details', icon: 'ti-user', component: 'firstTabSchema'},
{title: 'Is Logged In?', icon: 'ti-settings', component: 'secondTabSchema', hide: false},
],
formOptions: {
validationErrorClass: "has-error",
validationSuccessClass: "has-success",
validateAfterLoad: true,
validateAfterChanged: true,
},
}
},
created() {
this.html = this.tree;
this.index = this.ind;
},
props: ['tree', 'ind'],
methods: {
onComplete: function () {
alert('Yay. Done!');
},
setLoading(value) {
this.loadingWizard = value
},
handleChange(prevIndex, nextIndex) {
console.log(`Changing from ${prevIndex} to ${nextIndex}`)
},
setError(error) {
this.error = error
},
validateFunction: function () {
return new Promise((resolve, reject) => {
console.log(this.$refs.firstTabSchema);
setTimeout(() => {
if (this.count % 2 === 0) {
reject('Some custom error')
} else {
resolve(true)
}
this.count++
}, 100)
})
},
validate() {
return true
},
buildTree(tree, rep = 1) {
var html = '';
var app = this;
var dash = "--";
app.html = tree;
var test = this.formOptions;
for (var key in app.html) {
var isComponentExists = key in Vue.options.components
if(!isComponentExists) {
Vue.component(key, {
template: `<div :class="key"><vue-form-generator :model="model"
:schema="schema"
:options="formOptions"
ref="key"></vue-form-generator>{{ key }}</div>`,
mounted() {
this.schema = app.html[key]
this.key = key
},
data: function () {
return {
schema: app.html[key],
key: '',
formOptions: this.formOptions,
model: this.model,
}
},
}
)
//console.log(Vue.$options);
this.$emit('init')
}
}
}
},
watch: {
tree: {
handler() {
this.html = '';
this.buildTree(this.tree)
},
deep: true
}
}
}
</script>
So if I understand you correctly you are trying to use a list of some kind app.html to dynamically register a set of identical components under different names (key). I think it is possible, but i cannot tell from the code you provided what is going wrong.
I can tell you that this approach to code reuse/abstraction is probably not the right way to go. The whole point of components is that you can reuse functionality with the use of binding props. What you are trying to do is probably better achieved like this:
Vue.component('my-custom-form', {
props: ['key', 'schema', 'formOptions', 'model'],
template: `
<div>
<vue-form-generator
:model="model"
:schema="schema"
:options="formOptions"
:ref="key"
></vue-form-generator>{{ key }}
</div>`,
})
Then in your vue template:
<my-custom-form
v-for="(key, value) in app.html"
:key="key"
:schema="value"
:formOptions="formOptions"
:model="model"
/>
Let me know if that helps. Otherwise, if you are sure you want to stick with your original approach give me some more context for the code and I will see what i can do. Best of luck!
I think i understand a little bit better where you are getting stuck. I see this piece of code in your jsfiddle:
<div id="app">
<div>
<form-wizard #on-complete="onComplete">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component"></component>
</tab-content>
</form-wizard>
</div>
</div>
You don't need to use the component :is syntax to solve this problem. You could also write is as follows:
<div id="app">
<div>
<form-wizard #on-complete="onComplete">
<tab-content v-for="(tab, tabindex) in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<my-custom-form v-if="tabindex == 1" :key="'the first key'" :schema="app.html['the first key']"/>
<my-custom-form v-else-if="tabindex == 2" :key="'the second key'" :schema="app.html['the second key']"/>
</tab-content>
</form-wizard>
</div>
</div>
etc. Let me know if that example is clear.
best