Vuejs 3 not updating menu in primevue - javascript

I am new to Vuejs. I am using Primevue library to build the api using the composition vuejs 3.
my problem is that menu is not updating. I want to hide the show button when the element is shown and vice versa. I search all the internet and tried all the solutions I found but in vain.
Any help is appreciated, thank you
export default {
name: "Quote",
components: {
loader: Loader,
"p-breadcrumb": primevue.breadcrumb,
"p-menu": primevue.menu,
"p-button": primevue.button,
},
setup() {
const {
onMounted,
ref,
watch,
} = Vue;
const data = ref(frontEndData);
const quoteIsEdit = ref(false);
const toggle = (event) => {
menu.value.toggle(event);
};
const quote = ref({
display_item_date: true,
display_tax: true,
display_discount: false,
});
const menu = ref();
const items = ref([
{
label: data.value.common_lang.options,
items: [{
visible: quote.value.display_item_date,
label: data.value.common_lang.hide_item_date,
icon: 'pi pi-eye-slash',
command: (event) => {
quote.value.display_item_date = !quote.value.display_item_date;
}
},
{
visible: !quote.value.display_item_date,
label: data.value.common_lang.unhide_item_date,
icon: 'pi pi-eye',
command: () => {
quote.value.display_item_date = !quote.value.display_item_date;
}
}
]
]);
}
return {
data,
quoteIsEdit,
menu,
items,
toggle
};
},
template:
`
<div class="container-fluid" v-cloak>
<div class="text-right">
<p-menu id="overlay_menu" ref="menu" :model="items" :popup="true"></p-menu>
<p-button icon="pi pi-cog" class="p-button-rounded p-button-primary m-2" #click="toggle" aria-haspopup="true" aria-controls="overlay_menu"></p-button>
<p-button :label="data.common_lang.save + ' ' + data.common_lang.quote" class=" m-2" /></p-button>
</div>
</div>
`
};

The problem is the items subproperty change is not reactive, so the items.value.items[].visible props are not automatically updated when quote.value.display_item_date changes.
One solution is to make items a computed prop, so that it gets re-evaluated upon changes to the inner refs:
// const items = ref([...])
const items = computed(() => [...])
demo

Related

I'm using CDN links of intro.js, I want to implement "don't show again checkbox on Intro.js pop-up", how can we do that?

