How to get the element using ref in vuejs for shepperd - javascript

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

Related

Setting preselected value as object via v-model in child component made of DevExtreme DxSelectBox not working in Vue3 composition API

I have made a child component - Dropdown based on Devextreme DxSelectBox.
I set in parent component v-model as attribute and forward to it an variable as ref, which is set to initial selected valueconst item = ref({ value: "option 1" }), but the DxSelectBox is empty when loading the project.
Child component - Dropdown is emitting the right selected option, but unfortunately the initial selected value is not set.
Code for Parent App.vue component is:
<template>
<template>
<Dropdown :items="items" v-model:option="item" />
<div class="emitting">Emitting: {{ item }}</div>
</template>
<script>
import { ref } from "vue";
import Dropdown from "./components/Dropdown.vue";
export default {
name: "App",
components: {
Dropdown: Dropdown,
},
setup(props, context) {
const emitValue = (e) => {
context.emit("update:option", e.value);
};
const items = ref([
{
value: "option 1",
},
{
value: "option 2",
},
{
value: "option 3",
},
]);
const item = ref({
value: "option 1",
});
return {
items,
item,
emitValue,
};
},
};
</script>
<style>
#import "https://cdn3.devexpress.com/jslib/18.2.8/css/dx.light.css";
#import "https://cdn3.devexpress.com/jslib/18.2.8/css/dx.common.css";
</style>
and code for Dropdown.vue component is:
<template>
<template>
<div class="dx-field">
<DxSelectBox
:data-source="items"
:value="option"
#valueChanged="emitValue"
:width="width"
:height="height"
:drop-down-options="{ width: width }"
display-expr="value"
:disabled="disabled"
:read-only="readOnly"
:hint="hint"
:placeholder="placeholder"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, watch, ref } from "vue";
import DxSelectBox from "devextreme-vue/select-box";
export default defineComponent({
name: "DpmDropdown",
components: {
DxSelectBox,
},
emits: ["update:option"],
props: {
label: { type: String, default: "" },
showLabel: { type: Boolean, default: true },
headingTooltip: { type: String, default: "" },
items: { type: Array, default: () => [] },
option: { type: Object, default: () => ({}) },
width: { type: [Number, String], default: "100%" },
height: { type: String, default: "40px" },
icon: { type: String, default: "" },
hint: { type: String, default: "" },
disabled: { type: Boolean, default: false },
readOnly: { type: Boolean, default: false },
placeholder: { type: String, default: "" },
},
setup(props, context) {
const emitValue = (e: any) => {
context.emit("update:option", e.value);
};
return {
emitValue,
};
},
});
</script>
The link for sandbox project is here.

how to create guide tour component using shepperd in vue

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.

PowerBI Embedded vertical scrollbar not visible

I have embedded a PowerBI report. This is the Javascript with the page setup.
For some reason I don't get a vertical scrollbar in my report. When I open it in the workspace online the scrollbars work just fine. I already tried switching between different 'View' options in PowerBi but that does not seem to make a difference.
<H2>PowerBI</H2>
<p><i>User: {{username}} | AccessLevel: {{access_level}}</i></p>
<div id="reportContainer" style="height: 80vh;"></div>
<script type="text/javascript">
window.onload = function () {
// Get models. models contains enums that can be used.
var models = window['powerbi-client'].models;
var embedConfiguration = {
type: 'report',
id: '{{txtembedreportid}}',
embedUrl: '{{txtreportembed}}',
tokenType: models.TokenType.Embed,
accessToken: '{{txtaccesstoken}}',
settings: {
layoutType: models.LayoutType.Custom,
customLayout: {
pageSize: {
type: models.PageSizeType.Widescreen,
}
},
panes:{
bookmarks: {
visible: false
},
fields: {
expanded: false
},
filters: {
expanded: false,
visible: false
},
pageNavigation: {
visible: true
},
selection: {
visible: true
},
syncSlicers: {
visible: true
},
visualizations: {
expanded: false
}
}
}
};
var $reportContainer = $('#reportContainer');
var report = powerbi.embed($reportContainer.get(0), embedConfiguration);
report.fullscreen();
}
</script>
Try adding the "displayOption" property with value "FitToWidth" in customLayout object, as this option will try to fit the report as per the total available size for the page and will introduce a scrollbar for the remaining part if necessary.
Also change "Widescreen" to "Custom" in pageSizeType object
After all changes, your embedConfiguration will become as following.
var embedConfiguration = {
type: 'report',
id: '{{txtembedreportid}}',
embedUrl: '{{txtreportembed}}',
tokenType: models.TokenType.Embed,
accessToken: '{{txtaccesstoken}}',
settings: {
layoutType: models.LayoutType.Custom,
customLayout: {
displayOption: models.DisplayOption.FitToWidth, // Add "FitToWidth"
pageSize: {
type: models.PageSizeType.Custom, // Change to "Custom"
}
},
panes:{
bookmarks: {
visible: false
},
fields: {
expanded: false
},
filters: {
expanded: false,
visible: false
},
pageNavigation: {
visible: true
},
selection: {
visible: true
},
syncSlicers: {
visible: true
},
visualizations: {
expanded: false
}
}
}
};
Refer docs: https://learn.microsoft.com/en-us/javascript/api/overview/powerbi/custom-layout

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