You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Here's my solution to making a table with editable rows. It uses brython to run python code in the browser, instead of messing with javascript and quasar / vue slots.
Problem
Some notes on the problem.
We want a button to edit the data in each row of table
nicegui doesn't provide this feature
nicegui doesn't let us edit table rows or cells directly. ui.table objects only allow manipulating the top-level table.
nicegui makes it easy to put edit buttons elsewhere on the page. we just can't place them inside of table objects.
changing rows or cells inside the table object requires internal knowledge of vue / quasar and slots.
Approach
Instead of putting edit buttons directly inside the table, we can break the problem into 2 parts:
Put edit buttons anywhere on the page.
Move edit buttons next to corresponding row.
This approach avoids any knowledge of slots or js or vue / quasar. Part 1 is trivial with nicegui. For part 2, we'll use brython to reposition buttons dynamically.
Solution
Here's a screenshot of the end result. The edit drawer on right isn't fully implemented. I just put a drawer showing the item, you can replace with item edit fields as needed.
Just put them in same directory and run python edit-table.py.
Nicegui page
I'll explain the solution in snippets. Let's start with our nicegui code.
First our page function. It's very straightforward. Make random table data, set columns, build table, add edit buttons.
Things to note:
every data item needs a unique identifier. this examples uses uid field.
we put class editable on first table cell in each row, so brython can find them all.
we put class uid-XXXX on first table cell so brython can find the uid for that row.
edit buttons for each data item pop up a right drawer for editing, per OP's request. for this example, we just display the data and close the drawer. you can add actual editing code as you like.
---------- Show code ----------
@ui.page ('/')defmainpage () :
shared_headers ()
# --- make random datadata= [
{
'uid' : random.randint (1000, 10*1000) ,
'file' : ''.join (random.choices (string.ascii_lowercase, k=10)) ,
'size' : random.randint (2**8, 2**14) ,
'mtime' : random.randint (14e8 , 17e8) , # timestamp in seconds'long stuff' : ''.join (random.choices (string.ascii_lowercase, k=50)) ,
}
forxinrange (50)
]
# --- make column settingscols= []
fields=list (data [0])
forfieldinfields :
cols.append ({
'name' : field ,
'label' : field.title () ,
'field' : field ,
'align' : 'left' ,
})
# add uid to class of first cellcols [0] [':classes'] ='row => "editable uid-" + row.uid'# --- make tableprint (f'{data [0] =}')
print ('cols = '+json.dumps (cols, indent='\t'))
table=ui.table (
columns=cols ,
rows=data ,
pagination= { 'rowsPerPage' : 20 } ,
).props ('id=mytable')
# --- make edit buttons# nicegui / quasar dont let us manipulate table rows directly, because reasons :/# instead we'll add edit buttons here and re-position laterdefedit_row (item) :
drawer=ui.right_drawer ()
withdrawer :
# put whatever edit fields you want hereui.label (str (item))
save=ui.button ('save')
save.on ('click', lambda : drawer.toggle ())
foritemindata :
editbutton=ui.button (icon='edit').classes ('rowedit')
editbutton.props (f'id=button-{item ["uid"] }')
# set default arg on lambda or will always use last item in dataeditbutton.on ('click', lambdaitem=item : edit_row (item))
Loading Brython
Now some plumbing. Our nicegui page needs a way to include and launch brython code. We do that in the shared_headers () call, which is shared with all our nicegui pages. shared_headers adds scripts and stylesheets to document head, so we need to call it before building the page.
Things to note:
first we have a js code snippet to launch brython. I normally keep this in a separate file, but for simplicity included it inline.
we also load 2 js files for brython itself: brython.min.js for the interpreter and brython.stdlib.js for python stdlib. stdlib file is optional, we don't actually use it in this example. See more here.
the brython script for this example is in the file rowedit.py, which is added to page head with type "text/python". brython will detect and execute this script.
Here's the meat of our approach. Our brython script scans the page for table cells and moves the corresponding button next to them. Let's start with the place_button function that takes a table cell as arg, finds corresponding button, calculates new position, and places it.
---------- Show code ----------
gap=10# leave small space between button and tabledefplace_button (node) :
'place edit button next to node'log (f'place button : node = {str (node) }')
uid= [ x.split ('-') [-1] forxinnode.classListifx.startswith ('uid-') ] [0]
button=doc.querySelector (f'#button-{uid}')
log (f'found uid = {uid}')
# make button visiblebutton.classList.remove ('hide')
coords=node.getBoundingClientRect ()
width=button.getBoundingClientRect ().width# get doc origin coords to translate from boundingrect to absolute css# Bounding Rect coords are positive / negative based on current view area# while css coords are always positive starting at 0,0 in top left of pagedocpos=doc.body.getBoundingClientRect ()
doctop=docpos.topdocleft=docpos.leftbutton.style.left=f'{coords.left-docleft-width-gap}px'button.style.top=f'{coords.top-doctop}px'button.style.position='absolute'return
Next we have a function scan_rows to scan the doc for all visible table rows and call place_button on each.
---------- Show code ----------
defscan_rows () :
'scan doc for visible rows and place edit buttons next to them'# hide all edit buttons on pagebuttons=doc.querySelectorAll (f'.rowedit')
forbuttoninbuttons :
button.classList.add ('hide')
# get visible table cellscells=doc.querySelectorAll (f'td.editable') # cells with editable classforcellincells :
try :
place_button (cell)
exceptExceptionase :
warn (f'place button failed : {cell} : {e}')
Finally, we need a small piece of code to update the buttons when rows change (ie when user clicks pagination controls to view next / previous page of data). We'll make a mutation observer function watch_rows to listen for change in the DOM and call scan_rows to update.
Why I like this approach better than vue / quasar / slots approach:
pure python. the only js is a small stub once per page to start brython
code separation. my code doesn't need to know anything about quasar / vue internals and slots, which may change in future. my code just uses standard DOM methds and CSS positioning.
broadly applicable to a wide range of problems. quasar slots only help with tables, and maybe a few other types of controls. whereas brython can create almost any type of functionality.
brython has access to DOM and can do things nicegui can't. Some issues are easier to handle in the browser / frontend.
no round trip communication. features implemented in brython happen locally, no back and forth with server. not really an issue in this example, but for other uses it can help.
it's easy to understand. less than 40 lines of brython code, ignoring comments, blank lines, and boilerplate. plus a few extra lines in nicegui code to launch brython, which I already need anyway for other nicegui pages with brython scripts.
The brython approach does have a few differences that some might consider drawbacks (they're strengths to me):
needs some basic knowledge of DOM and CSS. It's not hard to pickup though, and very useful to have.
adds a new dependency on brython. it's stable and been around quite awhile, with good docs. but it's another item in the stack.
if you need to maintain state between brython and nicegui, then it takes a bit more work. brython works best in situations like this one where frontend changes (edit button placement) don't affect backend data.
That's off the top of my head. If you want more info, check out the brython docs, in particular:
There are many ways to possibly improve the code. A few that come to mind:
you could do more checks in mutation observer watch_rows to only act on changes which affect the table, instead of reacting to every change on the page. I didn't bother because I wanted to keep the code clean, if a bit inefficient. If you update other parts of the page often, you'll call scan_rows a lot unnecessarily.
if you really need to be efficient, you can catch changes to the table and debounce calls to scan_rows.
Instead of just positioning the edit buttons with css, you might use brython to actually move the button node inside the table row it belongs to. I didn't try it, could potentially interfere with nicegui's view of the page.
querySelectorAll is a bit inefficient. You could try giving table cells (or rows) an id instead of classes for scan_rows to locate them. I don't know how hard that would be. Manipulating table cells / rows in nicegui is difficult.
you should probably download brython files and load from your own server, instead of using jsdelivr.net which may be unreachable or slower than local delivery.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
LOCAL COPY
Here's my solution to making a table with editable rows. It uses brython to run python code in the browser, instead of messing with javascript and quasar / vue slots.
Problem
Some notes on the problem.
ui.tableobjects only allow manipulating the top-level table.Approach
Instead of putting edit buttons directly inside the table, we can break the problem into 2 parts:
This approach avoids any knowledge of slots or js or vue / quasar. Part 1 is trivial with nicegui. For part 2, we'll use brython to reposition buttons dynamically.
Solution
Here's a screenshot of the end result. The edit drawer on right isn't fully implemented. I just put a drawer showing the item, you can replace with item edit fields as needed.

Entire solution is available in these 2 files:
Just put them in same directory and run
python edit-table.py.Nicegui page
I'll explain the solution in snippets. Let's start with our nicegui code.
First our page function. It's very straightforward. Make random table data, set columns, build table, add edit buttons.
Things to note:
uidfield.editableon first table cell in each row, so brython can find them all.uid-XXXXon first table cell so brython can find the uid for that row.---------- Show code ----------
Loading Brython
Now some plumbing. Our nicegui page needs a way to include and launch brython code. We do that in the
shared_headers ()call, which is shared with all our nicegui pages.shared_headersadds scripts and stylesheets to document head, so we need to call it before building the page.Things to note:
rowedit.py, which is added to page head with type "text/python". brython will detect and execute this script.---------- Show code ----------
Brython Script
Here's the meat of our approach. Our brython script scans the page for table cells and moves the corresponding button next to them. Let's start with the
place_buttonfunction that takes a table cell as arg, finds corresponding button, calculates new position, and places it.---------- Show code ----------
Next we have a function
scan_rowsto scan the doc for all visible table rows and callplace_buttonon each.---------- Show code ----------
Finally, we need a small piece of code to update the buttons when rows change (ie when user clicks pagination controls to view next / previous page of data). We'll make a mutation observer function
watch_rowsto listen for change in the DOM and callscan_rowsto update.---------- Show code ----------
Features
Why I like this approach better than vue / quasar / slots approach:
The brython approach does have a few differences that some might consider drawbacks (they're strengths to me):
That's off the top of my head. If you want more info, check out the brython docs, in particular:
diffs due to limitations of browser environment)
Possible Improvements
There are many ways to possibly improve the code. A few that come to mind:
watch_rowsto only act on changes which affect the table, instead of reacting to every change on the page. I didn't bother because I wanted to keep the code clean, if a bit inefficient. If you update other parts of the page often, you'll callscan_rowsa lot unnecessarily.scan_rows.querySelectorAllis a bit inefficient. You could try giving table cells (or rows) an id instead of classes forscan_rowsto locate them. I don't know how hard that would be. Manipulating table cells / rows in nicegui is difficult.Files
Entire solution is available in these 2 files:
Just put them in same directory and run
python edit-table.py.Beta Was this translation helpful? Give feedback.
All reactions