calling parent function from cellrenderer like emit on vuejs ag-grid-vue - javascript

i have implemented the ag-grid-vue on my project now i have a seperate component on one of the columns which is basically Actions , now the user can either edit view or delete depending on the selection, now for edit and delete it works just fine, the problem is when i am deleting a record i want the table to be re-rendered by fetching the updated data from the Api, for that i need to call some method in the parent, from the CellRenderer Component, let me show you the code
HTML
<ag-grid-vue
ref="agGridTable"
:components="components"
:gridOptions="gridOptions"
class="ag-theme-material w-100 my-4 ag-grid-table"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:rowData="accounts"
rowSelection="multiple"
colResizeDefault="shift"
:animateRows="true"
:floatingFilter="true"
:pagination="true"
:paginationPageSize="paginationPageSize"
:suppressPaginationPanel="true"
:enableRtl="$vs.rtl">
</ag-grid-vue>
JS
import CellRendererActions from "./CellRendererActions.vue"
components: {
AgGridVue,
vSelect,
CellRendererActions,
},
columnDefs: [
{
headerName: 'Account ID',
field: '0',
filter: true,
width: 225,
pinned: 'left'
},{
headerName: 'Account Name',
field: '1',
width: 250,
filter: true,
},
{
headerName: 'Upcoming Renewal Date',
field: '2',
filter: true,
width: 250,
},
{
headerName: 'Business Unit Name',
field: '3',
filter: true,
width: 200,
},
{
headerName: 'Account Producer',
field: '4',
filter: true,
width: 200,
},
{
headerName: 'Actions',
field: 'transactions',
width: 150,
cellRendererFramework: 'CellRendererActions',
},
],
components: {
CellRendererActions,
}
CellRenderer Component
<template>
<div :style="{'direction': $vs.rtl ? 'rtl' : 'ltr'}">
<feather-icon icon="Edit3Icon" svgClasses="h-5 w-5 mr-4 hover:text-primary cursor-pointer" #click="editRecord" />
<feather-icon icon="EyeIcon" svgClasses="h-5 w-5 mr-4 hover:text-danger cursor-pointer" #click="viewRecord" />
<feather-icon icon="Trash2Icon" svgClasses="h-5 w-5 hover:text-danger cursor-pointer" #click="confirmDeleteRecord" />
</div>
</template>
<script>
import { Auth } from "aws-amplify";
import { API } from "aws-amplify";
export default {
name: 'CellRendererActions',
methods: {
async deleteAccount(accountId) {
const apiName = "hidden";
const path = "/hidden?id="+accountId;
const myInit = {
headers: {
Authorization: `Bearer ${(await Auth.currentSession())
.getIdToken()
.getJwtToken()}`
}
};
return await API.get(apiName, path, myInit);
},
viewRecord(){
this.$router.push("/accounts/" + this.params.data[0]).catch(() => {})
},
editRecord() {
// console.log(this.params.data);
this.$router.push("hidden" + this.params.data[0]).catch(() => {})
/*
Below line will be for actual product
Currently it's commented due to demo purpose - Above url is for demo purpose
this.$router.push("hidden" + this.params.data.id).catch(() => {})
*/
},
confirmDeleteRecord() {
this.$vs.dialog({
type: 'confirm',
color: 'danger',
title: `Confirm Delete`,
text: `You are about to delete "${this.params.data[1]}"`,
accept: this.deleteRecord,
acceptText: "Delete"
})
},
deleteRecord() {
/* Below two lines are just for demo purpose */
this.$vs.loading({ color: this.colorLoading });
this.deleteAccount(this.params.data[0]).then(() => {
this.$vs.loading.close();
this.showDeleteSuccess()
});
/* UnComment below lines for enabling true flow if deleting user */
// this.$store.dispatch("userManagement/removeRecord", this.params.data.id)
// .then(() => { this.showDeleteSuccess() })
// .catch(err => { console.error(err) })
},
showDeleteSuccess() {
this.$vs.notify({
color: 'success',
title: 'User Deleted',
text: 'The selected user was successfully deleted'
})
}
}
}
</script>
now the component above is where i need to make the changes, i tried to use the reqgular vuejs emit and on but that didnt work any help?

