Primeng: Datatable - Editable - events incorrectly raised on keydown

Created on 19 Jun 2017  路  36Comments  路  Source: primefaces/primeng

I'm submitting a ... (check one with "x")

[x] bug report => Search github for a similar issue or PR before submitting
[ ] feature request => Please check if request is not on the roadmap already https://github.com/primefaces/primeng/wiki/Roadmap
[ ] support request => Please do not submit support request here, instead see http://forum.primefaces.org/viewforum.php?f=35

Plunkr Case (Bug Reports)

http://plnkr.co/edit/mVdxxoHepxX8bpVqdvLV?p=preview

Current behavior
Editable table events (OnEdit/OnEditComplete/OnEditCancel) are raised on keydown. This is the wrong event to use for change detection and form submission.

Expected behavior

  • The editor should monitor input events for OnEdit
  • The editor should monitor change (or blur) events for OnEditComplete and OnEditCancel
  • The editor should ignore keydown events

    • because keypresses do not necessarily cause changes

    • because mouse events can cause changes that keydown doesn't catch

Minimal reproduction of the problem with instructions
See Plunkr test cases.

onEditComplete not raised on clicks

  1. Click any cell and change the current value in the input field
  2. Click anywhere outside the cell to conclude editing
  • Expected behavior: onEditComplete is raised
  • Actual behavior: no event is raised

onEditCancel not raised on clicks

  1. Click any cell but do not change the current value
  2. Click anywhere outside the cell to cancel editing
  • Expected behavior: onEditCancel is raised
  • Actual behavior: no event is raised

onEdit raised on meta keys

  1. Click any cell but do not change the current value
  2. Press and hold down a meta key
  • Shift or Ctrl/Command or even Caps Lock/Num Lock/Scroll Lock
  • Expected behavior: no event is raised
  • Actual behavior: onEdit is raised

What is the motivation / use case for changing the behavior?

Please tell us about your environment:

  • Angular version: 4.1.3

  • PrimeNG version: 4.0.3

  • Browser: all

  • Language: all

  • Node (for AoT issues): node --version = 8.1.2

enhancement

Most helpful comment

@cagataycivici here is the directive i implemented to listen for the events inside the custom template of a dataTable cell:

@Directive({
  selector: "[dataTableEditor]"
})
export class DataTableEditorAttribute {
  @Input() dataTableEditor: any;

  @HostListener('keydown', ['$event'])
  public onCellEditorKeydown(event: Event) {
    this.forwardEvent('onCellEditorKeydown', event);
  }

  @HostListener('input', ['$event'])
  public onCellEditorInput(event: Event) {
    this.forwardEvent('onCellEditorInput', event);
  }

  @HostListener('blur', ['$event'])
  public onCellEditorBlur(event: Event) {
    this.forwardEvent('onCellEditorBlur', event);
  }

  @HostListener('change', ['$event'])
  public onCellEditorChange(event: Event) {
    this.forwardEvent('onCellEditorChange', event);
  }

  private forwardEvent(eventName: string, event: Event) {
    this.dataTableEditor.dt[eventName](event, this.dataTableEditor.col, this.dataTableEditor.rowData, this.dataTableEditor.rowIndex);
  }
}

And it is used like this:

<p-column field="model.name" header="Program Name" [editable]="true">
  <ng-template let-col let-data="rowData" pTemplate="editor">
    <input type="text" pInputText [(ngModel)]="model.name" [dataTableEditor]="{col:col, rowData:data, dt: dt, rowIndex: index}" />
  </ng-template>
</p-column>

All 36 comments

After trying to implement editing in a DataTable, there seems to be a parallel issue: if you use your own template for the editor, it looks like none of the keypress listeners work. Escape and Enter keys do nothing, and the only way to "stop" editing is to tab away from the cell or click somewhere outside the editor.

Since a custom template seems to be the only way to enforce validation, either the way the listeners work needs to change, or some mechanism to relay events from the custom template to the DataTable needs to be added, or a good bit of documentation needs to be added to explain how a developer should mimic what the DataTable is doing internally to handle these things?

@brian428 You can call the onCellEditorKeydown function in your custom template like so:

<input type="number" class="ui-inputtext ui-widget ui-state-default ui-corner-all"
                                       [(ngModel)]="rowData[col.field]" [ngModelOptions]="{standalone:true}"
                                       (keydown)="dt.onCellEditorKeydown($event, col, rowData, rowIndex)" />

