how to create guide tour component using shepperd in vue - javascript

I am trying to make a component using shepperd library for my guide tour in vuejs. Here is the library: https://shepherdjs.dev/ So I created a component for it and I want to use it in my parent component:
<template>
<div></div>
</template>
<script>
export default {
name: 'Example',
props: {
element: {
type: Object,
required: true,
},
text: {
type: String,
required: true,
},
},
mounted() {
this.$nextTick(() => {
const tour = this.$shepherd({
defaultStepOptions: {
cancelIcon: {
enabled: true,
},
scrollTo: { behavior: 'smooth', block: 'center' },
},
});
tour.addStep({
attachTo: { element: this.element, on: 'top' },
text: this.text,
buttons: [
{
action() {
return this.back();
},
classes: 'shepherd-button-secondary',
text: 'Back',
},
{
action() {
return this.next();
},
text: 'Next',
},
],
});
tour.start();
});
},
};
Here is my parent component:
So in my parent component I want to add two times this component into my parent component. But I dont know how to connect with them. So when I click next, I want to trigger it and kill it then I want to trigger the next one.
Any help will be appreciated.

Related

How to get the element using ref in vuejs for shepperd

I am using shepperd for my guide tour in vue. Here is the documentation: https://shepherdjs.dev/
I created component for it:
<template>
<div></div>
</template>
<script>
export default {
name: 'Guide',
props: {
element: {
type: Object,
required: true,
},
title: {
type: String,
},
text: {
type: String,
required: true,
},
position: {
type: String,
required: true,
},
},
mounted() {
this.$nextTick(() => {
const tour = this.$shepherd({
defaultStepOptions: {
cancelIcon: {
enabled: true,
},
classes: 'class-1 class-2',
scrollTo: { behavior: 'smooth', block: 'center' },
},
});
tour.addStep({
attachTo: { element: this.element, on: this.position },
title: this.title,
text: this.text,
buttons: [
{
action() {
return this.back();
},
classes: 'shepherd-button-secondary',
text: 'Back',
},
{
action() {
return this.next();
},
text: 'Next',
},
],
id: 'creating',
});
tour.start();
});
},
};
</script>
<style lang="scss">
#import '~shepherd.js/dist/css/shepherd.css';
</style>
Whats wrong in my code is, in parent component I cannot get the element. I am trying to do it with ref in vue. So I want to get the element of this button:
<button ref="button">
Click
</button>
beforeMount() {
this.element = this.$refs.button;
},
But it is getting null all the time. How can I get the element of the button?
https://v2.vuejs.org/v2/api/#mounted
If you want to access the DOM element, you should put the code in mounted hooks.
mounted() {
this.element = this.$refs.button;
}
Note: mounted does not guarantee that all child components have also been mounted. If you want to wait until the entire view has been rendered, you can use vm.$nextTick inside of mounted

How to make quill text editor readonly when you click the button in vue

I have a quill text editor and as a default I want to set it as readonly and when the button is clicked, this should be true/false. So here is my component:
<template>
<div ref="editor"></div>
</template>
<script>
import Quill from 'quill';
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.bubble.css';
import 'quill/dist/quill.snow.css';
export default {
props: {
disabled: { type: Boolean, default: false },
value: {
type: String,
default: ''
}
},
data() {
return {
editor: null,
};
},
mounted() {
this.editor = new Quill(this.$refs.editor, {
modules: {
toolbar: [
[{ header: [1, 2, 3, 4, false] }],
['bold', 'italic', 'underline']
]
},
readOnly: this.disabled,
theme: 'snow',
formats: ['bold', 'underline', 'header', 'italic']
});
this.editor.root.innerHTML = this.value;
this.editor.on('text-change', () => this.update());
},
methods: {
update() {
this.$emit('input', this.editor.getText() ? this.editor.root.innerHTML : '');
}
}
}
</script>
I am sending this value as a prop to my component which is 'disabled' and when I click the button true becomes false and when I click again visa versa. So there is nothing wrong with that. Also when I do readOnly: true, or readOnly: false it works but with this way, readOnly: this.disabled it doesnt work.
I am using vue but I created a component based on quilljs directly.
The documentation I use is this one: https://quilljs.com/docs/configuration/#readonly

How organize data in vue component to one structure?