2 ways to solve this -
1. cellRendererParams approach
You can use cellRendererParams like this -
cellRendererParams : {
action : this.doSomeAction.bind(this); // this is your parent component function
}
Now in your cell renderer component you can invoke this action
this.params.action(); // this should correspond to the object key in cellRendererParam
2. Using context gridOption
There is another way to solve this as described in this example
You basically setup context in your main grid component like this -
:context="context" (in template)
this.context = { componentParent: this };
Then in your component you can call parent component like this -
invokeParentMethod() {
this.params.context.componentParent.methodFromParent(
`Row: ${this.params.node.rowIndex}, Col: ${this.params.colDef.headerName}`
);
}

In my case #click event is being removed automatically.
Am I missing something?
<button #click="editRecord" >Click Me</button>
Actual Output:
<button >Click Me</button>

Related

Displaying image component from inside a list. How do you do this?

I hope you could help me out.
Before going through the code, let me quickly explain what I want:
I have two components that I use for uploading and displaying images. I have FileResourceService that is used for uploading, and FileResourceImage which is used for storing and displaying the data. These work together with a v-model called profilePictureFileResourceId which basically just ties the images to specific users on the page, depending on who is logged on.
When displaying the image on a template, it is very straightforward. I just grab the FileResourceImage component and tie it with the v-model.
<file-resource-image v-model="form.user.profilePictureFileResourceId" can-upload style="width: 100px; height: 100px;" />
That is all very easy, but I have some pages where I use tables that contain information about my users, and I would like for the user’s profile images to actually be displayed in the table. Here is an example of a list used for the table.
fields() {
return [
{
key: "email",
label: this.$t('email'),
sortable: true,
template: {type: 'email'}
},
{
key: "name",
label: this.$t('name'),
sortable: true
},
{
key: 'type',
label: this.$t('type'),
formatter: type => this.$t(`model.user.types.${type}`),
sortable: true,
sortByFormatted: true,
filterByFormatted: true
},
{
key: 'status',
label: this.$t('status'),
formatter: type => this.$t(`model.user.status.${type}`),
sortable: true,
sortByFormatted: true,
filterByFormatted: true
},
{
key: "actions",
template: {
type: 'actions',
head: [
{
icon: 'fa-plus',
text: 'createUser',
placement: 'left',
to: `/users/add`,
if: () => this.$refs.areaAuthorizer.fullControl
}
],
cell: [
{
icon: 'fa-edit',
to: data => `/users/${data.item.id}/edit`
}
]
}
I know that I cannot just make an array that looks like this:
fields() {
return [
{
<file-resource-image v-model="form.user.profilePictureFileResourceId" can-upload />
}
]
}
So how would you make the component display from within in the list? I believe it can be done with props, but I am totally lost at what to do.
By the way, these are the two components I use for uploading and display. I thought I might as well show them, so you can get an idea of what they do.
For upload:
import axios from '#/config/axios';
import utils from '#/utils/utils';
export const fileResourceService = {
getFileResource(fileResourceId) {
return axios.get(`file/${fileResourceId}`);
},
getFileResourceFileContent(fileResourceId) {
return axios.get(`file/${fileResourceId}/download`, {responseType: 'arraybuffer', timeout: 0});
},
downloadFileResource(fileResourceId) {
return fileResourceService.getPublicDownloadToken(fileResourceId)
.then(result => fileResourceService.downloadPublicTokenFile(result.data));
},
downloadPublicTokenFile(fileResourcePublicDownloadTokenId) {
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href =
`${axios.defaults.baseURL}/file/public/${fileResourcePublicDownloadTokenId}/download`;
tempLink.setAttribute('download', '');
document.body.appendChild(tempLink);
tempLink.click();
setTimeout(() => document.body.removeChild(tempLink), 0);
},
getPublicDownloadToken(fileResourceId) {
return axios.get(`file/${fileResourceId}/public-download-token`);
},
postFileResource(fileResource, file) {
return axios.post(`file`, utils.toFormData([
{name: 'fileResource', type: 'json', data: fileResource},
{name: 'file', data: file}
]), {timeout: 0});
}
};
Then we have the component that is used for DISPLAYING the images:
<template>
<div :style="style" #click="upload" style="cursor: pointer;">
<div v-if="url === null">
<i class="fas fa-camera"></i>
</div>
<div v-if="canUpload" class="overlay">
<i class="fas fa-images"></i>
</div>
</div>
</template>
<script>
import {fileResourceService} from '#/services/file-resource';
import utils from '#/utils/utils';
export default {
model: {
prop: 'fileResourceId',
event: 'update:fileResourceId'
},
props: {
fileResourceId: String,
canUpload: Boolean,
defaultIcon: {
type: String,
default: 'fas fa-camera'
}
},
data() {
return {
url: null
};
},
computed: {
style() {
return {
backgroundImage: this.url && `url(${this.url})`,
backgroundSize: 'contain',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
};
}
},
methods: {
upload() {
if(this.canUpload) {
utils.openFileDialog()
.then(([file]) => fileResourceService.postFileResource({}, file))
.then(result => this.$emit('update:fileResourceId', result.data.id))
.catch(() => this.$bvModalExt.msgBoxError())
}
}
},
watch: {
fileResourceId: {
immediate: true,
handler() {
this.url = null;
if (this.fileResourceId) {
fileResourceService.getFileResourceFileContent(this.fileResourceId).then(result => {
const reader = new FileReader();
reader.onload = event => this.url = event.target.result;
reader.readAsDataURL(new Blob([result.data]));
});
}
}
}
}
};
</script>

Binding a typescript variable to translate service

I'm trying to have a TypeScript variable bind to the translate service just like binding in the HTML markup, which works fine.
Here's what I've tried so far
ngOnInit() {
this.customTranslateService.get("mainLayout.userProfileDropdown.changeLocale").subscribe((result) => {
this.changeLocaleText = result;
})
this.customTranslateService.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.changeLocaleText = this.customTranslateService.instant("mainLayout.userProfileDropdown.changeLocale");
});
this.userProfileMenuOptions = [
{
text: this.changeLocaleText, itemId: "LocaleSelect"
},
{
text: "Report a bug", itemId: "BugReport"
},
{
text: "Request a feature", itemId: "FeatureRequest"
},
{
text: "Log Out", itemId: "LogOut"
}
];
}
customTranslateService is just a service that wraps TranslateService.
The first subscription works ok, but when I switch languages, the onLangChange does trigger, changing the variable content correctly, but userProfileMenuOptions's reference to changeLocaleText is not binded therefore not updated.
Using a BehaviorSubject can't really be done here as it is typescript code, not html markup that can use the async pipe.
Maybe recreating the userProfileMenuOptions array everytime the language change subscription is called could be an option, although I'm not sure the component that uses the array will like it.
PS: instant will work here because I have an application loader that loads all available languages before the application is available to the user.
Any ideas ?
ngOnInit() {
this.customTranslateService.get("mainLayout.userProfileDropdown.changeLocale").subscribe((result) => {
this.changeLocaleText = result;
})
const getUserPorfileMenuOptions = (changeLocaleText: string) => {
return [
{
text: this.changeLocaleText, itemId: "LocaleSelect"
},
{
text: "Report a bug", itemId: "BugReport"
},
{
text: "Request a feature", itemId: "FeatureRequest"
},
{
text: "Log Out", itemId: "LogOut"
}
];
}
this.customTranslateService.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.changeLocaleText = this.customTranslateService.instant("mainLayout.userProfileDropdown.changeLocale");
this.userProfileMenuOptions = getUserPorfileMenuOptions(this.changeLocaleText);
});
this.userProfileMenuOptions = getUserPorfileMenuOptions(this.changeLocaleText);
}

