Vue form Wizard prevent back step - javascript

we are working with injection of dynamic components by server response, but once the user has approved a step, we will prevent him from going back to the steps he has already approved.
HTML
<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>
JS
Vue.use(VueFormWizard)
Vue.component('step1', {
template:` <div> My first tab content <br>
</div>`
}
)
Vue.component('step2', {
template:`<div> My second tab content </div>`
})
Vue.component('step3', {
template:`<div> My third tab content </div>`
})
Vue.component('step4', {
template:`<div> Yuhuuu! This seems pretty damn simple </div>`
})
new Vue({
el: '#app',
data() {
return {
tabs: [{title: 'Personal details', icon: 'ti-user', component: 'step1'},
{title: 'Is Logged In?', icon: 'ti-settings', component: 'step2', hide: false},
{title: 'Additional Info', icon: 'ti-location-pin', component: 'step3'},
{title: 'Last step', icon: 'ti-check', component: 'step4'},
],
}
},
methods: {
onComplete: function(){
alert('Yay. Done!');
}
}
})
but we have not found answers in the documentation if suddenly someone has had this problem and can tell us how to solve it, I would appreciate it, thanks.

Once the user has approved a step, we will prevent him from going back
to the steps he has already approved.
Validate going forward, then simply remove the back button.
I did some tests and the beforeTabSwitch doesn't fire if going backwards props.prevTab(), shame as you could then do it in the validate call.
Here is an example, which validates going forward and removes the Previous button and prevents navigating via the header (wizard-step).
Vue.use(VueFormWizard)
Vue.component('step1', {
template: ` <div> My first tab content</div>`,
data: () => ({
name: ''
}),
methods: {
validate() {
// change `true` to things checked on model, beyond scope of question
this.$emit('on-validate', this.$data, true)
return true
}
}
})
Vue.component('step2', {
template: `<div> My second tab content </div>`,
data: () => ({
logged_in_yada: ''
}),
methods: {
validate() {
this.$emit('on-validate', this.$data, true)
return true
}
}
})
Vue.component('step3', {
template: `<div> My third tab content </div>`,
data: () => ({
additional_info: ''
}),
methods: {
validate() {
this.$emit('on-validate', this.$data, true)
return true
}
}
})
Vue.component('step4', {
template: `<div> Yuhuuu! This seems pretty damn simple </div>`,
data: () => ({
last_step: ''
}),
methods: {
validate() {
this.$emit('on-validate', this.$data, true)
return true
}
}
})
new Vue({
el: '#app',
data() {
return {
tabModel: {},
tabs: [{
title: 'Personal details',
icon: 'ti-user',
component: 'step1'
},
{
title: 'Is Logged In?',
icon: 'ti-settings',
component: 'step2',
hide: false
},
{
title: 'Additional Info',
icon: 'ti-location-pin',
component: 'step3'
},
{
title: 'Last step',
icon: 'ti-check',
component: 'step4'
},
],
}
},
methods: {
onComplete: function() {
alert('Yay. Done!');
},
validateStep(name) {
return this.$refs[name][0].validate()
},
mergeTabModel(model, isValid) {
if (isValid) {
// merging each step model into the final model
this.tabModel = Object.assign({}, this.tabModel, model)
}
}
}
})
<script src="https://vuejs.org/js/vue.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-form-wizard/dist/vue-form-wizard.min.css">
<script src="https://unpkg.com/vue-form-wizard/dist/vue-form-wizard.js"></script>
<link rel="stylesheet" href="https://rawgit.com/lykmapipo/themify-icons/master/css/themify-icons.css">
<div id="app">
<div>
<form-wizard #on-complete="onComplete">
<wizard-step slot-scope="props" slot="step" :tab="props.tab" :transition="props.transition" :index="props.index">
</wizard-step>
<tab-content v-for="tab in tabs" v-if="!tab.hide" :key="tab.title" :title="tab.title" :icon="tab.icon" :before-change="()=>validateStep(tab.component)">
<component :is="tab.component" :ref="tab.component" #on-validate="mergeTabModel"></component>
</tab-content>
<template slot="footer" scope="props">
<div class="wizard-footer-left">
<!-- remove previous button -->
<!-- <wizard-button v-if="props.activeTabIndex > 0 && !props.isLastStep" #click.native="props.prevTab()" :style="props.fillButtonStyle">Previous</wizard-button> -->
</div>
<div class="wizard-footer-right">
<wizard-button #click.native="props.nextTab()" class="wizard-footer-right finish-button" :style="props.fillButtonStyle">{{props.isLastStep ? 'Done' : 'Next'}}</wizard-button>
</div>
</template>
</form-wizard>
<pre>{{ tabModel }}</pre>
</div>
</div>

