I have added the "Edit" button in the grid of the table for every row. The data for the table is coming from the api with the following JSON response.
{
Id: 1783
total: 2
trasfer: true
Sizing: true
name: "runner"
}
I am trying to implement when user clicks the edit button a new screen appears where one can edit the values of that row. So far I have implemented a button rendered component and alert when the button is click. How can I implement router to a new screen along with editable data of that particular row.
Demo: https://ag-grid2.zoltanhalasz.net/
button-renderer.component.ts
#Component({
selector: 'app-button-renderer',
template: `
<button type="button" (click)="onClick($event)">{{label}}</button>
`
})
export class ButtonRendererComponent implements ICellRendererAngularComp {
afterGuiAttached?(params?: import("ag-grid-community").IAfterGuiAttachedParams): void {
return;
}
ngAfterViewInit(): void {
return;
}
public params;
label: string;
constructor(){}
agInit(params: ICellRendererParams): void {
this.params = params;
this.label = this.params.label || null;
}
refresh(params: any): boolean {
return false;
}
public onClick(event) {
this.params.data[this.params.colDef.field] = event.checked;
if (typeof this.params.context.componentParent.notifyCellUpdate === "function"){
this.params.context.componentParent.CellUpdate(this.params);
}
}
}
app.component.ts
columnDef = [ {
headerName: 'Button', field : 'changeSettings',
cellRenderer: 'buttonRenderer',
cellStyle: {'text-align': 'center'},
cellRendererParams: {
label: 'Open'
}
} ]
CellUpdate(params){
if (params.colDef.field === "changeSettings"){
alert("Notified Button Clicked");
}
}
Create a component and navigate to it with input data.
There are many ways for transforming data between components.
Read this article
In your sample demo you should define route with an id and navigate to it's component then call an api to fetch the data.
Also you can open a modal to show the data instead.
Example:
CellUpdate(params){
if (params.colDef.field === "changeSettings"){
const modalRef = this.modalService.open(YourEditComponent);
modalRef.componentInstance.data= params;
}
}
Related
The source code:
import { LitElement, html, css } from '../vendor/lit-2.4.0/lit-all.min.js';
export class SearchInput extends LitElement {
static get properties() {
return {
src: { type: String },
items: { type: Array }
}
};
static styles = css`
`;
constructor() {
super();
this.items = [
{ text: 'Hola' },
{ text: 'mundo!' }
];
this.selectedItem = null;
this.text = 'foo';
}
selectItem(item) {
this.selectedItem = item;
this.text = this.selectedItem.text;
}
render() {
return html`
<div class="control">
<input class="input" type="text" value="${this.text}">
<ul class="result-list">
${this.items.map((item) => html`<li #click="${this.selectItem(item)}">${item.text}</li>`)}
</ul>
</div>
`;
}
}
customElements.define('search-input', SearchInput);
The text input (input type="text") value is not updating after changing property (this.text) using an event (this.selectItem) with LitElement library.
I tried it in browser but there is no error in browser console.
I expect that input value update after changing property with the event.
Thanks for the question! There are a few minor issues resulting in the value not updating.
One issue is that this.text is not a reactive property, so changing it isn't scheduling a re-render. Fix is to add text to the static properties.
The second issue is that your event listener click handler is the result of calling this.selectItems(item) and not a function, fixed with: #click=${() => this.selectItems(item)}.
Bonus: You may want to change the value attribute expression to a property expression using the live directive, .value="${live(this.text)}". I suggested this because the native input browser element always updates its contents if you update the value property, but only updates before a user has interacted with it when updating the value attribute. And the live directive is useful to tell Lit to dirty check the live DOM value in the input element.
Your code with the minor fixes: https://lit.dev/playground/#gist=a23dfbcdfbfcfb7de28b1f7255aaa8ee
or running in StackOverflow:
<script type="module">
import { LitElement, html, live } from 'https://cdn.jsdelivr.net/gh/lit/dist#2/all/lit-all.min.js';
class SearchInput extends LitElement {
static get properties() {
return {
src: { type: String },
items: { type: Array },
text: { type: String }, // <- Added this to make `this.text` a reactive property.
}
};
constructor() {
super();
this.items = [
{ text: 'Hola' },
{ text: 'mundo!' },
{ text: 'click these' },
];
this.selectedItem = null;
this.text = 'foo';
}
selectItem(item) {
this.selectedItem = item;
this.text = this.selectedItem.text;
}
render() {
return html`
<div class="control">
<!-- live directive is needed because user can edit the value of the input.
This tells Lit to dirty check against the live DOM value. -->
<input class="input" type="text" .value="${live(this.text)}">
<ul class="result-list">
<!-- Click event is a function -->
${this.items.map((item) =>
html`<li #click="${() => this.selectItem(item)}">${item.text}</li>`)}
</ul>
</div>
`;
}
}
customElements.define('search-input', SearchInput);
</script>
<search-input></search-input>
I have a modal component and I'm writing the story for it. It looks something like this:
import { Story, Meta } from '#storybook/html';
export default {
title: 'Components/Modal',
argTypes: {
open: {
name: 'Opened',
control: 'boolean'
},
},
args: {
open: false,
}
} as Meta;
const Template: Story = (args) => {
return `
<my-modal open="${args.open}">
Some example content inside the modal
</my-modal>
`;
};
export const Modal: Story = Template.bind({});
I have the arg open on the controls and I can change its value to true and the modal shows. But I would like the story to have a button and when it's clicked, the modal shows.
I can't find a way to do this in the current version of Storybook for web components.
I've seen there are some hooks available for React (import { useArgs } from '#storybook/api';) that allows you to change the arguments value dynamically but I can't see how to do this for web components?
Any helps will be highly appreciated.
Just add that button to the template:
import { Story, Meta } from '#storybook/html';
export default {
title: 'Components/Modal',
argTypes: {
open: {
name: 'Opened',
control: 'boolean'
},
},
args: {
open: false,
}
} as Meta;
const Template: Story = (args) => {
return `
<button
type="button"
onclick="this.nextElementSibling.open = !this.nextElementSibling.open">
Toggle Modal
</button>
<my-modal .open=${args.open}>
Some example content inside the modal
</my-modal>
`;
};
export const Modal: Story = Template.bind({});
Also, for boolean attributes - if implemented properly -
you should work with the property (prefix it in the template with a .) rather than the attribute.
Doing that with all native code isn't rocket science...
<my-dialog id="DIALOG" open>
Hello *Native* Web Components world!
</my-dialog>
<button onclick="DIALOG.open()">OPEN</button>
<script>
customElements.define("my-dialog", class extends HTMLElement {
static get observedAttributes() {
return ["open"];
}
constructor() {
super() // sets and returns 'this'
.attachShadow({mode:"open"}) // sets and return this.shadowRoot
.innerHTML = `<dialog><slot></slot><button>Close</button></dialog>`;
this.dialog = this.shadowRoot.querySelector("dialog");
}
connectedCallback() {
this.onclick = () => this.close(); // or attach to button
}
attributeChangedCallback(name,oldValue,newValue) {
this.open();
}
open() {
this.dialog.showModal(); // or .show()
}
close() {
this.dialog.close();
}
});
</script>
I am having a custom column filter with a button toggle.By default, the column filter is set to false. When I click on the button the column filter is toggled by setting the floatingFilter: true. While the floatingFilter becomes true during the button click it doesn't show the filter.
Whereas if we make the floatingFilter to be true by default at that time it shows the filter after that if we toggle the button to show/hide the floatingFilter it works expected.
May i know how to update the defaultColDef dynamically in ag-grid to make the floatingFilter to be true during button click.
defaultColDef:
this.defaultColumnDefs = {
suppressMenu: true,
suppressMovable: true,
sortable: true,
resizable: true,
floatingFilter: this.hasFloatingFilter
};
toggleFilter:
toggleFloatingFilter() {
this.hasFloatingFilter = !this.hasFloatingFilter;
this.clearSelectedRows();
this.gridApi.setRowData(this.rowData);
this.defaultColumnDefs = {...this.defaultColumnDefs, floatingFilter: this.hasFloatingFilter};
if (!this.hasFloatingFilter) {
this.gridApi.setFilterModel(null);
this.loadData();
}
setTimeout(() => {
this.gridApi.refreshHeader();
}, 0);
}
GridHTML:
<app-data-grid
[columnDefs]="columnDefs"
[defaultColDef]="defaultColumnDefs"
[overlayNoRowsTemplate]="overlayNoRowsTemplate"
[frameworkComponents]="frameworkComponents"
[rowData]="rowData"
[hasMultipleRows]="rowSelection"
[hasRowAnimation]="hasRowAnimation"
[multiSortKey]="multiSortKey"
(rowDataChanged)="onRowDataChanged()"
(selectionChanged)="onSelectionChanged()"
(rowClicked)="gotoDetailView($event)"
(sortChanged)="onSortChanged($event)"
(columnResized)="onColumnResized()"
(gridReady)="OnGridReady($event)"
>
</app-data-grid>
AppDataGrid Component:
export class DataGridComponent {
gridApi;
gridColumnApi;
constructor() {}
#Input() columnDefs: DeviceColumns;
#Input() rowData: any[];
#Input() overlayNoRowsTemplate: any;
#Input() defaultColDef: any;
#Input() hasMultipleRows: boolean;
#Input() hasRowAnimation: boolean;
#Input() hasFloatingFilter: boolean;
#Input() frameworkComponents: any;
#Input() multiSortKey: string;
#Output() gridReady = new EventEmitter();
#Output() selectionChanged = new EventEmitter();
#Output() rowClicked = new EventEmitter();
#Output() rowDataChanged = new EventEmitter();
#Output() sortChanged = new EventEmitter();
#Output() columnResized = new EventEmitter();
onGridReady(params): void {
this.gridApi = params.api;
this.gridReady.emit(params);
this.gridApi.setGridAutoHeight(true);
}
onSelectionChanged(): void {
this.selectionChanged.emit(this.gridApi);
}
onRowClicked(params): void {
this.rowClicked.emit(params.data);
}
onRowDataChanged(): void {
this.rowDataChanged.emit();
}
onSortChanged(params): void {
this.sortChanged.emit(params.api.getSortModel());
}
onColumnResized() {
this.columnResized.emit(this.gridApi);
}
}
Ag-Grid HTML
<ag-grid-angular
class="ag-theme-balham"
[rowData]="rowData"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[overlayNoRowsTemplate]="overlayNoRowsTemplate"
[frameworkComponents]="frameworkComponents"
(selectionChanged)="onSelectionChanged()"
(rowDataChanged)="onRowDataChanged()"
(rowClicked)="onRowClicked($event)"
(sortChanged)="onSortChanged($event)"
[suppressRowClickSelection]="true"
[rowSelection]="hasMultipleRows"
[animateRows]="hasRowAnimation"
[multiSortKey]="multiSortKey"
(columnResized)="onColumnResized()"
(gridReady)="onGridReady($event)"
>
</ag-grid-angular>
Example: https://plnkr.co/edit/w2UDNd4u657tdr0Q?preview
Current behavior
Not showing the floating filter during button click (When the flaotingFilter is false by default and it is changed to true dynmically)
Expected behavior
It should show the floating filter when
ag-Grid version: 23.2.1
you need to do this with columnDefs instead of defaultColDef. plunkr link
showFilter() {
/*
this.defaultColDef = {...this.defaultColDef, floatingFilter: true};
setTimeout(() => {
this.gridApi.refreshHeader();
}, 0);*/
var columnDefs = this.gridApi.getColumnDefs();
columnDefs.forEach(function (colDef, index) {
colDef.floatingFilter = true;
});
this.gridApi.setColumnDefs(columnDefs);
}
Also AG grid merges defaultColDefs with colDefs while rendering the grid and then uses colDefs object in setupFloatingFilter method thus setting value in defaultColDefs is of no use.
Calling gridApi.setColumnDefs calls HeaderContainer.prototype.init thus rendering your filter component whereas calling refreshHeader interanlly calls gridPanel.setHeaderAndFloatingHeights and headerRootComp.refreshHeader but there is no call to init function which will render your filter component.
To update defaultColDef in agGrid you can use setDefaultColDef method in gridApi passing there the brand new colDef. And do not forget to refresh all headers.
this.gridApi.api.setDefaultColDef({
...this.defaultColDef,
floatingFilter: true
});
this.gridApi.api.refreshHeader();
Hope this will help
I have a parent component (B) that is getting data from it's parent input (A)
(C) have is (B) child component.
Inside (B) I'm having a selector that gets data from the store.
export class BComponent implements OnChanges {
#Input() branchId;
ngOnChanges() {
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
directionChanged(event) {
this.selectedDirection = event;
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
}
}
directionChanged is the Output event that I get from (C)
The issue this that selectedDataByBranch subscription is not getting the new data update triggered inside selectedDataByBranch$
I have also tried this way
directionChanged(event) {
this.selectedDirection = event;
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection });
}
What i could suggest is. Turn your parameters into a Subject then merge with the store selection, in your directionChanged(event) method provide value to subject.
So your final code will be something like this:
export class BComponent implements OnChanges {
#Input() branchId;
criterias$= new Subject<{branchId:number,dir:number}>;
ngOnChanges() {
this.selectedDataByBranch$ = this.criterias$.pipe(mergeMap(criteria=> this.store.pipe(
select(selectBranchDirections, { branchId: criteria.branchId, dir: this.searchDirection})
)));
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
this.criterias$.next({branchId:this.branchId,dir:this.sortDirection}); // init first call
}
directionChanged(event) {
this.selectedDirection = event;
this.criterias$.next({ branchId: criteria.branchId, dir: this.searchDirection}});
);
}
}
This stackblitz tries to materialize what i say.
I want to update/re-render the component after a new update comes up. All I am doing is:
I have a list of dealers for a casino game, what I want is to add a new dealer, and once the new dealer is added then display it in the view. It is actually happening, but in order for me to see the new dealer, I have to reload the page.
I am not updating the state, I am working with this.props. Look at my code
#connectToStores
export default class Dealers extends Component {
constructor (props) {
super(props);
this.state = {}
}
componentWillMount () {
GetDealersActions.getDealers();
}
static getStores () {
return [ GetDealersStore, CreateDealersStore ];
}
static getPropsFromStores () {
return {
...GetDealersStore.getState(),
...CreateDealersStore.getState(),
}
}
render () {
return (
<div>
{!!this.props.dealerData ?
this.props.dealerData.dealersData.map((dealer) => {
return (here I am rendering what I need);
: <p>Loading . . .</p>
</div>
}
_addDealer = () => {
CreateDealersActions.createDealer({
DealerName : this.refs.DealerName.getValue(),
CardId : this.refs.CardId.getValue(),
NickName : this.refs.NickName.getValue(),
});
}
}
as you see the component above in the code is doing the initial rendering properly, the problem comes up when you hit _addDealer(), which is not updating the component, you should reload the page in order to see the new item in the view.
If you do a console.log(this.props); within _addDealer(), you will get something like this
{params: Object, query: Object, dealerData: Object, newDealerData: null}
where dealerData holds the full data of the dealers in the view but you can't see there the new dealer created. And newDealerData remains null
so, what do you think I should do in order to update the component everytime a new prop/dealer comes up ? or how do I update the props? which is the proper method in this situation ?
here is the full code for stores and actions just in case
action
#createActions(flux)
class CreateDealersActions {
constructor () {
this.generateActions('createDealerSuccess', 'createDealerFail');
}
createDealer (data) {
const that = this;
that.dispatch();
axios.post(`${API_ENDPOINT}/create-dealer/create-dealer`, data)
.then(function success (data) {
that.actions.createDealerSuccess({data});
})
}
};
store
#createStore(flux)
class CreateDealersStore {
constructor () {
this.state = {
newDealerData : null,
};
}
#bind(CreateDealersActions.createDealerSuccess)
createDealerSuccess (data) {
this.setState({
newDealerData : data.response.config.data,
});
}
}
the Dealers component is within a tab named management, which is this one:
const menuItems = [
{ route : 'dealers', text : 'Dealers' },
{ route : 'game-info', text : 'Game Info' },
{ route : 'player-info', text : 'Players Info' },
{ route : 'money', text : 'Money' }
];
export default class Management extends React.Component {
static propTypes = {
getActivePage : React.PropTypes.func,
menuItems : React.PropTypes.arrayOf(React.PropTypes.object),
}
static contextTypes = {
router : React.PropTypes.func,
}
render () {
return (
<div>
<TabsMainMenu menuItems={menuItems} getActivePage={this._getActivePage} />
<RouteHandler />
</div>
);
}
_getActivePage = () => {
for (const i in menuItems) {
if (this.context.router.isActive(menuItems[i].route)) return parseInt(i, 10);
}
}
}