Webix offers several ways for editing tabular data of various types. Most common practices to do it is via built-in Datatable editors or with the help of a standalone Webix Form. But the question remains – how to put it all together to deliver good UI and UX to users?
Editing with inline editors
Let’s imagine we have a table with some data. To provide a better user experience we make it editable so that users can make changes to the data right in the table.
Everything seems to work fine. However, this approach is not that efficient because every time a user saves a value in the editor the Datatable sends it to the server. To overcome this peculiarity, we can edit the entire row with the dedicated editRow method. We can define an icon in the last column that will trigger this method upon clicking on it:
view:"datatable",
editable: true,
editaction: "custom",
columns:[
// … data columns
// edit icon
{template: `<span class='webix_icon wxi-dots'></span>`}
]
onClick: {
"wxi-dots": function(ev, id) {
this.editRow(id);
}
}
}
You probably noticed that after selecting an option from Combobox, all the editors got closed. It happens because Webix finishes all the current editings when a user selects an option from Combobox. The same goes for clicking outside a row being edited – the row gets closed. But what if we need to start and end editing strictly on clicking a particular control?
Editing with Webix Form
A simple and straightforward solution is to implement a modal window with a form inside. Right like in the demo below:
Editing now works as expected and looks not so bad. However, we can take a step forward and deliver even better user experience by sticking this form to a row being edited.
Combining form and inline editing
We can combine form and inline editing. As far as the form is going to be inside Webix Window we can tune its appearance so that it won’t differ from the Datatable built-in editor.
That’s how it looks:
Exactly what we need!
What to pay attention to
There are some details you should pay attention to while implementing this solution.
Grid
You should specify a template for the last grid column to switch its icon depending on the current editing status: “wxi-dots” if editing is not active and “wxi-check” if editing is active.
view:"datatable",
id:"grid",
columns:[
// template for icon
{width:55, css:"edit", template:function(obj){
return `<span class='webix_icon wxi-${obj.edit?"check":"dots"}'></span>`;
}}
],
// other properties
}
The edit property of a data item represents its status and gets switched by the dedicated editStart and editEnd functions described further.
Window and Form
When styling the window make sure to hide everything that makes it a window. You should disable its header and hide the borders. Make sure to put the window inside the grid container to provide its correct positioning during scroll.
view:"window",
id:"editor",
height: 50,
container:$$("grid").$view,
head:false,
borderless:true,
// … other properties
}
The height of the form should coincide with the row height (in our case it’s 50px) and the width of the controls should be the same as the column width. Specify the form controls depending on the type of data they are used for. If some fields are not supposed for editing use Webix labels as it is shown below:
body:{
view:"form",
padding:0, height:49, margin:0,
cols:[
//labels for non-editable fields
{view:"label", css:"mylabel", width:40, name:"rank"},
{view:"combo", width:100, name:"catId", options:cats},
{view:"text", width:100, name:"votes"},
{view:"datepicker", width:120, name:"start", editable:true}
}
Editing lifecycle
Start editing
In our case editing fires upon a button click (the “wxi-dots” icon). The starter function retrieves the values of the active row and sets them into the form. When editing is active the “wxi-dots” icon is replaced with the “wxi-check”.
//…
const vals = grid.getItem(itemId);
form.setValues(vals);
// editing is active
vals.edit = true;
grid.refresh(itemId);
}
It is also important to place the window right above the row so you can make use of its show method. Note that the “x” and “y” properties are calculated with regard to the table offset to ensure correct positioning in any layout.
const offset = webix.html.offset($$("grid").$view);
const xOffset = offset.x;
const yOffset = offset.y;
// ...
function showForm(id){
//row position
const node = grid.getItemNode(id);
if(node){
const pos = webix.html.offset(node);
editor.show({x:pos.x-xOffset, y:pos.y-yOffset-1});
}
// other properties
}
The showForm function is also called after every scroll. It is done to stick the editor window to its row while scrolling.
view:"datatable",
on:{
onAfterScroll:function(){
if(itemId) showForm(itemId);
}
},
// … other settings
}
End editing
When you click on the “wxi-check” icon the form validates its values and saves them if they match the validation rules.
if(form.validate()){
const vals = form.getValues();
// editing is finished
vals.edit = false;
if(form.isDirty())
grid.updateItem(itemId, vals);
else {
grid.getItem(itemId).edit = false;
grid.refresh(itemId);
}
// ...
}
}
Note that you can check whether the form has been changed with the help of the isDirty method. If it is the case you should call the updateItem method to render new values in the grid and send them to the server. If there were no changes – just change the icon back to the “wxi-dots” with the refresh method. That’s it.
Solution for horizontal scroll
If you want to implement a horizontal scroll make sure to wrap the table in scrollView with the scroll property set to “X” to position and render the editing window correctly.
view:"datatable",
id:"grid",
rowHeight:50,
autowidth:true,
// other properties
}};
You should also take into account how much the user has scrolled the table. You can retrieve the current scroll position with the help of the getScrollState method and then position the window based on that. In the example below the scroll position is available as state.x:
const node = grid.getItemNode(id);
const pos = webix.html.offset(node);
const state = scrolls.getScrollState();
editor.show({
x:pos.x-xOffset+state.x,
y:pos.y-yOffset
});
}
What’s next
How would you solve this task? Share your thoughts about this solution and feel free to propose your own ideas in the comments section below.