Author: CTO Epicmax Yauheni Prakopchyk
Original: Epicmax Blog
Vuestic is a growing vue UI framework with high customizability and SSR support. It’s a good choice for modern responsive application.
In this article you will learn to create an application using Vuestic UI and AG Grid.
You can find repo here and codesandbox here.
Requirements
The application we’re going to build is a CRUD page of users - something an admin would use to manage users. You can find something similar in almost any admin dashboard.
Here’s a wireframe for application:
Can’t work on app without a plan, so here’s one:
-
Scaffold new project
-
Make a simple table with
image
,name
,email
-
Add
active
column -
Implement sorting for all columns
-
Implement full-text filtering
-
Add create, edit and delete functionality
Scaffold new project
As we’re starting from scratch, we need a runtime environment. Vite is an optimal choice.
npm create vite ag-grid-project -- --template vue-ts
cd ag-grid-project
npm install
npm run dev
If you open the page in browser - you should see default vite page:
There are several dependencies we’d need for our project:
npm install vuestic-ui @vuestic/ag-grid-theme sass
Then we should add Vuestic UI import to main.ts
file:
import { createApp } from "vue";
import App from "./App.vue";
import { createVuesticEssential } from "vuestic-ui"; // <--
import 'vuestic-ui/dist/vuestic-ui.css' // <--
const app = createApp(App);
app.use(createVuesticEssential()); // <--
app.mount("#app");
Make a simple table with image
, name
, email
Now let’s work on UsersPage.vue
component:
<template>
<div style="height: 300px; width: 100%;">
<ag-grid-vue
class="ag-theme-vuestic"
style="width: 100%; height: 100%;"
:columnDefs="columnDefs"
:rowData="reactiveUsers"
:modules="modules"
/>
</div>
</template>
<script setup lang="ts">
import { AgGridVue } from '@ag-grid-community/vue3'
import {
ClientSideRowModelModule,
} from '@ag-grid-community/client-side-row-model'
import { users } from '../data/users'
import { reactive } from 'vue'
const modules = [ClientSideRowModelModule]
const reactiveUsers = reactive(users)
const columnDefs = [
{ field: 'name' },
{ field: 'email' },
]
</script>
<style lang="scss">
@import "@vuestic/ag-grid-theme/index.scss";
</style>
Here we used modules
import for AG Grid instead of packages
. The difference is that modules
is something like lego bricks, you assemble the configuration you need with small pieces to minimize footprint, and packages
bundle already has everything included. modules
is generally preferrable.
We need data for several users:
type User = {
id: number,
image: string,
name: string,
email: string,
active: boolean,
}
export const users: User[] = [
{
id: 1,
image: 'https://picsum.photos/id/1/50/50',
name: 'Ashley Mcdaniel',
email: 'ashleymcdaniel@nebulean.com',
active: true,
},
...
We don’t use backend in this example, but normally you’ll fetch this data from API. In our case - we import it directly from data/users.ts
file.
reactive(users)
is of no importance right now, but would be useful for dynamic data updates later on.
Image doesn’t look right! We can use AG Grid formatter, but better idea would be to use va-avatar from Vuestic UI. For that let’s create a component:
<template>
<va-avatar :src="url" :size="44" />
</template>
<script setup lang="ts">
import { VaAvatar } from 'vuestic-ui'
import { computed } from 'vue'
const props = defineProps<{params: {value: string}}>()
const url = computed(() => props.params.value)
</script>
and register it in UsersPage.vue
const columnDefs = [
{ field: 'image', cellRenderer: UserImage, width: 65 },
...
All custom components we have to attach via cellRenderer
. From component side we also have to use params
, which is a prop AG Grid passes to component.
We can mark this step as complete and do a little celebration 🎉!
Add active
column
In active field we modify user data. Let’s create the component
<template>
<va-switch class="user-active" v-model="user.active" size="small" />
</template>
<script setup lang="ts">
import { VaSwitch } from 'vuestic-ui'
import { computed, reactive } from 'vue'
import { User } from '../data/users'
const props = defineProps<{params: {data: User}}>()
// AG Grid strips reactivity for some reason, so we have to force it back.
const user = computed(() => reactive(props.params.data))
</script>
and register it in UsersPage.vue
const columnDefs = [
` `...
{ field: 'active', cellRenderer: UserActive, width: 75 },
Generally, you won’t modify user data directly, and instead rely on backend. Something like this:
const active = computed(() => props.params.data.active)
const setActive = (value) => api.setActive(props.params.data.id, value)
Then, in component we can:
<va-switch :modelValue="active" @modelValue:update="setActive" />
But our example is complicated enough as it is 😉, so let’s move to the next step.
Implement sorting for all columns
Sorting is the simplest thing if we just use sorting from AG Grid:
const columnDefs = [
{ field: 'image', headerName: '', cellRenderer: UserImage, width: 65 },
{ field: 'name', \*\*sortable: true\*\* },
{ field: 'email', \*\*sortable: true\*\* },
{ field: 'active', cellRenderer: UserActive, width: 75, \*\*sortable: true\*\* },
]
Implement full text filtering
For filtering AG Grid built in functionality is far from being sufficient, and we have to mostly implement it externally.
<template>
<va-input
v-model="filter"
placeholder="Filter..."
/>
<div style="height: 300px; width: 100%;">
<ag-grid-vue
class="ag-theme-vuestic"
style="width: 100%; height: 100%;"
:columnDefs="columnDefs"
:rowData="reactiveUsers"
:isExternalFilterPresent="() => true"
:doesExternalFilterPass="doesExternalFilterPass"
@grid-ready="onGridReady"
:modules="modules"
/>
</div>
</template>
<script setup lang="ts">
...
const filter = ref('')
let gridApi: any = null
const onGridReady = (params: any) => {
gridApi = params.api
}
watch(() => filter.value, () => {
gridApi.onFilterChanged()
})
const doesExternalFilterPass = ({ data: user }: {user: User}) => {
return JSON.stringify(user).includes(filter.value)
}
...
That’s a lot of code, so let’s try to piece it apart.
-
watch
part is needed to let AG Grid know that filter had changed, as it doesn’t catch up on its own. -
isExtenalFilterPresent
informs AG Grid that external filter is present. -
doesExternalFilterPass
allows us to pass external filter.
A bit excessive, but it works 😬.
Add create, edit and delete functionality
That’s a big task, compared to what we did before, so let’s do it piece meal.
First, let’s focus on UI: we’d need “Actions” column with “Edit” and “Delete” buttons as well as “Create” button.
For “Actions”, as before, we’ll create a component with 2 buttons:
<template>
<div>
<va-button flat icon="edit" @click="editUser(user)" />
<va-button flat icon="close" @click="removeUser(user)" />
</div>
</template>
<script setup lang="ts">
import { VaButton } from 'vuestic-ui'
import { computed, reactive } from 'vue'
import { User } from '../data/users'
const props = defineProps<{params: {data: User, context: {
editUser: (user: User) => {},
removeUser: (user: User) => {},
}}}>()
const { editUser, removeUser } = props.params.context
const user = computed(() => reactive(props.params.data))
</script>
The key difference with “Active” component is that we use params.context
, which gives access to state from parent component. In our case we pass editUser
and removeUser
functions from parent, which “Actions” component can run.
Create button is not hard at all and will just call a method UsersPage
component.
<va-button @click="createUser()" icon="add" text-color="white" color="success" />
Before we focus on users list logic we have to add an editing modal, with input for each field:
<va-modal
:model-value="!!selectedUser"
ok-text="Save"
cancel-text="Cancel"
@cancel="selectedUser = null"
@ok="saveUser()"
\>
<va-input class="mb-3 d-block" label="Image" v-model="selectedUser.image"/>
<va-input class="mb-3 d-block" label="Name" v-model="selectedUser.name"/>
<va-input class="mb-3 d-block" label="Email" v-model="selectedUser.email"/>
</va-modal>
In parent component we implement all logic for creating, updating and deletion:
const editUser = (user: User) => {
selectedUser.value = { ...user }
}
const removeUser = (userToRemove: User) => {
reactiveUsers.value = reactiveUsers.value.filter(user => user !== userToRemove)
}
const createUser = () => {
selectedUser.value = {
id: Math.ceil(Math.random()\*10000),
image: 'https://picsum.photos/id/100/50/50',
name: '',
email: '',
active: true,
}
}
const saveUser = () => {
const currentUser = selectedUser.value
selectedUser.value = null
if (!currentUser) {
return
}
const users = reactiveUsers.value
const existingUser = users.find(user => user.id === currentUser.id)
if (existingUser) {
reactiveUsers.value = reactiveUsers.value.map(user => existingUser === user ? currentUser : user)
} else {
reactiveUsers.value.push(currentUser)
}
}
const agGridContext = {
editUser,
removeUser,
}
The only interesting part here is agGridContext
, which we have to pass to ag-grid-vue
component as a prop:
<ag-grid-vue
...
:context="agGridContext"
/>
Alternative would be to use inject/provide, but it doesn’t work for composition api.
Here’s the end result of our journey:
And here’s an actual page if you want to click around.
Conclusion
In this article you learned how to use Vuestic UI and AG Grid together. AG Grid Vue is an advanced and configurable solution, that will support your application regardless of scale and complexity. That’s definitely a good option to consider if you’re looking for open source data grid with virtual scroll, fixed columns, editing and many other features you can check in docs.
Author: CTO Epicmax Yauheni Prakopchyk
Original: Epicmax Blog