Is dt exposed as a template variable (via let)? Or do we need to declare the DataTable as a template variable ourselves?

You have to declare it, like so:

<p-dataTable #dt [value]="dataRows">...

it is a great suggestion.

in fact, onEdit event should use debounce instead.
the current behaviors of onEdit event block users input, and they may feel it is lagging when they key in.

http://plnkr.co/edit/A8Ms99r7sazUeZS90z7K?p=preview

+1

This is a major problem. My team is trying to solve a problem where data is lost because our users are clicking away from the input field and the onEditComplete event is not being raised. We have to tell them to press enter to save their input.

How do we fix this? We might have to drop primeng data tables altogether because of this.

@gabrielalack Add an event handler for the blur event and call onEditComplete then. See my code snippet above.

Any forecast for resolution of this issue? I'm currently using 4.1.3 and this still seems to be a problem. We already had to remove DataTable from our alpha release because of the IE memory leak issue. Now I'm adding back in for our beta and running into this problem... Major problem. Thanks.

Really needed this functionality to have the proper save mechanism for prime dataTable. We are even using the custom components inside the template Editor for columns of dataTable. So would really like to know how events will behave as part of this solution, and will decide on going with custom components / prime components inside dataTable.
Please let us know the forecast for resolution of this issue.

@wujitouch your suggestion to add a blur event on a cell is neither working with pColumn, nor with ng-template (pTemplate="body"), nor if I add it to the datatable properties. Moreover, I have written a directive to add an event listener for blur event, but it also won't work on the datatable cells.

@a-kolybelnikov
Sorry I didn't paste the code for the blur event. The blur event has to be handled differently than the click event because the DataTable is looking for certain keys pressed to trigger the update (I think it's keyCode 13). What I did was call a function in my component that calls dt.onCellEditorKeydown with a custom object instead of $event. My custom object looks like a key press event object, like this:

  {
    keyCode: 13,
    target: event.target // may have to find the correct target
  }

Take a look at the source of the DataTable code to see what it's doing. This will help you determine what you need to do to trigger it.
https://github.com/primefaces/primeng/blob/master/src/app/components/datatable/datatable.ts

Hope this helps :)

@wujitouch thanks for the explanation, however, I am still having difficulty to understand your implementation. If I get you right, this has nothing to do with the blur event itself. You're working around it with keydown events ... ?

@a-kolybelnikov
Correct, the element in the template would fire my function on blur and then call the onCellEditorKeydown function in the DataTAble. I didn't test this, but something like this:

<input type="number" class="ui-inputtext ui-widget ui-state-default ui-corner-all"
                                   [(ngModel)]="rowData[col.field]" [ngModelOptions]="{standalone:true}"
                                   (blur)="dataTableOnBlur($event, col, rowData, rowIndex, dt)" />

class MyComponent {
    // ...

    dataTableOnBlur(event:any, col:any, rowData:any, dt:any) {
      let evt:any = {
         keyCode: 13, target: event.target
      }
      dt.onCellEditorKeydown(evt, col, rowData, rowIndex);
    }
}

I did test the 'blur' event on the datatable to no success. There is no event emitted at all on blur. It might have something to do with the fact that the "useCapture" parameter of the addEventListener must be set to true (?): https://developer.mozilla.org/en-US/docs/Web/Events/blur

The blur event is on the input element. It should fire. I'll create a plunkr when I get a chance

On Aug 29, 2017, 9:44 AM, at 9:44 AM, Andrey Kolybelnikov notifications@github.com wrote:

I did test the 'blur' event on the datatable to no success. There is no
event emitted at all on blur. It might have something to do with the
fact that the "useCapture" parameter of the addEventListener must be
set to true (?):
https://developer.mozilla.org/en-US/docs/Web/Events/blur

--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
https://github.com/primefaces/primeng/issues/3138#issuecomment-325668336

@a-kolybelnikov

Apparently I had forgotten my workaround from before. I didn't create a custom object and call dt.onCellEditorKeydown. Sorry about that, here's a working plunkr

http://plnkr.co/edit/KMblOG4IeBSY2huOym9z