using refs
this.$refs.wizardFirst.displayPrevButton = false
"vue-form-wizard": "0.8.4",

use this CSS instead:
.vue-form-wizard .wizard-nav-pills a, .vue-form-wizard .wizard-nav-pills li{
cursor: not-allowed;
pointer-events: none;
}

Related

How to pass data after page loading to components in VueJS?

In my project I use Vue.js and Nuxt.js and I have this page.
This page is settings page, where user can changes his settings. As you can see, this is only one page, where user can switch between tabs.
<template>
<div class="account-wrapper">
<div class="avatar" #click="redirect('/account/me')">
<img class='avatar-box' src="../../../assets/img/testava.jpg" alt="ava">
<div class="avatar-text">
<h2 class="nmp">{{ personalSettings.username }}</h2>
<p class="paragraph opacity nmp">Public profile</p>
</div>
</div>
<div class="side-bar">
<div v-for="item in accountHeaderItems" :key="item.title" class="flex">
<div v-if="item.active" class="vertical-line" />
<p :class="[item.active ? 'item item-active' : 'item']" #click="changeSubsection(item)">
{{ item.title }}
</p>
</div>
</div>
<personal-information v-if="currentSection === 'Public account'" :personal-settings="personalSettings" />
<security-settings v-else-if="currentSection === 'Security settings'" :security-settings="securitySettings" />
<site-settings v-else />
</div>
</template>
<script>
import SecuritySettings from '~/components/pageComponents/settings/SecuritySettings'
import PersonalInformation from '~/components/pageComponents/settings/PersonalInformation'
import SiteSettings from '~/components/pageComponents/settings/SiteSettings'
import { getUserSettings } from "~/api";
export default {
name: 'Settings',
components: {
SecuritySettings,
PersonalInformation,
SiteSettings
},
data() {
return {
accountHeaderItems: [
{ title: 'Public account', active: true },
{ title: 'Security settings', active: false },
{ title: 'Appearance settings', active: false },
{ title: 'Notifications', active: false }
],
currentSection: 'Public account',
personalSettings: {},
securitySettings: {},
}
},
async mounted() {
if (localStorage.getItem('token') !== null) await this.getUsersSettings(localStorage.getItem('token'))
else await this.$router.push('/')
},
methods: {
async getUsersSettings(token) {
const userSettings = await getUserSettings(token)
if (userSettings.status === -1)
return this.$router.push('/')
this.personalSettings = userSettings.personalSettings
this.securitySettings = userSettings.securitySettings
},
changeSubsection(item) {
this.currentSection = item.title
this.accountHeaderItems.forEach(header => {
header.active = item.title === header.title
})
},
redirect(path) {
this.$router.push(path)
},
}
}
</script>
The problem is when page loads. When in async mounted() I get data I want to pass it to my components. And here is the problem, when I try to do that it seems to work fine, but there is strange behaviour, I always need to switch between tabs, to make data be visible on page.
For example - in personalSettings object there is field first_name. So, in personal-information component in custom Input I want to show this data in this way (in mounted I make copy of object to prevent mutations):
<Input
v-model="personalInfo.first_name"
:title="'First name'"
:title-class="'small'"
:additional-class="'small'"
/>
...
props: {
personalSettings: {
type: Object,
default: () => {}
}
},
data() {
return {
personalInfo: {},
loading: false,
showPopup: false
}
},
mounted() {
this.personalInfo = this.personalSettings
},
Everything seems to be fine, but, actually, I have to switch to another tab and switch back to this tab to see this data. What's wrong? How can I prevent this behaviour and show data in correct way?
There are many ways to do it, you can use Store, and emit changes and Data you want to use late.
See: https://vuex.vuejs.org/guide/#the-simplest-store

