Skip to content
Merged
11 changes: 8 additions & 3 deletions nicegui/nicegui.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,14 @@ def _on_ack(_: str, msg: dict) -> None:
client.outbox.prune_history(msg['next_message_id'])


@sio.on('too_long_message')
def _on_too_long_message(_: str) -> None:
log.warning('Received a too long message from the client.')
@sio.on('log')
def _on_log(_: str, msg: dict) -> None:
{
'debug': log.debug,
'info': log.info,
'warning': log.warning,
'error': log.error,
}[msg['level']](msg['message'])


async def prune_tab_storage(*, force: bool = False) -> None:
Expand Down
30 changes: 28 additions & 2 deletions nicegui/static/nicegui.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ function emitEvent(event_name, ...args) {
getElement(0).$emit(event_name, ...args);
}

function logAndEmit(level, message) {
if (level === "error") {
console.error(message);
} else if (level === "warning") {
console.warn(message);
} else {
console.log(message);
}
window.socket.emit("log", { level, message });
}

function stringifyEventArgs(args, event_args) {
const result = [];
args.forEach((arg, i) => {
Expand Down Expand Up @@ -348,8 +359,9 @@ function createApp(elements, options) {
return function (...args) {
const msg = args[0];
if (typeof msg === "string" && msg.length > MAX_WEBSOCKET_MESSAGE_SIZE) {
console.error(`Payload size ${msg.length} exceeds the maximum allowed limit.`);
args[0] = '42["too_long_message"]';
const errorMessage = `Payload size ${msg.length} exceeds the maximum allowed limit.`;
console.error(errorMessage);
args[0] = `42["log",{"level":"error","message":"${errorMessage}"}]`;
if (window.tooLongMessageTimerId) clearTimeout(window.tooLongMessageTimerId);
const popup = document.getElementById("too_long_message_popup");
popup.ariaHidden = false;
Expand Down Expand Up @@ -399,6 +411,20 @@ function createApp(elements, options) {
.map(([_, element]) => loadDependencies(element, options.prefix, options.version));
await Promise.all(loadPromises);

let eventListenersChanged = false;
for (const [id, element] of Object.entries(msg)) {
if (element === null) continue;
const oldListenerIds = new Set((this.elements[id]?.events || []).map((ev) => ev.listener_id));
if (element.events?.some((e) => !oldListenerIds.has(e.listener_id))) {
delete this.elements[id];
eventListenersChanged = true;
}
}
if (eventListenersChanged) {
logAndEmit("warning", "Event listeners changed after initial definition. Re-rendering affected elements.");
await this.$nextTick();
}

for (const [id, element] of Object.entries(msg)) {
if (element === null) {
delete this.elements[id];
Expand Down
20 changes: 20 additions & 0 deletions tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,23 @@ def page():
screen.click('Checkbox')
screen.wait(0.5)
assert events == [(True, False), (False, True)]


async def test_late_event_registration(screen: Screen):
events = []

@ui.page('/')
async def page():
name = ui.input('Name')
name.on('keydown.a', lambda: events.append('A'))
await ui.context.client.connected()
name.on('keydown.b', lambda: events.append('B'))
ui.label('Ready')

screen.open('/')
screen.should_contain('Ready')
screen.selenium.find_element(By.XPATH, '//*[@aria-label="Name"]').send_keys('ab')
assert events == ['A', 'B']
assert 'Event listeners changed after initial definition. Re-rendering affected elements.' in screen.render_js_logs()
screen.assert_py_logger('WARNING',
'Event listeners changed after initial definition. Re-rendering affected elements.')
3 changes: 3 additions & 0 deletions tests/test_socketio_too_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ def page():

assert events == ['changed'] * 2, 'two more characters are ok'
assert len([log for log in screen.selenium.get_log('browser') if 'Payload size' in log['message']]) == 3
screen.assert_py_logger('ERROR', 'Payload size 999901 exceeds the maximum allowed limit.')
screen.assert_py_logger('ERROR', 'Payload size 999902 exceeds the maximum allowed limit.')
screen.assert_py_logger('ERROR', 'Payload size 999903 exceeds the maximum allowed limit.')