@victor-ponce
thanks for the plunkr link. I've been testing it, and while a blur event is being emitted, the last two arguments (rowIndex and dt) are throwing 'undefined' in the console. I can grab the event, the column data, the rowData, but I can neither emit the onEditComplete event, nor save the changed model data, because the dt is undefined.
I have tried to just update my model onBlur, without emitting an onEditComplete, but that won't work.

@a-kolybelnikov
I didn't have the rowIndex variable on the column ng-template. I added it and both rowIndex and dt are valid. dt is a template variable on the p-dataTable element.

The plunkr works but if you're testing in you're code make sure you add the template variable dt, doesn't have to be called dt.
http://plnkr.co/edit/KMblOG4IeBSY2huOym9z

@victor-ponce 1. I had to add a row-index variable. 2. I had a typo in my datatable reference (!). It works now, and the data is being passed and stored. Thank you so much.

@victor-ponce
hi, I have another question to the example code in plunkr. My dt object does not have a switchCellToViewMode() function and an error is being thrown on each edit:
ERROR TypeError: this.dataTable.switchCellToviewMode is not a function.
For the time being I have commented it out, but I'm just curious, where does it come from in your code. Thank you for helping with this!

I got it from the source code for onCellEditorKeydown function in the DataTable. You can see it here:
https://github.com/primefaces/primeng/blob/master/src/app/components/datatable/datatable.ts

line 1883

Looks like you have a typo. Check the casing, it should be switchCellToViewMode

@victor-ponce , thanks for the great plunkr example.
it works fine if user click away from the input box.
However, it is not working if user hit the tab key to make the input blur.

you should add this.
(keyup.Tab)="dataTableOnBlur($event, col, rowData, rowIndex, dt)"

@jefftham It works fine for me in Chrome but it might not on other browsers. Thanks for the tip!

all, I have another glitch with this implementation. Now that the data is being save on blur/ key-up, it's not possible to immediately activate input fields in other cells: they get active / inactive on first click, and only on the second click they can be activated, which creates a super weird effect, and breaks the flow. Has anyone else experienced this? Wondering, if it is due to some other settings of the datatable.

I updated my original post under _Expected behavior_ because the wording wasn't very clear.

@a-kolybelnikov If I'm not mistaken you're saying that when one cell has focus (is active) and you want another cell to have focus, it requires two clicks into the other cell? I don't see this behavior in the plnkr. In the plnkr, you only need to click the other cell once. Put your code in a plnkr so we can see it.

@all, I did resolved this using event mechanism from angular. After checking the code of dataTable the hooks weren't emitted at the useful level. Hence for all the custom components meaning drop-down, input text etc I have raised a custom event called valueChange whenever their change event is invoked. Once the value change is there, I will save the entire dataset of a single row. This way i don't need behavior for focus / blur from dataTable.

I've done the changes according to the original post for the built-in editor input. Not sure about custom editors though, since you have full control on the events, you may decide when to call the onEdit* logic or use the local template variable;

<p-dataTable #dt

<p-column field="brand" header="Brand" [editable]="true">
            <ng-template let-col let-car="rowData" pTemplate="editor">
                <p-dropdown ... (keydown)="dt.onCellEditorKeydown($event, col, car)"
                 (input)="dt.onCellEditorInput($event, col, car)"
                 (focus)="dt.onCellEditorFocus($event, col, car)"
                 (blur)="dt.onCellEditorBlur($event, col, car)"></p-dropdown>
            </ng-template>
        </p-column>

Creating a directive to auto bind these events also sound handy like;

<p-dataTable #dt

<p-column field="brand" header="Brand" [editable]="true">
            <ng-template let-col let-car="rowData" pTemplate="editor">
                <p-dropdown ... pCellEditor="dt"></p-dropdown>
            </ng-template>
        </p-column>

Any feedback?

DataTable methods for input/change events is perfect. Just please make sure that the methods can do everything that the built-in editor inputs does.

@victor-ponce I have found the "bug" to be on my side. I had a line in my code with made the datatable re-render on edit save. It all works perfectly now hat I have gotten rid of that. The only thing which does not work, is Enter key event. The binding is gone, although it is there in the built-in editor. I'm looking into this.




[(ngModel)]="rowData[col.field]" [ngModelOptions]="{standalone:true}"
(onchange)="dataTableOnBlur($event, col, rowData, rowIndex, dt)" />


On Change event is not firing . I tried blur but its triggering even though the value not changed. Can you help me on this ?