Vue.js - How to dynamically bind v-model to route parameters based on state

I'm building an application to power the backend of a website for a restaurant chain. Users will need to edit page content and images. The site is fairly complex and there are lots of nested pages and sections within those pages. Rather than hardcode templates to edit each page and section, I'm trying to make a standard template that can edit all pages based on data from the route.
I'm getting stuck on the v-model for my text input.
Here's my router code:
{
path: '/dashboard/:id/sections/:section',
name: 'section',
component: () => import('../views/Dashboard/Restaurants/Restaurant/Sections/Section.vue'),
meta: {
requiresAuth: true
},
},
Then, in my Section.vue, here is my input with the v-model. In this case, I'm trying to edit the Welcome section of a restaurant. If I was building just a page to edit the Welcome text, it would work no problem.:
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
This issue is that I need to reference the "welcome" part of the v-model dynamically, because I've got about 40 Sections to deal with.
I can reference the Section to edit with this.$route.params.section. It would be great if I could use v-model="restInfo. + section", but that doesn't work.
Is there a way to update v-model based on the route parameters?
Thanks!
Update...
Here is my entire Section.vue
<template>
<div>
<Breadcrumbs :items="crumbs" />
<div v-if="restInfo">
<h3>Update {{section}}</h3>
<div class="flex flex-wrap">
<div class="form__content">
<form #submit.prevent>
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
<div class="flex">
<button class="btn btn__primary mb-3" #click="editText()">
Update
<transition name="fade">
<span class="ml-2" v-if="performingRequest">
<i class="fa fa-spinner fa-spin"></i>
</span>
</transition>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { VueEditor } from "vue2-editor"
import Loader from '#/components/Loader.vue'
import Breadcrumbs from '#/components/Breadcrumbs.vue'
export default {
data() {
return {
performingRequest: false,
}
},
created () {
this.$store.dispatch("getRestFromId", this.$route.params.id);
},
computed: {
...mapState(['currentUser', 'restInfo']),
section() {
return this.$route.params.section
},
identifier() {
return this.restInfo.id
},
model() {
return this.restInfo.id + `.` + this.section
},
crumbs () {
if (this.restInfo) {
let rest = this.restInfo
let crumbsArray = []
let step1 = { title: "Dashboard", to: { name: "dashboard"}}
let step2 = { title: rest.name, to: { name: "resthome"}}
let step3 = { title: 'Page Sections', to: { name: 'restsections'}}
let step4 = { title: this.$route.params.section, to: false}
crumbsArray.push(step1)
crumbsArray.push(step2)
crumbsArray.push(step3)
crumbsArray.push(step4)
return crumbsArray
} else {
return []
}
},
},
methods: {
editText() {
this.performingRequest = true
this.$store.dispatch("updateRest", {
id: this.rest.id,
content: this.rest
});
setTimeout(() => {
this.performingRequest = false
}, 2000)
}
},
components: {
Loader,
VueEditor,
Breadcrumbs
},
beforeDestroy(){
this.performingRequest = false
delete this.performingRequest
}
}
</script>
Try to use the brackets accessor [] instead of . :
<vue-editor v-model="restInfo[section]"

Vue Events - Can't listen to $emit event from child component