Maybe complex question but i want try...
In vue component, (which is modal window for administration slider with few slide types like video, image...) i want have whole modal structure in json object. So i was created baseTypeStructure (inputs common for all slide types), avalibleTypeStructure (inputs for specific slide type) and activeTypeStructure (complete inputs structure after pick some slide type)
But i am in vue (and js) pretty new and i have complications with reactivity...
So if you pick some slide type i bind activeTypeStructure from baseTypeStructure and specific type from avalibleTypeStructure. (by type key)
But i have problems with reactivity... On one side i need reactive behavior because there are input values... on other side there are informations about visibility... and if i change in activeTypeStructure visibility to true, that value is changed in avalibleTypeStructure if i make clone (without reactivity), every change slide type i do reset all values...
Can anybody help with this? Resolve it this way (as one json format for whole modal). Maybe absolute bad thinking?
data() {
return {
detailModalIsOpened: false,
loading: false,
action: '',
title: 'default',
baseTypeStructure: {
inputs: {
name: {
visible: true,
value: ''
},
type: {
visible: true,
value: '',
selected: [],
slideTypes: []
},
duration: {
visible: true,
value: 5000
},
image: {
inputs: {
imageFile: {
visible: false,
value: ''
}
}
},
video: {
inputs: {
videoFile: {
visible: false,
value: ''
}
}
}
}
},
avalibleTypeStructures: {
none: {},
image: {
// name: 'image',
inputs: {
imageFile: {
visible: true,
value: ''
}
}
},
video: {
// name: 'video',
inputs: {
videoFile: {
visible: true,
value: ''
}
}
}
},
activeTypeStructure: {},
}
}

How do I create reusuable external functions in Vue?

As my project is growing, I've noticed a lot of repetitions. I'm starting with the navigations buttons, as they can appear in multiple places (side menu, navbar).
I'd like to centralize them and let the component import them as needed. So I've tried creating this file to hold all my menu items:
// menuItems.js
export default {
data() {
return {
items: [
{ title: 'Profile', icon: 'mdi-account-circle', reqAuth: true, hideOnAuth: false},
{ title: 'Dashboard', icon: 'mdi-view-dashboard', reqAuth: true, hideOnAuth: false },
{ title: 'Settings', icon: 'mdi-cog', reqAuth: true, hideOnAuth: false },
{ title: 'Signup', icon: 'mdi-account-circle-outline', reqAuth: false, hideOnAuth: true},
{ title: 'Login', icon: 'mdi-login', reqAuth: false, hideOnAuth: true },
{ title: 'Logout', icon: 'mdi-logout', reqAuth: true, hideOnAuth: false},
]
}
},
methods: {
menuItems: function(authenticated) {
if (!authenticated) {
// Gets items that do and don't require Auth, except for ones that should hide on Auth
return this.items.filter(o => o.reqAuth || !o.reqAuth && !o.hideOnAuth)
}
// Gets items that don't require Auth
return this.items.filter(o => !o.reqAuth)
}
}
}
Buttons can require authentication to be visible, and they can also be hidden once authenticated (eg. The login button).
Now lets assume I have a vue component for my nav bar, how do I import in the method that returns the filtered items?
// NavBarComponent.vue
<template>
<div>
<v-btn v-for="(item, i) in menuItems(authenticated)">
{{ item.title }}
</v-btn>
</div>
</template>
<script>
export default {
name: "NavBarComponent",
data() {
return {
authenticated: true,
}
},
methods: {
}
}
</script>
In this case, how do i make menuItems in the component reference the external file that will do the work of filtering?
You can create a mixin file and put your methods in that mixin and apply the mixin your component.
https://v2.vuejs.org/v2/guide/mixins.html
Your mixin would look like this:
// /mixins/menuItemsMixin.js
const menuItemsMixin= {
data() {
return {
items: [
{ title: 'Profile', icon: 'mdi-account-circle', reqAuth: true, hideOnAuth: false},
{ title: 'Dashboard', icon: 'mdi-view-dashboard', reqAuth: true, hideOnAuth: false },
{ title: 'Settings', icon: 'mdi-cog', reqAuth: true, hideOnAuth: false },
{ title: 'Signup', icon: 'mdi-account-circle-outline', reqAuth: false, hideOnAuth: true},
{ title: 'Login', icon: 'mdi-login', reqAuth: false, hideOnAuth: true },
{ title: 'Logout', icon: 'mdi-logout', reqAuth: true, hideOnAuth: false},
]
}
},
methods: {
menuItems: function(authenticated) {
if (!authenticated) {
// Gets items that do and don't require Auth, except for ones that should hide on Auth
return this.items.filter(o => o.reqAuth || !o.reqAuth && !o.hideOnAuth)
}
// Gets items that don't require Auth
return this.items.filter(o => !o.reqAuth)
}
}
};
export default menuItemsMixin
And in your component:
// NavBarComponent.vue
<script>
import menuItemsMixin from './mixins/menuItemsMixin.js'
export default {
mixins:[menuItemsMixin]
}
</script>
You can import this mixin in multiple components and you can also add more mixins in this component where the unique methods will be added.
I ended up creating a javascript file:
// views.js
export const views = [
{title: 'Dashboard'},
{title: 'Profile'},
{title: 'Login/Signup'},
]
then in my navbar component I imported it like so:
import {mapGetters} from "vuex";
import {views} from "../../common/views";
export default {
data: () => ({
items: views
}),
computed: {
...mapGetters(['isAuthenticated',])
menuItems: function (){
if (this.isAuthenticated) {
// do this
} else {
// do this
}
},
}
}
Then I did the same for the filtering function, but I could also just re-code it as needed if required in each component. I determined authentication state using Vuex's store, and retrieve it with mapgetters.
<componentA v-if='isAuthenticated'>
<navItem v-for='item in menuItems'>
</componentA>