@sviswanathjcet You probably found your answer by now but the change is event should not be prefixed with "on". It should be (change)="dataTableOnBlur($event, col, rowData, rowIndex, dt)"

@victor-ponce Thank you so much for the reply. I have figured out.But i am facing another issue. The below footer function called even outside the data table mouse click. its called multiple time when edit happened in the datatable. Can you help me on this ?








Any news? Thanks

@cagataycivici here is the directive i implemented to listen for the events inside the custom template of a dataTable cell:

@Directive({
  selector: "[dataTableEditor]"
})
export class DataTableEditorAttribute {
  @Input() dataTableEditor: any;

  @HostListener('keydown', ['$event'])
  public onCellEditorKeydown(event: Event) {
    this.forwardEvent('onCellEditorKeydown', event);
  }

  @HostListener('input', ['$event'])
  public onCellEditorInput(event: Event) {
    this.forwardEvent('onCellEditorInput', event);
  }

  @HostListener('blur', ['$event'])
  public onCellEditorBlur(event: Event) {
    this.forwardEvent('onCellEditorBlur', event);
  }

  @HostListener('change', ['$event'])
  public onCellEditorChange(event: Event) {
    this.forwardEvent('onCellEditorChange', event);
  }

  private forwardEvent(eventName: string, event: Event) {
    this.dataTableEditor.dt[eventName](event, this.dataTableEditor.col, this.dataTableEditor.rowData, this.dataTableEditor.rowIndex);
  }
}

And it is used like this:

<p-column field="model.name" header="Program Name" [editable]="true">
  <ng-template let-col let-data="rowData" pTemplate="editor">
    <input type="text" pInputText [(ngModel)]="model.name" [dataTableEditor]="{col:col, rowData:data, dt: dt, rowIndex: index}" />
  </ng-template>
</p-column>

created a version of @hughanderson4 's dataTable editor for the p-table:

@Directive({
    selector: '[pTableEditor]'
})
export class PTableEditorDirective extends DefaultValueAccessor {
    @Input('pTableEditor') table: any;

    @HostListener('keydown', ['$event'])
    public onCellEditorKeydown(event: Event) {
        this.forwardEvent('onCellEditorKeydown', event);
    }

    @HostListener('input', ['$event'])
    public onCellEditorInput(event: Event) {
        this.forwardEvent('onCellEditorInput', event);
    }

    @HostListener('blur', ['$event'])
    public onCellEditorBlur(event: Event) {
        this.forwardEvent('onEditComplete', event);
    }

    @HostListener('change', ['$event'])
    public onCellEditorChange(event: Event) {
        this.forwardEvent('onCellEditorChange', event);
    }

    private forwardEvent(eventName: string, event: Event) {
        console.log({ eventName, event, table: this.table });
        let emitter: EventEmitter;
        emitter = this.table[eventName];

        if (emitter) {
          console.log({ handler: this.table[eventName] });
          const evt:any = {
            field: this.table.editingCellField,
            data: this.table.editingCellData,
            originalEvent: event
          };
          emitter.emit(evt);
        }
    }
}

The blur event is mapped to the p-table onEditComplete. You can input events however you like by binding them with @HostListener and calling this.forwardEvent with the event on the p-table want to map to.

Usage in html slightly different, due to p-table differences and modification to simplify the binding:

<p-table
    [value]="documentation"
    dataKey="WorkID"
    rowHover="true"
    (onEditComplete)="saveData($event)"
    #dt
>
    <ng-template
        pTemplate="body"
        let-doc
        let-editing="editing"
        let-ri="rowIndex"
    >
        <tr [pEditableRow]="doc" class="tabulator-row ui-selectable-row">
            <td [pEditableColumn]="doc" [pEditableColumnField]="'Hours'">
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText type="text" [(ngModel)]="doc.Hours" [pTableEditor]="dt"/>
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{ doc.Hours }}
                    </ng-template>
                </p-cellEditor>
            </td>
        </tr>
    </ng-template>
</p-table>

I'm using NG8 and PrimeNG8, YMMV

Was this page helpful?
0 / 5 - 0 ratings

Related issues

svideau picture svideau  路  30Comments

cagataycivici picture cagataycivici  路  31Comments

gatapia picture gatapia  路  64Comments

sourdoth picture sourdoth  路  34Comments

JakeSummers picture JakeSummers  路  29Comments