Vue bind multiple class with conditional - vue.js

I have the following div in my component:
<div :class="sidebarOpen ? 'translate-x-0 ease-out transition-medium' : '-translate-x-full ease-in transition-medium'" class="sidebar fixed z-30 inset-y-0 left-0 w-64 px-8 py-4 border-r overflow-auto lg:static lg:inset-auto lg:translate-x-0">
I want to add another class to this, which is a computed property:
computed: {
borderColor() {
return {
"bg-white": this.$apollo.loading,
[colorBorder[this.board?.color]]: true
}
},
}
Can anyone advise how I can do this please?

Try to use the array syntax :
<div :class="[sidebarOpen ?'...':'...',borderColor]"

Related

How can I use a computed property to change the color of a text pill depending on the word using vue composition api?

I have a pill shape that say's either Production or Development. I want the background color to be different depending on if it is production or development. I've done this before with option api, but I'm having trouble in composition api.
Computed Method:
const reportStatus = computed(function() {
if (dashboard.status === 'Production') {return 'status--production'}
return 'status--qa'
});
Styling:
.status--production {
background-color: blue;
color: white;
}
.status--qa {
background-color: green;
color: white;
}
Template code:
<div>
<span class="float-left text-center w-24 inline-block flex-shrink-0 rounded-full px-2 py-0.5 text-xs font-medium text-white inset-x-0 top-0" :class="reportStatus">
{{ dashboards.status }}</span>
</div>
Script Dashboard code:
const dashboard = [
{
report: "Ad Hoc",
description: "",
status: "Production"
},
Use computed properties for things like changing reactive variable output. For example, some different date format.
The best way to your issue is:
<template>
<span class="fload ..." :class="[ dashboard.status === 'Production' ? 'status-production' : 'status-qa']">{{ dashboard.status }}</span>
</template>
Make sure dashboard is a reactive object/value like ref() or reactive() second one fit best for objects. Objects are tricky to use every time you have to assign a full new object instead of just change a value in it.
Computed property:
<template>
<div>
<span class="float ..." :class="addClass">{{ dashboard.status }}</span>
<button #click="dashboard = { ...dashboard, status: 'lol' }">Change</button>
<button #click="dashboard = { ...dashboard, status: 'Production' }">Production</button>
</div>
</template>
<script setup>
const dashboard = ref({ status: 'Production' })
const addClass = computed(() => dashboard.value.status === 'Production' ? 'status-production' : 'status-qa')
</script>
If you use ref() and change dashboard like "dashboard.value.status = 'some value'" reactivity won't work same as "dashboard.status = 'some value'" in template. You will always need to assign a new object to trigger reactivity.
reactive() don't have that problem because all items inside of it are reactive.
When you have two class attributes, the second one gets ignored. Combine your two class attributes into one;
<div>
<span :class="`float-left text-center w-24 inline-block flex-shrink-0 rounded-full px-2 py-0.5 text-xs font-medium text-white inset-x-0 top-0 ${reportStatus}`">
{{ dashboards.status }}
</span>
</div>
Your code should work fine, Just for a demo purpose I am using just an object for dashbaords variable but you can change it to an array as per your requirement.
Here is the live demo (Please have a look and try to find the root cause of the issue you are facing by referring this) :
const { ref, computed, onMounted } = Vue;
let options = {
setup: function () {
let dashboards = ref({});
const reportStatus = computed(function() {
return (dashboards.value.status === 'Production') ? 'status--production' : 'status--qa'
});
onMounted(function() {
dashboards.value = {
report: "Ad Hoc",
description: "",
status: "Production"
}
});
return {
dashboards,
reportStatus
};
}
};
let appVue = Vue
.createApp(options)
.mount('#app');
.status--production {
background-color: blue;
color: white;
border: 1px solid black;
}
.status--qa {
background-color: green;
color: white;
border: 1px solid black;
}
<script src="https://unpkg.com/vue#3.0.0-beta.14/dist/vue.global.js"></script>
<div id="app">
<span :class="reportStatus">
{{ dashboards.status }}
</span>
</div>

I am converting my code snippet from pure html to Vue2 but facing some difficulty

This is pure html code with javascript.
filter.js
var group_filter=document.getElementById("group-filter");
var btn_show_filter=document.getElementById("icon-filter");
var btn_close_filter=document.getElementById("icon-close-filter");
function showfilter(){
group_filter.style.display='block';
btn_show_filter.style.display='none';
btn_close_filter.style.display='inline';
}
function closefilter(){
group_filter.style.display='none';
btn_show_filter.style.display='inline';
btn_close_filter.style.display='none';
}
And here is the code that I'm trying to process but it doesn't look right.
<div class="job-filter">
<h3>Filter <img id="icon-filter" #click="showfilter" :class="{ 'display-none': this.display }" src="../assets/recruit/angle-down-svgrepo-com.svg" alt=""> <img id="icon-close-filter" :class="{ 'display-inline': this.display }" #click="showfilter" src="../assets/recruit/close-svgrepo-com.svg" alt=""></h3>
<div class="radio-group" id="group-filter" :class="{ 'display-block': this.display}" >
</div>
</div>
Thank you everyone, and I look forward to your help.
You can simply achieve this by creating an object in the data method like this :
data() {
return {
display: {
group_filter: '',
btn_show_filter: '',
btn_close_filter: ''
}
}
}
And in methods object, you can assign the values :
methods: {
showfilter() {
this.display.group_filter = 'block';
this.display.btn_show_filter = 'none';
this.display.btn_close_filter = 'inline';
},
closefilter() {
this.display.group_filter = 'none';
this.display.btn_show_filter = 'inline';
this.display.btn_close_filter = 'none';
}
}
And then in the template, you can assign like this :
<div class="job-filter">
<h3>Filter <img id="icon-filter" #click="showfilter" :style="{ 'display': display.btn_show_filter }" src="../assets/recruit/angle-down-svgrepo-com.svg" alt=""> <img id="icon-close-filter" :style="{ 'display': display.btn_close_filter }" #click="closefilter" src="../assets/recruit/close-svgrepo-com.svg" alt=""></h3>
<div class="radio-group" id="group-filter" :style="{ 'display': display.group_filter }"></div>
</div>
I hope this is what you want to achieve.
I have create one demo on stackblitz.
link - https://vue-etrbkr.stackblitz.io
I have attached source code here.
<template>
<div id="app">
<div class="job-filter">
<h3>
Filter
<img
#click="toggleFilter"
class="filterIcon"
:src="
isFilterOpen
? 'https://cdn-icons-png.flaticon.com/512/61/61155.png'
: 'https://iaeste.org/assets/icons/arrow_dropdown-
8e096a444299e295fa03bc919f3cea625987792e13aaf64ec6b0be881a8f7c0a.svg'
"
width="20"
height="20"
/>
</h3>
<div class="radio-group" id="group-filter" v-if="isFilterOpen">
Filter Content
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
isFilterOpen: false,
};
},
methods: {
toggleFilter() {
this.isFilterOpen = !this.isFilterOpen;
},
},
};
</script>
<style>
.filterIcon {
vertical-align: bottom;
cursor: pointer;
}
</style>