We have a requirement where I have to add a checkbox on the intro.js pop-up,
so I have used intro.js min and CSS files CDN links to use the Intro.js,
currently, I'm getting intro.js pop-up with the intro and checkbox,
Issues I am facing are:
If I am using checkboxes on multiple steps then I am not able to fetch the checkbox value, its works in the case of one step
Is there any other way to add a checkbox in intro.js
when I click the checkbox and click on the done button the value of the checkbox should be passed to the backend
I Have tried something like.
Reactjs code
const HomePage: React.FunctionComponent<Props> = ({ id }: Props) => {
const [checkTour, setCheckTour] = React.useState(false);
React.useEffect(() => {
if ((window as any).introJs) {
(window as any)
.introJs()
.setOptions({
disableInteraction: true,
exitOnEsc: false,
exitOnOverlyClicking: false,
overlayOpacity: 0.7,
showBullets: false,
showProgress: false,
showStepNumbers: false,
tooltipClass: "customTooltip",
steps: [
{
title: "Welcome",
intro: `Hello World! 👋 `,
element: document.querySelector("#logoHeighlight"),
tooltipClass: "welcomeTooltip",
},
{
intro: "<style>{color: 'red';};</style>step 1"! 👋 `,
element: document.querySelector("#cart"),
},
{
intro: `Go to plp page <label for='donotShowMeAgain'> Do Not Show Me Again </label><input type='checkbox' id='donotShowMeAgain'>`,
element: document.querySelector("#HeaderSignIn"),
},
],
})
.onbeforeexit(function () {
// eslint-disable-next-line no-console
console.log("before Done Calling");
})
.start();
const doneTour = document.getElementById("donotShowMeAgain");
doneTour?.addEventListener("click", donotShowMeAgainClicked);
return () => {
doneTour?.removeEventListener("click", donotShowMeAgainClicked);
};
}
}, []);
const donotShowMeAgainClicked = (e: any) => {
setCheckTour(e.target.checked);
};
// eslint-disable-next-line no-console
console.log("checkTour", checkTour);
return (
<Page data-test-selector="homePage" {...styles.homePageContainer} className="homePageContainer">
<Zone contentId={id} zoneName="Content" requireRows />
<AddToListModal />
</Page>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.0/umd/react-dom.production.min.js"></script>

AG Grid React: How to get the state of rows after changing the order?

After implementing the drag and drop feature on AG Grid table, I'm looking for a way to get the current state with the updated order/index of rows. My goal is to persist the table data after changing the order, but can't find the respective state of the current order.
I'd appreciate any help or any idea.
Sandbox demo and example code below
import React from "react";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
function App() {
const [gridApi, setGridApi] = React.useState(null);
const [gridColumnApi, setGridColumnApi] = React.useState(null);
const onGridReady = (params) => {
setGridApi(params.api);
setGridColumnApi(params.columnApi);
};
const defaultColDef = {
flex: 1,
editable: true
};
const columnDefs = [
{
headerName: "Name",
field: "name",
rowDrag: true
},
{ headerName: "stop", field: "stop" },
{
headerName: "duration",
field: "duration"
}
];
const rowData = React.useMemo(
() => [
{
name: "John",
stop: 10,
duration: 5
},
{
name: "David",
stop: 15,
duration: 8
},
{
name: "Dan",
stop: 20,
duration: 6
}
],
[]
);
return (
<div>
<h1 align="center">React-App</h1>
<div>
<div className="ag-theme-alpine" style={{ height: "700px" }}>
<AgGridReact
columnDefs={columnDefs}
rowData={rowData}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
rowDragManaged={true}
></AgGridReact>
</div>
</div>
</div>
);
}
export default App;
You can get the order of the rows inside the grid by iterating over them using the Grid API method forEachNode:
API for Row Nodes
const rows = [];
gridApi.forEachNodeAfterFilterAndSort((node) => rows.push(node.data));
console.log(rows);
See this implemented in the following sample.
You're currently using managed dragging by passing rowManagedDragging={true}, which means the AgGridReact component is managing the row order state.
If you want to maintain row order state outside the component, you need to use Unmanaged Dragging.
Add a handler for onRowDragMove, and use the node and overIndex or overNode properties of the event to update your local event order state, and pass it to the AgGridReact component to re-render.
Take a look at this example from the docs

How to toggle event if class is active vue3

Question
I want to toggle an event if the 'active' class gets added to an element. How can I achieve this?
In my opinion it could be somehow achieved with a watcher method but I don't know how to watch if a classname applies on an element.
I'm using vue3.
Edit
I have a carousel, where you can slide through some divs and the visible gets the class 'active'. I want to watch all divs, and if they get active call a function.
Here's an example of achieving this in a declarative way.
const { watch, ref } = Vue;
const CarouselItem = {
props: ['item'],
template: `<h1 :class="{ ...item }">{{ item.name }}</h1>`,
setup(props) {
watch(
props.item,
(item, prevItem) => item.active && console.log(`${item.name} made active!`),
);
}
};
Vue.createApp({
components: { CarouselItem },
template: '<CarouselItem v-for="item in items" :item="item" />',
setup() {
const items = ref([
{ name: 'Doril', active: false },
{ name: 'Daneo', active: false },
{ name: 'Mosan', active: false },
]);
// simulate a carousel item being made active
setTimeout(() => items.value[1].active = true, 1000);
return { items };
},
}).mount('#app');
.active {
color: red;
}
<script src="https://unpkg.com/vue#3.0.7/dist/vue.global.js"></script>
<div id="app"></div>

Ag-grid Cell containing menu button

I am using community version of ag-grid in my project. I am trying add menu button in one of the cell of every row. on clicking of the menu button, there should be menu pop up, which will have Edit/delete/rename options and I need to fire event with row value when any item on menu is clicked.
I am trying to create a cell renderer which will display the button. menu will be hidden initially and on clicking of button, I am changing display using css class. I am seeing the css class is getting added correctly but the menu is still not visible. I checked in the console and it is hidden behind the table. I used position absolute and z-index at various place but ended up with no luck.
I can not use context menu or enterprise menu out of box as I am using community version. can you please help me here? also, is there any better way to achieve this result then let me know. Thanks a lot in advance.
var students = [
{value: 14, type: 'age'},
{value: 'female', type: 'gender'},
{value: "Happy", type: 'mood'},
{value: 21, type: 'age'},
{value: 'male', type: 'gender'},
{value: "Sad", type: 'mood'}
];
var columnDefs = [
{
headerName: "Value",
field: "value",
width: 100
},
{headerName: "Type", field: "type", width: 100},
{headerName: "Action", width: 100, cellRenderer: 'actionMenuRenderer' }
];
var gridOptions = {
columnDefs: columnDefs,
rowData: students,
onGridReady: function (params) {
params.api.sizeColumnsToFit();
},
components:{
actionMenuRenderer: ActionMenuCellRenderer
}
};
function ActionMenuCellRenderer() {
}
ActionMenuCellRenderer.prototype.init = function (params) {
this.eGui = document.createElement('div')
if (params.value !== "" || params.value !== undefined || params.value !== null) {
this.eGui.classList.add('menu');
this.eGui.innerHTML = this.getMenuMarkup();
this.actionBtn = this.eGui.querySelector(`.actionButton`);
this.menuWrapper = this.eGui.querySelector(`.menuWrapper`);
this.actionBtn.addEventListener('click', event => this.onActionBtnClick(event));
}
};
ActionMenuCellRenderer.prototype.getGui = function () {
return this.eGui;
};
ActionMenuCellRenderer.prototype.onActionBtnClick = function() {
alert('hey');
this.menuWrapper.classList.toggle('showMenu');
}
ActionMenuCellRenderer.prototype.getMenuMarkup = function () {
return `
<button type="button" class="actionButton">
menu
</button>
<div class="menuWrapper">
<a class="menuItem">
Edit
</a>
<a class="menuItem">
Delete
</a>
<a class="menuItem">
Duplicate
</a>
</div>
`;
}
My plnkr sample-
plnkr sample
The issue is due to the context menu also renders inside the ag-grid cell. So it does not matter how much z-index you give it can not display it outside the cell renderer div of the ag grid. The solution is we can use the library like Tippys which will render the menu outside the ag-grid main div which will fix the issue. Below is the sample code for react to show the menu on click of a button in ag-grid cell renderer.
There was nice blog by the ag-grid on the same. Here is the reference link
import React, { useState, useEffect, useMemo, useRef } from "react";
import { AgGridReact } from "ag-grid-react";
import Tippy from "#tippyjs/react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
function ActionsMenu(props) {
const tippyRef = useRef();
const [visible, setVisible] = useState(false);
const show = () => setVisible(true);
const hide = () => setVisible(false);
const menu = (
<div className="menu-container">
<div className="menu-item" onClick={hide}>
Create
</div>
<div className="menu-item" onClick={hide}>
Edit
</div>
<div className="menu-item" onClick={hide}>
Delete
</div>
</div>
);
return (
<Tippy
ref={tippyRef}
content={menu}
visible={visible}
onClickOutside={hide}
allowHTML={true}
arrow={false}
appendTo={document.body}
interactive={true}
placement="right"
// moveTransition='transform 0.1s ease-out'
>
<button onClick={visible ? hide : show}>Actions</button>
</Tippy>
);
}
const frameworkComponents = {
ActionsMenu: ActionsMenu,
};
export default function App() {
const [rowData, setRowData] = useState([
{ make: "Ford", model: "Focus", price: 20000 },
{ make: "Toyota", model: "Celica", price: 40000 },
{ make: "BMW", model: "4 Series", price: 50000 },
]);
const [columnDefs, setColumnDefs] = useState([
{ field: "make" },
{ field: "model" },
{ field: "price" },
{ field: "", cellRenderer: "ActionsMenu" },
]);
const defaultColDef = useMemo(
() => ({
sortable: true,
filter: true,
}),
[]
);
useEffect(() => {
fetch("https://www.ag-grid.com/example-assets/row-data.json")
.then((result) => result.json())
.then((r) => setRowData(r));
}, []);
return (
<div className="ag-theme-alpine" style={{ height: 500, width: "100%" }}>
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
frameworkComponents={frameworkComponents}
/>
</div>
);
}

Unable to perform click on Vuetify vSwitch when testing with Jest

I am currently writing tests for a Vue Component which implements a Vuetify Switch. As part of the testing I want to check the functionality of the vuetify switch. I am having troubling triggering a click on the switch to then verify that the switches value has changed (and once I have done that I will verify that the value bound to the switch has changed as well)
I have looked at the API docs for Vuetify and there are no methods to directly set the state of a Vuetify switch which is bewildering in my opinion. Because of this I am trying to perform a click on the VSwitch component using wrapper.find().trigger('click') but this isn't changing the switch value, leading me to believe the click isn't doing anything at all.
Below are two tests
the first checks that the switch has the correct state on creation, which is passing
The second tries to perform a click event and check that the state has changed, which is failing
Any help in resolving this problem would be greatly appreciated.
switch.vue
<template>
<v-row>
<v-col>
<label class="label-text" :for="`${fieldLabel}`">{{labelText}}</label>
<v-row>
<label class="left-label">{{toggleLeftText}}</label>
<v-switch
:id="`${fieldLabel}`"
v-model="toggleState"
class="ma-0 pa-0"
:data-qa="`${fieldLabel}Checkbox`"
>
</v-switch>
<label class="right-label">{{toggleRightText}}</label>
</v-row>
<!--Hidden input field includes switch value in form when submitted-->
<input type="hidden" :value="toggleState" :name="`${fieldLabel}`">
</v-col>
</v-row>
</template>
<script>
export default {
name: "Switch",
props: {
fieldLabel: {
type: String,
required: true
},
labelText: {
type: String,
required: true
},
toggleLeftText: {
type: String,
required: true
},
toggleRightText: {
type: String,
required: true
},
toggleValue: {
type: Boolean,
required: true
},
},
data: function () {
return {
toggleState: this.toggleValue
}
}
}
</script>
switch.spec.js
describe('Switch', () => {
const toggleState = true;
const localVue = createLocalVue();
localVue.use(Vuetify, {
components: {
VRow,
VCol,
VSwitch,
InputError
}
});
const wrapperFactory = () => {
return shallowMount(Switch, {
localVue,
vuetify: new Vuetify(),
propsData: testProps,
});
};
const testProps = {
labelText: "Test Label",
fieldLabel: "testLabel",
toggleLeftText: "No",
toggleRightText: "Yes",
toggleValue: toggleState
};
let wrapper;
beforeEach(() => {
wrapper = wrapperFactory(testProps);
});
afterEach(() => {
wrapper.destroy();
});
it("should have correct toggle value", () => {
const vSwitch = wrapper.find(VSwitch);
expect(vSwitch.vm.value).toBe(toggleState);
});
it("should have correct toggle value after click", async () => {
const vSwitch = wrapper.find(VSwitch);
await vSwitch.trigger('click');
expect(vSwitch.vm.value).toBe(!toggleState);
});
});
I might be a bit late for answering your question, but this way you should be able to get your v-switch.
const vSwitch = wrapper.find({ name: 'v-switch' });
and then trigger the event with
vSwitch.$emit('change', <true or false>);, depending on what you're testing.
The limit with this approach is that if you have multiple v-switches in your code, you would need to target them with a data-test-id, for example like this:
<v-switch data-test-id="my-switch-1"> ... </v-switch>;
<v-switch data-test-id="my-switch-2"> ... </v-switch>;
and then I defined a helper function on top of my test file, like so:
const getSwitchComponent = (wrapper: Wrapper<Vue>, testId: string): Wrapper<Vue> => {
const switches = wrapper.findAll({ name: 'v-switch' });
const component = switches.wrappers.find(wrapper =>
wrapper.contains(`[data-test-id="${testId}"]`),
);
if (!component) {
throw Error(`Element not found: ${testId}`);
}
return component;
};
which will let you do something like this:
const mySwitch1 = getSwitchComponent(wrapper, 'my-switch-1');
const mySwitch2 = getSwitchComponent(wrapper, 'my-switch-2');
and trigger the change event so:
mySwitch1.vm.$emit('change', false);
mySwitch2.vm.$emit('change', true);

Categories