Objects gets empty in VueJS Component

I'm trying to build an application in VueJS where I'm having a component something like this:
<template>
<div>
<base-subheader title="Members" icon="la-home" heading="Member Details" description="Home"></base-subheader>
<div class="m-content">
<div class="row">
<div class="col-xl-6">
<nits-form-portlet
title="Add Member/User"
info="Fill the details below to add user, fields which are mandatory are label with * (star) mark."
headIcon="flaticon-multimedia"
headerLine
apiUrl="api/user"
backUrl="members__home"
action="create"
:formElements="form_elements"
>
</nits-form-portlet>
</div>
<div class="col-xl-6">
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "member-add",
data() {
return {
form_elements: [
{
field_name: 'first_name',
config_elements: {
label: 'First Name *',
type: 'text',
inputStyle: 'pill',
placeholder: 'Enter first name of user',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-exclamation'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'last_name',
config_elements: {
label: 'Last Name',
type: 'text',
inputStyle: 'pill',
placeholder: 'Enter last name of user',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-exclamation'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'email',
config_elements: {
label: 'Email *',
type: 'email',
inputStyle: 'pill',
placeholder: 'Enter email of user',
formControl: true,
addonType: 'left',
addon: {leftAddon: '#'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'password',
config_elements: {
label: 'Password *',
type: 'password',
inputStyle: 'pill',
placeholder: 'Enter password',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-expeditedssl'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'confirm_password',
config_elements: {
label: 'Confirm Password *',
type: 'password',
inputStyle: 'pill',
placeholder: 'Confirm password should match',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-expeditedssl'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'role',
config_elements: {
label: 'Select Role *',
inputStyle: 'pill',
options: [
{option: 'Select one'},
{value: '1', option: 'Admin'},
{value: '2', option: 'Subscriber'},
{value: '3', option: 'Analyst'},
{value: '4', option: 'Guest'}
],
addonType: 'left-icon',
addon: {leftAddon: 'la-user-secret'},
},
value: '',
nitsFormType: 'nits-select'
},
{
field_name: 'profile_pic',
config_elements: {
label: 'Profile pic',
},
value: '',
nitsFormType: 'nits-file-input'
}
],
}
}
}
</script>
<style scoped>
</style>
Whenever I try to pass data to my component config_elements which holds an object of all attributes being passed to child component gets lost, if I move from other component to nits-form-portlet> it renders the child components appropriately, but in my Vue-debug tool it shows empty:
But the components are rendered properly:
And same happens to the props inside the component:
But in case of any event or refreshing the page it shows:
Since all the attributes are gone, if I try to configure my config_elements object data, I get the attributes but within a fraction of seconds it again goes to empty. But attributes gets passed and it displays the fields:
I am using render function to render these components so for nits-form-portlet component I have:
return createElement('div', { class: this.getClasses() }, [
createElement('div', { class: 'm-portlet__head' }, [
createElement('div', { class: 'm-portlet__head-caption' }, [element])
]),
createElement('form', { class: 'm-form m-form--fit m-form--label-align-right' }, [
createElement('div', { class: 'm-portlet__body' }, [
createElement('div', { class: 'form-group m-form__group m--margin-top-10' }, [infoElement]),
this.computedFormElements.map(a => {
if(this.error[a.field_name]) {
a.config_elements.error = this.error[a.field_name][0];
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
}
else
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
})
]),
footerElement
])
])
And I think factors affecting the template is this map statement:
this.computedFormElements.map(a => {
if(this.error[a.field_name]) {
a.config_elements.error = this.error[a.field_name][0];
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
}
else
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
})
but still I'm sharing my whole code of render function, Link to code # github hoping someone can help me with this strange situation.
It's a bit hard to tell for me without running the code, but I've noticed something that may be related.
I see that you're overwriting the formElements prop here:
a.config_elements.error = this.error[a.field_name][0];
Vue usually gives you a warning that you can't do that, but maybe because it's abstracted through the computed it's not doing so.
If you want to extend the data for use in the child component, then it's best to do copy of the object (preferably a deep copy using computed, which will allow you to have it dynamically updated)
because config_elements is an object, when you add error variable you are likely triggering an observer, that may be clearing the data in the parent.
Anyway, it's just a guess, but something you may want to resolve anyway.

Categories

Resources