Vuex error and action following the click

I am having a problem on a project in Vue JS and VueX. We have a Modal component which must open or close upon the click of a button. We have therefore well informed in Vue X the correct modal module:
namespaced: true,
state : {
show: false
},
// Getter functions
getters : {
showModal( state ) {
return state.show
}
},
actions : {
showModal({commit}){
commit('SHOWMODAL');
}
},
// Mutations
mutations : {
SHOWMODAL(state) {
state.show = !state.show
}
}
}
export default ModalModule;// export the module
On the component that triggers the action, we have imported the getters and the actions
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
data() {
return {
};
},
computed: {
...mapGetters('ModalModule', [
'showModal',
])
},
components: {
},
methods: {
...mapActions('ModalModule', [
'showModal',
]),
}
};
</script>
And in the template :
<button
class="bg-green-500 text-white active:bg-green-600 font-bold uppercase text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 ease-linear transition-all duration-150"
type="button"
#click="showModal()"
>
FERMER
</button>
But when i click on this button it doesn't work and when i click on the button which open the modal i've got :
<button
class="bg-green-500 active:bg-green-600 uppercase text-white font-bold hover:shadow-md shadow text-xs px-4 py-2 rounded outline-none focus:outline-none sm:mr-2 mb-1 ease-linear transition-all duration-150"
type="button"
#click="showModal()"
>
I've this error message :
Computed property "showModal" was assigned to but it has no setter.
And when i click on Fermer button i've this :
Error in v-on handler: "TypeError: _vm.showModal is not a function"
i don't understand why thank you very much if you've got a clue.
You should give an alias while mapping your action into methods so you can prevent name collision:
methods: {
...mapActions('ModalModule', {
'toggleShowModal' : 'showModal',
}),
And then in your template use the alias :
<button class="bg-green-500 active:bg-green-600 uppercase text-white font-bold hover:shadow-md shadow text-xs px-4 py-2 rounded outline-none focus:outline-none sm:mr-2 mb-1 ease-linear transition-all duration-150"
type="button"
#click="toggleShowModal()"
>

Vuejs compute property doesn't get updated properly

I'm very new to Vuejs, I'm following their documentation which is very helpful. However, I find it difficult to understand how compute properties actually are triggered.
I'm using ag-grid for my project and I would like to update the total number of rows to my custom page size drop-down list.
The following is my code:
<template>
<div id="ag-grid-demo">
<vx-card>
<!-- TABLE ACTION ROW -->
<div class="flex flex-wrap justify-between items-center">
<!-- ITEMS PER PAGE -->
<div class="mb-4 md:mb-0 mr-4 ag-grid-table-actions-left"></div>
<!-- TABLE ACTION COL-2: SEARCH & EXPORT AS CSV -->
<div class="flex flex-wrap items-center justify-between ag-grid-table-actions-right">
<vs-button class="mb-4 md:mb-0" #click="gridApi.exportDataAsCsv()">Export as CSV</vs-button>
</div>
</div>
<ag-grid-vue
ref="agGridTable"
:gridOptions="gridOptions"
class="ag-theme-material w-100 my-4 ag-grid-table"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:rowModelType="rowModelType"
#grid-ready="onGridReady"
rowSelection="multiple"
colResizeDefault="shift"
:animateRows="true"
:pagination="true"
:paginationPageSize="paginationPageSize"
:cacheBlockSize="cacheBlockSize"
:enableRtl="$vs.rtl"
:modules="modules"
></ag-grid-vue>
<div class="flex flex-wrap justify-between items-center">
<!-- CUSTOM PAGESIZE DROP-DWON -->
<div class="mb-4 md:mb-0 mr-4 ag-grid-table-actions-left">
<vs-dropdown vs-trigger-click class="cursor-pointer">
<div class="p-4 border border-solid d-theme-border-grey-light rounded-full d-theme-dark-bg cursor-pointer flex items-center justify-between font-medium">
<span class="mr-2"
>{{ currentPage * paginationPageSize - (paginationPageSize - 1) }} - {{ recordCount - currentPage * paginationPageSize > 0 ? currentPage * paginationPageSize : recordCount }} of {{ recordCount }}</span>
<feather-icon icon="ChevronDownIcon" svgClasses="h-4 w-4" />
</div>
<vs-dropdown-menu>
<vs-dropdown-item #click="gridApi.paginationSetPageSize(10)">
<span>10</span>
</vs-dropdown-item>
<vs-dropdown-item #click="gridApi.paginationSetPageSize(50)">
<span>50</span>
</vs-dropdown-item>
<vs-dropdown-item #click="gridApi.paginationSetPageSize(100)">
<span>100</span>
</vs-dropdown-item>
<vs-dropdown-item #click="gridApi.paginationSetPageSize(150)">
<span>150</span>
</vs-dropdown-item>
</vs-dropdown-menu>
</vs-dropdown>
</div>
<!-- CUSTOM TABLE PAGINATION -->
<div class="flex flex-wrap items-center justify-between ag-grid-table-actions-right">
<vs-pagination :total="totalPages" :max="maxPageNumbers" v-model="currentPage" />
</div>
</div>
</vx-card>
</div>
</template>
<script>
import { AgGridVue } from "ag-grid-vue";
import { ServerSideRowModelModule } from "#ag-grid-enterprise/server-side-row-model";
import { MenuModule } from "#ag-grid-enterprise/menu";
import { ColumnsToolPanelModule } from "#ag-grid-enterprise/column-tool-panel";
import CompanyServices from "../../../_services/company.service";
import "#/assets/scss/vuexy/extraComponents/agGridStyleOverride.scss";
export default {
components: {
AgGridVue
},
data() {
return {
gridOptions: {},
maxPageNumbers: 7,
gridApi: null,
defaultColDef: {
sortable: true,
editable: false,
resizable: true,
suppressMenu: false
},
columnDefs: [
{ headerName: "Id", field: "id", filter: false },
{
headerName: "Company Name",
field: "companyName",
filter: true,
checkboxSelection: true,
headerCheckboxSelectionFilteredOnly: true
}
],
rowModelType: "serverSide",
modules: [ServerSideRowModelModule, MenuModule, ColumnsToolPanelModule],
cacheBlockSize: 10,
};
},
computed: {
paginationPageSize() {
if (this.gridApi) return this.gridApi.paginationGetPageSize();
else return 10;
},
totalPages() {
if (this.gridApi) return this.gridApi.paginationGetTotalPages();
else return 0;
},
currentPage: {
get() {
if (this.gridApi) return this.gridApi.paginationGetCurrentPage() + 1;
else return 1;
},
set(val) {
this.gridApi.paginationGoToPage(val - 1);
}
},
recordCount: function() {
if (window.recordCount === undefined) return 0;
else return window.recordCount;
}
},
methods: {
onGridReady: function(params) {
var datasource = new ServerSideDatasource();
params.api.setServerSideDatasource(datasource);
}
},
mounted() {
this.gridApi = this.gridOptions.api;
this.gridColumnApi = this.gridOptions.columnApi;
}
};
window.ServerSideDatasource = function ServerSideDatasource(server) {
return {
getRows: function(params) {
CompanyServices.list({
startRow: params.request.startRow,
endRow: params.request.endRow,
SortColumn: "CompanyName",
SortOrder: "asc"
})
.then(response => {
window.recordCount = response.data.total;
params.successCallback(response.data.rows, response.data.total);
})
.catch(error => {
params.failCallback();
});
}
};
};
</script>
My issue is that computed property 'recordCount' does not get updated as 'window.recordCount' being changed. 'recordCount' always shows value as zero.
Could someone please shed a light here?
Thanks in advance!
There seems to be a couple of issues here, mainly that you are setting data and functions outside of the vue instance.
First of all, you should not be setting any data on window, Vue has plenty of ways to handle your data and makes it all reactive for you.
Start by moving your window.ServerSideDataSource function into the vue instance.
In Vue, functions are put under "methods", so after your onGridReady function, add a ,
and then put:
serverSideDatasource() {
return {
getRows: functions(params) {..}
}
}
Notice that I put serverSideDatasource with a small starting s, as camelCase is widely considered best practice when naming variables and functions.
Next, your window.recordCount should be put into Vue's data. Just add it after cacheBlockSize and set it like so:
recordCount: 0
Now that all your data and methods (functions) are within Vue, you can reach them by using "this".
So replace "window." with "this." all the places you use it.
Now, since your recordCount starts as 0, and is only changed when you call getRows, there is no need for a computed called recordCount, so delete that.
Notice: Having both a property in data and computed called recordCount would conflict, so even if you kept it, you would have to rename the computed.
Do tell me if this fixes it, or how far it gets you :)