Open Options menu on click event of icon in ag-grid in angular

I have an icon inside ag-grid cell and on the click of icon I am trying to open a popup near the icon which will have edit and delete as below:
I have code as below:
<ag-grid-angular
style="padding-top: 10px;"
[rowData]="rowData"
[columnDefs]="columnDefs"
(gridReady)="onGridReady($event)"
[pagination]="true"
[paginationPageSize]="5"
[defaultColDef]="defaultColDef"
domLayout="autoHeight"
>
</ag-grid-angular>
public columnDefs: ColDef[] = [{
{
headerName: 'Options',
field: 'propId',
cellRenderer: function (params: any) {
let propId = params.value;
const eDiv = document.createElement('div');
var optionsOnHtml = '<i class="icon-options" style="color:#0672CB; margin-right: 10px;padding-right:10px" aria-hidden="true"></i>';
eDiv.innerHTML = optionsOnHtml;
const optionsIcon = eDiv.querySelectorAll('.icon-options')[0];
optionsIcon.addEventListener('click', () => {
// Code here
});
return eDiv;
},
cellStyle: {
borderRightColor: '#e1e1e1',
borderRightWidth: '1px',
borderRightStyle: 'solid',
},
},];
I wanted to know how can I add these two options along with propId so that I can edit / delete the record with propId.
You will need to add getContextMenuItems in your ag-grid-angular.
<ag-grid-angular
..
[getContextMenuItems]="getContextMenuItems"
></ag-grid-angular>
Then add something like:
getContextMenuItems(params) {
let result = [
{
name: "Edit",
action: () => { this.openEdit(params) }
},
{
name: "Delete",
action: () => { this.openDelete(params) }
}
];
});
return result;
}
And then finally bind this where you are initializing the grid.
this.getContextMenuItems = this.getContextMenuItems.bind(this);
This link should help