I'm having a simple issue, that I just can't figure out why it isn't working.
I have a child component "app-buttons", where i have an input field, i want to listen to, so i can filter a list based on the input value.
If i put the input in the root component where i have the list, all works good. But i want to split it up, and $emit the search input value, to the parent en then use it.
// THIS IS THE COMPONENT WHERE I WAN'T TO LISTEN TO THE SEARCH INPUT
import Buttons from './app-buttons.js';
import Event from './vue-event.js';
export default Vue.component('post-item', {
template: `
<section class="posts flex" v-if="posts">
<app-buttons :posts="posts"></app-buttons>
<transition-group name="posts" tag="section" class="posts flex">
<article class="postitem" :class="{ 'danger': !post.published }" v-for="post in posts" :key="post.id">
<p v-if="post.published">Post is published</p>
<p v-else>Post is <em><strong>not</strong></em> published</p>
<h4>{{ post.title }}</h4>
<button type="submit" #click="post.published = !post.published">Change status</button>
</article>
</transition-group>
</section>
`,
data() {
return {
};
},
components: {
Buttons,
},
props: {
posts: Array,
filterdList: [],
},
created() {
console.log('%c Post items', 'font-weight: bold;color:blue;', 'created');
},
computed: {
//filteredList() {
// return this.posts.filter(post => {
// return post.title.toLowerCase().includes(this.search.toLowerCase());
// });
//},
},
methods: {
update() {
console.log('test');
}
}
});
// THIS IS THE COMPONENT IM TRIGGERING THE $EVENT FROM
import Event from './vue-event.js';
export default Vue.component('app-buttons', {
template: `
<div class="postswrapper flex flex--center flex--column">
<section :class="className" class="flex flex--center">
<button type="button" #click="showUnpublished">Unpublish</button>
<button type="button" #click="shufflePosts">Shuffle</button>
</section>
<section :class="className">
<input type="text" v-model="search" v-on:input="updateValue" placeholder="Search..." />
</section>
</div>
`,
data() {
return {
className: 'buttons',
search: '',
}
},
props: {
posts: Array,
},
created() {
//console.log(this);
console.log('%c Buttons', 'font-weight: bold;color:blue;', 'created');
},
methods: {
updateValue() {
//console.log(this.search);
this.$emit('searchquery', this.search);
},
showUnpublished() {
this.posts.forEach(item => {
item.published = true;
})
},
shufflePosts() {
this.$emit('postshuffled', 'Fisher-Yates to the rescue');
for (var i = this.posts.length - 1; i >= 0; i--) {
let random = Math.floor(Math.random() * i);
let temp = this.posts[i];
Vue.set(this.posts, i, this.posts[random]);
Vue.set(this.posts, random, temp);
}
},
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue JS</title>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.6/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="app">
<app-header :logo="logo" :name="name"></app-header>
<post-item :posts="posts" v-on:searchquery="update" v-on:sq="update" v-on:postshuffled="update"></post-item>
</div>
<script type="module">
import AppHeader from './components/app-header.js';
import PostItem from './components/post-item.js';
const app = new Vue({
el: '#app',
data: {
name: 'Vue App',
logo: {
class: 'vue-logo',
src: 'https://vuejs.org/images/logo.png',
},
components: {
AppHeader,
PostItem,
},
posts: [
{id: 1, title: 'Test', published: true},
{id: 2, title: 'New post', published: true},
{id: 3, title: 'Added', published: true},
{id: 4, title: 'Another post', published: true},
{id: 5, title: 'In the future', published: false},
{id: 6, title: 'Last post', published: true},
],
},
created() {
console.log('Created');
},
mounted() {
console.log('Mounted');
},
methods: {
update() {
console.log('updated');
}
},
});
</script>
</body>
</html>
You say:
I have a child component "app-buttons", where i have an input field, i
want to listen to, so i can filter a list based on the input value.
You have:
<div id="app">
<app-header :logo="logo" :name="name"></app-header>
<post-item :posts="posts" v-on:searchquery="update" v-on:sq="update" v-on:postshuffled="update"></post-item>
</div>
That says you expect post-item to emit a searchquery event. It does not.
Within post-item, you have:
<app-buttons :posts="posts"></app-buttons>
So you expect app-buttons to emit an event and post-item to implicitly bubble it up, but Vue events do not bubble. If you want that behavior, you will need to have post-item handle the event:
<app-buttons :posts="posts" v-on:searchquery="$emit('searchquery', $event)"></app-buttons>
Okay so I've changed the markup, and it works. But would this be the best way to do it? :)
And thanks to Roy J for helping out.
import Event from './vue-event.js';
import Buttons from './app-buttons.js';
export default Vue.component('post-item', {
template: `
<section class="posts flex" v-if="posts">
<app-buttons :posts="posts" v-on:searchquery="update($event)"></app-buttons>
<transition-group name="posts" tag="section" class="posts flex">
<article class="postitem" :class="{ 'danger': !post.published }" v-for="post in filteredItems" :key="post.id">
<p v-if="post.published">Post is published</p>
<p v-else>Post is <em><strong>not</strong></em> published</p>
<h4>{{ post.title }}</h4>
<button type="submit" #click="post.published = !post.published">Change status</button>
</article>
</transition-group>
</section>
`,
data() {
return {
search: '',
};
},
components: {
Buttons,
},
props: {
posts: Array,
},
created() {
console.log('%c Post items', 'font-weight: bold;color:blue;', 'created');
},
computed: {
filteredItems(search) {
return this.posts.filter(post => {
return post.title.toLowerCase().indexOf(this.search.toLowerCase()) > -1
});
}
},
methods: {
update(event) {
this.search = event;
}
}
});
// Child component
import Event from './vue-event.js';
export default Vue.component('app-buttons', {
template: `
<div class="postswrapper flex flex--center flex--column">
<section :class="className" class="flex flex--center">
<button type="button" #click="showUnpublished">Unpublish</button>
<button type="button" #click="shufflePosts">Shuffle</button>
</section>
<section :class="className">
<input type="text" v-model="search" v-on:input="updateValue" placeholder="Search..." />
</section>
</div>
`,
data() {
return {
className: 'buttons',
search: '',
}
},
props: {
posts: Array,
},
created() {
console.log('%c Buttons', 'font-weight: bold;color:blue;', 'created');
},
methods: {
updateValue() {
this.$emit('searchquery', this.search);
},
showUnpublished() {
this.posts.forEach(item => {
item.published = true;
})
},
shufflePosts() {
for (var i = this.posts.length - 1; i >= 0; i--) {
let random = Math.floor(Math.random() * i);
let temp = this.posts[i];
Vue.set(this.posts, i, this.posts[random]);
Vue.set(this.posts, random, temp);
}
},
}
});
Index same as before, just without alle the events on the components.

Vue.js - Element UI - Nested dialog won't open from dropdown menu

I am new to Vue.js and ElementUI and having issues opening dialog from dropdown menu.
I am using Vue 2.5.2 and ElementUI: 2.3.4
I tried to follow the solution from
Vue.js - Element UI - Nested dialog won't open - v-if v-show but no luck.
Problem:
Dialog not showing up after clicking the dropdown menu item.
Thanks!
console.clear()
let popupData;
Vue.component('popup', {
name: "popup",
template: '#popup',
props : ['showDialog'],
data(){
return {
show: this.showDialog,
data: "Hello"
}
},
watch: {
showDialog: function(n,o){
this.show = this.showDialog;
}
},
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
}
},
created:function (){
},
});
var vm = new Vue({
el: '#app',
data: {
showDialog: false,
},
methods: {
}
});
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<div id="app">
<el-dropdown>
<span class="el-dropdown-link">
Dropdown List<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item #click.native="showDialog = true">Show Component PopUp
<popup :show-dialog.sync="showDialog"></popup>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<template id="popup">
<el-dialog :visible.sync="show" #visible-change="updateShowDialog" >{{data}}</el-dialog>
</template>
From de element.io docs, the el-dropdown-item do not have a #click event, you must use command-event in dropdown: http://element.eleme.io/#/en-US/component/dropdown#command-event
<div id="app">
<el-dropdown #command="onCommandDropdown">
<span class="el-dropdown-link">
Dropdown List<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="show-popup">
Show Component PopUp
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<popup :show-dialog.sync="showDialog"></popup>
</div>
<script type="x-template" id="popup">
<el-dialog title="Modal Title" :visible="showDialog" #close="onClose">
<span>{{ data }}</span>
</el-dialog>
</script>
The js part:
Vue.component('popup', {
template: '#popup',
props: ['showDialog'],
data() {
return {
show: this.showDialog,
data: "Hello modal!"
}
},
watch: {
showDialog(newValue) {
this.show = newValue;
}
},
methods: {
onClose() {
this.show = false;
this.$emit('update:showDialog', false);
}
}
});
new Vue({
el: '#app',
data: {
showDialog: false
},
methods: {
onCommandDropdown(command) {
if (command === 'show-popup') {
this.showDialog = true;
}
}
}
});
See working fiddle: https://jsfiddle.net/rafaph/pefwgL7y/2/

Laravel Vue.js fragment component

I have watched Jeffory's series on Vue.js and I'm practicing writing my own components using the vueify and browserify with gulp. Even after following along with the video I can't manage to get it to render properly. I keep getting this error.
TRY NUMBER ONE
Error:
Attribute "list" is ignored on component <alert> because the component is a fragment instance:
The view:
<div id = "app" class = "container">
<alert :list = "tasks"></alert>
</div>
The Componet:
<template>
<div>
<h1>My tasks
<span v-show = "remaining"> ( #{{ remaining }} )</span>
</h1>
<ul>
<li :class = "{ 'completed': task.completed }"
v-for = "task in list"
#click="task.completed = ! task.completed"
>
#{{ task.body }}
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['list'],
computed: {
remaining: function() {
return this.list.filter(this.inProgress).length;
}
},
methods: {
isCompleted: function(task) {
return task.completed;
},
inProgress: function(task) {
return ! this.isCompleted(task);
}
}
}
new Vue({
el: '#demo',
data: {
tasks: [
{ body: 'go to the store', completed: false },
{ body: 'go to the bank', completed: false },
{ body: 'go to the doctor', completed: true }
]
},
methods: {
toggleCompletedFor: function(task) {
task.completed = ! task.completed;
}
}
});
</script>
It gives me a link to read the Fragement Instance section in the documentation. What I understood was that if the template is composed of more than one top level element the component will be fragmented. So I took everything out of the template execpt the actual li tags. With this I still get the same error. What am missing?
Edited Template:
<li :class = "{ 'completed': task.completed }"
v-for = "task in list"
#click="task.completed = ! task.completed"
>
#{{ task.body }}
</li>
TRY NUMBER TWO
Same error
View
<div id ="app">
<alert>
<strong>Success!</strong> Your shit has been uploaded!
</alert>
<alert type = "success">
<strong>Success!</strong> Your shit has been uploaded!
</alert>
<alert type = "error">
<strong>Success!</strong> Your shit has been uploaded!
</alert>
</div>
Main.js
var Vue = require('vue');
import Alert from './componets/Alert.vue';
new Vue({
el: '#app',
components: { Alert },
ready: function() {
alert('Ready to go!');
}
});
Alert.Vue
<template>
<div>
<div :class ="alertClasses" v-show = "show">
<slot></slot>
<span class = "Alert_close" #click="show = false">X</span>
</div>
</div>
</template>
<script>
export default {
props: ['type'],
data: function() {
return {
show: true
};
},
computed: {
alertClasses: function () {
var type = this.type;
return{
"Alert": true,
"Alert--Success": type == "success",
"Alert--Error": type == "error"
}
}
}
}
</script>
Fresh re-install of the most curruent versions of node,gulp and vueify turned out to be the solution.

Categories

Resources