template v-if="main", not working with return this.$route.path.indexOf('/') === 0 in computed "main"

works with my other routes like "/dashboard", etc, but is appearing in all routes. I basically want this template to only appear when the url is "/". Have tried it throughout my project and just plum doesn't work. PLease help and thank you!! for any suggestions.
<template v-if="main">
<div id="accordion-nav">
<div class="accordion-panels">
Dashboard <i class="fa fa-caret-right" aria-hidden="true"></i>
</div>
<div class="accordion-panels">
Shifts <i class="fa fa-caret-right" aria-hidden="true"></i>
</div>
<div class="accordion-panels">
Other <i class="fa fa-caret-right" aria-hidden="true"></i>
</div>
</div>
</template>
<script>
export default {
name: 'accordion-nav',
computed: {
main: function () {
return this.$route.path.indexOf('/') === 0
}
}
}
</script>
<style scoped>
</style>
Setup the v-if in the sidebar component itself
'<template>
<div id="sidebar" class="sidebar"
:class="{ isOpen: isOpen }">
<div class="sidebar-opener"
#click="toggle">{{openerText}}</div>
<accordion-nav v-if="main"></accordion-nav>
<accordion></accordion>
</div>
</template>'
<script>
export default {
data () {
return {
}
},
computed:{
main(){
return this.$route.path === '/'
}
}
}
</script>
With this.$route.path you get a string that equals the path of the current route resolved as absolute path in any component of your app.
So you can use this to check whether you are in the root route using:
this.$route.path === '/'
Here is the example fiddle
Use v-if="main" as a method v-if="main()" instead of a computed property
methods: {
main: function () {
return this.$route.path.indexOf('/') === 0
}
Methods do such complex updates much better than computed properties. Computed = lazy