Vuex Store not updating when changing table values

I am working on a table using Quasar framework's Q-Popup-edit and Vuex Store.
It populates correctly. However, when I change values on the table, it goes back to its current value and is not reflected at all.
Here is my table:
tableData: [
{
'FrozenYogurt' : {
'topping': 'strawberry'
},
'FrozenYogurtPart2' : {
'topping2': 'strawberry2'
}
},
{
'IceCreamSandwich' : {
'baseFlavor': 'chocolate',
'somethingAgain': 'chocolatiest'
}
},
{
'CreamPuff' : {
'sourceBakery': 'Starbucks'
}
}
]
My Vuex mutation:
mutations: {
saveUpdatedData (newVal) {
console.log('inside MUTATION saveUpdatedData')
state.tableData.length = 0
state.tableData.push(newVal)
}
}
And using a two-way computed property (get/set) to populate the table:
tableRows: {
get: function () {
console.log('inside GET')
return this.$store.state.tableData.reduce((acc, item) => {
Object.keys(item).forEach(name => {
Object.keys(item[name]).forEach(property => {
acc.push({ name, property, value: item[name][property]})
})
})
return acc
}, [])
},
set: function (newValue) {
console.log('inside SET')
this.$store.commit('saveUpdatedData', newValue)
}
}
But the set() function isn't being called at all.
And finally my Vue code:
<q-table
:data="tableRows"
:columns="columns"
:rows-per-page-options="[]"
row-key="name" wrap-cells>
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="desc" :props="props">
{{ props.row.name }}
<q-popup-edit v-model="props.row.name" buttons>
<q-input v-model="props.row.name" dense autofocus counter ></q-input>
</q-popup-edit>
</q-td>
<q-td key="property" :props="props">
{{ props.row.property }}
<q-popup-edit buttons v-model="props.row.property">
<q-input type="textarea" v-model="props.row.property" autofocus counter #keyup.enter.stop></q-input>
</q-popup-edit>
</q-td>
<q-td key="value" :props="props">
{{ props.row.value }}
<q-popup-edit v-model="props.row.value" buttons>
<q-input v-model="props.row.value" dense autofocus ></q-input>
</q-popup-edit>
</q-td>
</q-tr>
</template>
</q-table>
How can I make the changes reflect on the vuex store??
CodePen here:
https://codepen.io/kzaiwo/pen/BaNYbZZ?editors=1011
Help!
You can extend your computed property by using computed getter and setter:
https://v2.vuejs.org/v2/guide/computed.html#Computed-Setter
Your existing computation code moves to the get function.
The set function needs to be implemented.
You can refer to this url https://vuex.vuejs.org/guide/mutations.html
You will find the correct way to mutate the vuex store.
I am afraid that you still have to use data prop like in the docs. (I have tried computed property setter but it wouldn't work)
To integrate it with the store you can use the following solution: https://codepen.io/woothu/pen/VwLXzNQ?editors=1011
data () {
return {
columns: [
{ name: 'desc', align: 'left', label: 'Data Element', field: 'desc', sortable: true, style: 'min-width: 180px; max-width: 180px;' },
{ name: 'property', align: 'center', label: 'Property', field: 'property', sortable: true, style: 'min-width: 20px; max-width: 20px;' },
{ name: 'value', align: 'left', label: 'Value', field: 'value', sortable: true, style: 'min-width: 300px; max-width: 300px; word-wrap: break-word; font-family: Consolas; font-size: 13px' }
],
tableRows: null
}
},
beforeMount () {
this.tableRows = this.$store.state.tableData.reduce((acc, item) => {
Object.keys(item).forEach(name => {
Object.keys(item[name]).forEach(property => {
acc.push({ name, property, value: item[name][property]})
})
})
return acc
}, [])
},
watch: {
tableRows: {
deep: true,
handler (val) {
this.$store.commit('saveUpdatedData', val)
}
}
}
Btw. I think the QTable component should emit the event when it changes data with the updated data structure. (Or make some other functionality to enable handling Vuex) It is probably worth opening the PR with mention of this discussion.

How to change property of indivudal buttons in vuetify data-table?

I have a vuetify data-table which renders the data received from axios call.
On the last column I'm using template v-slot for bespoke column so I can add two buttons. v-btn accepts has two props for loading state as per the documentation https://vuetifyjs.com/en/components/buttons#loaders:
:loading
:disabled
The problem is that when I call a function that changes those values, all of the buttons in the data table are receiving the prop state so instead of 1 button displying loader, all of them are.
<v-row no-gutters>
<v-data-table
:headers="tableHeaders"
:items="requests"
:items-per-page="10"
class="elevation-1"
:loading="loading" loading-text="Loading... Please wait"
>
<template v-slot:item.action="{ item }">
<v-btn color="success" #click="createPacks(item)" :loading="createloading" :disabled="createloading">Create</v-btn>
<v-btn color="error" #click="excludeRequest(item)" :loading="cancelloading" :disabled="cancelloading">Cancel</v-btn>
</template>
</v-data-table>
</v-row>
I'm aware this is because the buttons in the DOM are not unique and the framework is calling all of them but I have no idea how to change that default behaviour.
Data:
export default {
data() {
return {
loading: null,
error: null,
tableHeaders: [
{
text: 'ID',
value: 'req_id',
align: 'center',
},
{ text: 'Template', value: 'templateConcatenated'},
{ text: 'No of batches', value: 'no_batches' },
{ text: 'Batch size', value: 'batch_size' },
{ text: 'Date requested', value: 'requested_at' },
{ text: 'Requested by', value: 'requester' },
{ text: 'Actions', value: 'action', sortable: false, align: 'center'},
],
createloading: false,
cancelloading: false,
successmessage: '',
errormessage: '',
};
},
methods: {
createPacks(item) {
this.loading = true;
this.createloading = true;
let page_url = '/api/CreateProcedure?api_token='+this.$api_token;
axios
.post(page_url, item)
.then((response) => {
this.loading = false;
this.error = null;
this.createloading = false;
this.successmessage = 'Packs created successfully!';
this.errormessage = null;
})
.catch(err => {
this.createloading = false;
this.successmessage = null;
this.errormessage = 'Error creating the packs: '+err;
console.log("error: "+err);
})
},
}
}
Any idea how to call each individual button to change it's state?
Thank you
You've to set the loading properties on the item itself instead of defining them globally:
createPacks(item) {
this.loading = true;
item.createloading = true;
let page_url = '/api/CreateProcedure?api_token='+this.$api_token;
axios
.post(page_url, item)
.then((response) => {
this.loading = false;
this.error = null;
item.createloading = false;
this.successmessage = 'Packs created successfully!';
this.errormessage = null;
})
.catch(err => {
item.createloading = false;
this.successmessage = null;
this.errormessage = 'Error creating the packs: '+err;
console.log("error: "+err);
})
},
== UPDATE ==
I added a code based on the codepen you added in the comments, you have to use item.createloasing also in the HTML else it is not working. https://codepen.io/reijnemans/pen/LYPerLg?editors=1010
Currently only one button is working at the same time but this is probably because of axios is not defined in the codepen.

Categories

Resources