Creating a custom component Vue3js - vue.js

I downloaded the DataTable component vue3-easy-data-table. Then I created a custom component where I define the theme for the table.
Since the easy-data-table uses slots to operate on the table items, how can I redirect my template tags to the table instead of my custom component?
<script>
export default {
setup() {
const themeColor = getComputedStyle(document.documentElement).getPropertyValue('--primary');
return { themeColor }
}
}
</script>
<template>
<DataTable table-class-name="table-theme" :theme-color="themeColor" alternating>
<template #expand="item">
<slot name="expand" v-bind="item"></slot>
</template>
</DataTable>
</template>
<style lang="scss" scoped>
.table-theme {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
This is my custom component. At the moment I manually created a copy template for the expand function and it works alright, but not only it looks ugly and redundant, it also won't work to access specific items.
Do you have a solution?

I found a solution. The slots object that you get in your setup function will contain the "parent requested slot" even if they are not defined in the template.
Therefore you can easily pass the slots names to the template, iterate through them and automatically generate the needed slots.
<script>
export default {
setup(props, { slots }) {
const themeColor = getComputedStyle(document.documentElement).getPropertyValue('--primary');
const slotNames = Object.keys(slots)
return { themeColor, slotNames }
}
}
</script>
<template>
<DataTable table-class-name="table-theme" :theme-color="themeColor" alternating>
<template v-slot:[slot]="model" v-for="slot in slotNames">
<slot :name="slot" v-bind="model"></slot>
</template>
</DataTable>
</template>

You don't really need to wrap the table with a custom component.
But unfortunately the vue3-easy-data-table does not update with changing the themeColor.
So, the quick and dirty solution is to force the table recreate, by destroying it using v-if.
Here is the playground (check it in the full page)
const App = {
components: {
EasyDataTable: window['vue3-easy-data-table'],
},
data () {
return {
themeColor: "#f48225",
headers:[
{ text: "Name", value: "name" },
{ text: "Age", value: "age", sortable: true }
],
items: [
{ "name": "Curry", "height": 178, "weight": 77, "age": 20 },
{ "name": "James", "height": 180, "weight": 75, "age": 21 },
{ "name": "Jordan", "height": 181, "weight": 73, "age": 22 }
],
}
},
methods: {
resetTable() {
this.themeColor = null;
}
}
};
Vue.createApp(App).mount('#app');
<div id="app">
Theme Color: {{themeColor}}<br /><br />
<input type="radio" v-model="themeColor" value="#f48225" #input="resetTable"/> #f48225
<input type="radio" v-model="themeColor" value="#123456" #input="resetTable" /> #123456 <br /><br />
<easy-data-table v-if="themeColor != null"
:headers="headers"
:items="items"
:theme-color="themeColor"
buttons-pagination
alternating
/>
</div>
<link href="https://unpkg.com/vue3-easy-data-table/dist/style.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#3.2.1/dist/vue.global.js"></script>
<script src="https://unpkg.com/vue3-easy-data-table"></script>

Related

Vuejs Role based login with different authorization

I am new to vue js and I am working on a simple project about role based login and listing report. In my design, there are two type of user: HAuser and supplier. How should I redirect different user to their corresponding page and authorize them with different access right?
login component:
<template>
<div class="auth-inner">
<h3>{{ userType }} {{ $t('Login') }}</h3>
<form v-on:submit.prevent="onSubmit">
<div class="form-group">
<label>{{ $t('username') }}</label>
<input v-model="loginForm.username" type="username" class="form-control" :placeholder="$t('username')" />
</div>
<div class="form-group">
<label>{{ $t('password') }}</label>
<input v-model="loginForm.password" type="password" class="form-control" :placeholder="$t('password')" />
</div>
<div class="d-grid gap-2">
<button class="btn btn-primary" type="submit">{{ $t('Login') }}</button>
<vue-recaptcha
ref="recaptcha"
#verify="onCaptchaVerified"
#expired="onCaptchaExpired"
sitekey="6LfCmnEhAAAAAIsEHJx8QXbUHIAvIsuuwQW4JGj_"
class="mt-3" />
</div>
<div v-if="showErrorMessage" class="recaptcha-error-message"><strong>{{ $t('recaptcha-error-message') }}</strong></div>
</form>
</div>
</template>
<script>
import { VueRecaptcha } from 'vue-recaptcha';
export default {
name: 'login',
props: ['userType'],
components: { VueRecaptcha },
data(){
return{
showErrorMessage: false,
recaptchaToken: '',
status: '',
sucessfulServerResponse: '',
serverError: '',
recaptchaVerified: false,
submitted: false,
loading: false,
returnUrl: '',
error: '',
loginForm: {
username: '',
password: '',
}
}
},
methods: {
onCaptchaVerified: function (recaptchaToken) {
this.showErrorMessage = false;
this.loginForm.recaptchaVerified = true;
this.recaptchaToken = recaptchaToken;
},
onCaptchaExpired: function () {
this.loginForm.recaptchaVerified = false;
this.$refs.recaptcha.reset();
if (!this.loginForm.recaptchaVerified) {
this.showErrorMessage = true;
return ; // prevent form from submitting
}
},
onSubmit () {
if (!this.loginForm.recaptchaVerified) {
this.showErrorMessage = true;
return ; // prevent form from submitting
}
}
}
}
</script>
<style>
.recaptcha-error-message {
color: red;
}
.auth-inner {
width: 450px;
margin:auto;
background: #ffffff;
box-shadow: 0px 14px 80px rgba(34, 35, 58,0.2);
padding: 40px 55px 45px 55px;
border-radius: 15px;
transition: all .3s;
}
</style>
different user view:
HAuser:
<template>
<Login userType='HAuser'/>
</template>
<script>
import Login from '#/components/Login.vue';
export default {
components: {
Login
}
}
</script>
<style>
</style>
Supplier view:
<template>
<Login userType='Supplier' />
</template>
<script>
import Login from '#/components/Login.vue';
export default {
components: {
Login
},
}
</script>
<style>
</style>
report const
{
"reports": [
{ "type": "Supplier", "name": "Summary of Users Last Access Time" },
{ "type": "Supplier", "name": "Capital Budget Report - Grand Summary" },
{ "type": "HAuser", "name": "(Workflow) Report on Site Directions/Memo Status" },
{ "type": "HAuser", "name": "Yearly Budget Forecast Working Sheet by Project" },
{ "type": "HAuser", "name": "Report on SWD's Lotteries Fund Project Under Block" }
]
}
You would want to look into router-guards. You can use them to do some checks before routing further and if needed route the user elsewhere. So for example:
When your user logs in at that point you know the role. Then you route to the default page, but if you detect in the guard that the role should go to another page you redirect to that page instead of the default page.
https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
There are of course other options like creating a computed property on the login page that get the role from your pinia (vuex) store.. put a watcher on that computed property also in the login page and when it changes route to the correct page.

nuxtjs add and remove class on click on elements

I am new in vue and nuxt and here is my code I need to update
<template>
<div class="dashContent">
<div class="dashContent_item dashContent_item--active">
<p class="dashContent_text">123</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">456</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">789</p>
</div>
</div>
</template>
<style lang="scss">
.dashContent {
&_item {
display: flex;
align-items: center;
}
&_text {
color: #8e8f93;
font-size: 14px;
}
}
.dashContent_item--active {
.dashContent_text{
color:#fff;
font-size: 14px;
}
}
</style>
I tried something like this:
<div #click="onClick">
methods: {
onClick () {
document.body.classList.toggle('dashContent_item--active');
},
},
but it changed all elements and I need style change only on element I clicked and remove when click on another
also this code add active class to body not to element I clicked
This is how to get a togglable list of fruits, with a specific class tied to each one of them.
<template>
<section>
<div v-for="(fruit, index) in fruits" :key="fruit.id" #click="toggleEat(index)">
<span :class="{ 'was-eaten': fruit.eaten }">{{ fruit.name }}</span>
</div>
</section>
</template>
<script>
export default {
name: 'ToggleFruits',
data() {
return {
fruits: [
{ id: 1, name: 'banana', eaten: false },
{ id: 2, name: 'apple', eaten: true },
{ id: 3, name: 'watermelon', eaten: false },
],
}
},
methods: {
toggleEat(clickedFruitIndex) {
this.fruits = this.fruits.map((fruit) => ({
...fruit,
eaten: false,
}))
return this.$set(this.fruits, clickedFruitIndex, {
...this.fruits[clickedFruitIndex],
eaten: true,
})
},
},
}
</script>
<style scoped>
.was-eaten {
color: hsl(24, 81.7%, 49.2%);
}
</style>
In Vue2, we need to use this.$set otherwise, the changed element in a specific position of the array will not be detected. More info available in the official documentation.

Google Maps showing grey box in Vue modal

I have a <b-modal> from VueBootstrap, inside of which I'm trying to render a <GmapMap> (https://www.npmjs.com/package/gmap-vue)
It's rendering a grey box inside the modal, but outside the modal it renders the map just fine.
All the searching I've done leads to the same solution which I'm finding in some places is google.maps.event.trigger(map, 'resize') which is not working. Apparently, it's no longer part of the API [Source: https://stackoverflow.com/questions/13059034/how-to-use-google-maps-event-triggermap-resize]
<template>
<div class="text-center">
<h1>{{ title }}</h1>
<div class="row d-flex justify-content-center">
<div class="col-md-8">
<GmapMap
ref="topMapRef"
class="gmap"
:center="{ lat: 42, lng: 42 }"
:zoom="7"
map-type-id="terrain"
/>
<b-table
bordered
dark
fixed
hover
show-empty
striped
:busy.sync="isBusy"
:items="items"
:fields="fields"
>
<template v-slot:cell(actions)="row">
<b-button
size="sm"
#click="info(row.item, row.index, $event.target)"
>
Map
</b-button>
</template>
</b-table>
<b-modal
:id="mapModal.id"
:title="mapModal.title"
#hide="resetInfoModal"
ok-only
>
<GmapMap
ref="modalMapRef"
class="gmap"
:center="{ lat: 42, lng: 42 }"
:zoom="7"
map-type-id="terrain"
/>
</b-modal>
</div>
</div>
</div>
</template>
<script>
// import axios from "axios";
import { gmapApi } from 'gmap-vue';
export default {
name: "RenderList",
props: {
title: String,
},
computed: {
google() {
return gmapApi();
},
},
updated() {
console.log(this.$refs.modalMapRef);
console.log(window.google.maps);
this.$refs.modalMapRef.$mapPromise.then((map) => {
map.setCenter(new window.google.maps.LatLng(54, -2));
map.setZoom(2);
window.google.maps.event.trigger(map, 'resize');
})
},
data: function () {
return {
items: [
{ id: 1, lat: 42, long: 42 },
{ id: 2, lat: 42, long: 42 },
{ id: 3, lat: 42, long: 42 },
],
isBusy: false,
fields: [
{
key: "id",
sortable: true,
class: "text-left",
},
{
key: "text",
sortable: true,
class: "text-left",
},
"lat",
"long",
{
key: "actions",
label: "Actions"
}
],
mapModal: {
id: "map-modal",
title: "",
item: ""
}
}
},
methods: {
// dataProvider() {
// this.isBusy = true;
// let promise = axios.get(process.env.VUE_APP_LIST_DATA_SERVICE);
// return promise.then((response) => {
// this.isBusy = false
// return response.data;
// }).catch(error => {
// this.isBusy = false;
// console.log(error);
// return [];
// })
// },
info(item, index, button) {
this.mapModal.title = `Label: ${item.id}`;
this.mapModal.item = item;
this.$root.$emit("bv::show::modal", this.mapModal.id, button);
},
resetInfoModal() {
this.mapModal.title = "";
this.mapModal.content = "";
},
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1 {
margin-bottom: 60px;
}
.gmap {
width: 100%;
height: 300px;
margin-bottom: 60px;
}
</style>
Does anyone know how to get the map to display properly in the modal?
Surely, I'm not the first to try this?
Had this problem, in my case it was solved by providing the following options to google maps:
mapOptions: {
center: { lat: 10.365365, lng: -66.96667 },
clickableIcons: false,
streetViewControl: false,
panControlOptions: false,
gestureHandling: 'cooperative',
mapTypeControl: false,
zoomControlOptions: {
style: 'SMALL'
},
zoom: 14
}
However you can probably make-do with just center and zoom.
Edit: Try using your own google maps components, follow this tutorial:
https://v2.vuejs.org/v2/cookbook/practical-use-of-scoped-slots.html#Base-Example
You can use the package described in the tutorial to load the map, dont be scared by the big red "deprecated" warning on the npm package page.
However for production, you should use the package referenced by the author, which is the one backed by google:
https://googlemaps.github.io/js-api-loader/index.html
The only big difference between the two:
The 'google' object is not returned by the non-deprecated loader, it is instead attached to the window. See my answer here for clarification:
'google' is not defined Using Google Maps JavaScript API Loader
Happy coding!

In vue.js how can I filter products by size, color, category & by selecting each of these categories in step wise slider

In vue.js how can I filter products by size, color, category & by selecting each of these categories in step-wise slider where I have to select each filter one by one step-wise & at last I need to display the products which pass through the filter.
Please share code examples and ask specific questions about where you are stuck. This question is very broad and is difficult to answer.
I'll try to provide an answer as best as I can.
You might have a ProductList component that may look something like this:
<template>
<!-- your template -->
</template>
<script>
import RangeSlider from "./RangeSlider";
export default {
name: "ProductList",
components: {
RangeSlider
},
data() {
return {
products: [],
filters: {
color: null,
size: null,
category: null
},
options: {
color: ["Red", "Yellow", "Green"],
size: ["Small", "Medium", "Large"],
category: ["Men", "Women", "Boy", "Girl"]
}
};
}
};
</script>
The RangeSlider can use the native <input type="range" /> control to build the functionality you want.
In this example, it takes a label and an array of options to build the control, and will emit a change event when the user changes the value of the control by sliding, which is captured in ProductList to set the filters. You can then use the filters object and make an API call to fetch your products.
<template>
<div class="range-slider">
<label>
<strong>{{ label }}</strong>
</label>
<div>
<input type="range" min="0" :max="max" step="1" #change="onInputChange" :value="rangeValue">
</div>
<div>{{ options[rangeValue] }}</div>
</div>
</template>
<script>
export default {
name: "RangeSlider",
props: ["label", "options"],
data() {
return {
rangeValue: 0
};
},
mounted() {
this.onChange(0);
},
methods: {
onInputChange(e) {
this.onChange(e.target.value);
},
onChange(rangeValue) {
this.rangeValue = rangeValue;
let selectedOption = this.options[this.rangeValue];
this.$emit("change", selectedOption);
}
},
computed: {
max() {
return this.options.length - 1;
}
}
};
</script>
<style>
.range-slider {
padding: 1rem;
border-bottom: 1px solid;
}
</style>
Here is the working example on CodeSandbox: https://codesandbox.io/s/rangesliderwithoptionsexample-6pe10

Delivery data from parent to child

After googling for hours and trying all king examples I still can't figure out how to call to a function that located in a child or pass any data to a child from the parent. I tried so many examples without success and i got really confused with the Vue, is it so complicated to pass a data to a child? Here is what I came up with from one of the examples but it does not work with me, maybe I do something wrong here? Thanks :(
parent.vue
<template>
export default {
name:'parent'
data:{
retrun:{
dataTochild:''
}
},
methods:{
callToChild:function(){
this.dataTochild = 'Hello Child'
}
}
}
</template>
<child :myprop="dataTochild" />
<input #click="this.callToChild" />
child.vue
<template>
export default {
name:'child',
props:['myprop'],
watch:{
myprop:function(){
alert('Hello Parent')
}
}
}
<template>
By the way, I am using Vue 2 version!
you have a spelling error on return (retrun), and there could be other errors on code you have omitted.
Working live demo
Here it is working on a live demo
Code
Parent
<template>
<div class="parent">
<child :myprop="dataTochild" />
<button #click="callToChild">Press me!</button>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
name: "Parent",
data() {
return {
dataTochild: "",
counter: 0
};
},
methods: {
callToChild: function() {
this.counter = this.counter + 1;
this.dataTochild = "Hello Child " + this.counter;
}
},
components: {
Child
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
button {
width: 200px;
height: 60px;
}
</style>
Child
<template>
<div class="child">
<p>{{ myprop }}</p>
</div>
</template>
<script>
export default {
name: "Child",
props: ["myprop"],
watch: {
myprop: function() {
console.log("Hello Parent");
alert("Hello Parent");
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
div.child {
width: 400px;
height: 100px;
margin: auto;
}
</style>