0.4.0 — Signal binding on fields
Added
- A
Signalcan now hold an empty value, so clearing a field is no longer silent. A signal takes its type from the value you create it with and had no way to say "nothing", so clearing a bound field left the signal on its last value and nothing watching it heard. Passallow_empty=Truewhen the value can also be empty —bs.Signal(date.today(), allow_empty=True)— andclear()empties it, in both directions between field and signal. A signal can also start empty, in which case name its type:bs.Signal(None, allow_empty=True, dtype=date). Empty reads asNone, except where the signal is the widget's own variable, as for a text field or radio group, where it reads as'', and a signal holding aset, as a multi-select toggle group does, which empties to the empty set — a falsiness check covers all three. Signals you already have are unchanged: withoutallow_empty=True,clear()andset(None)raise. Binding one that allows empty to a checkbox, switch, toggle button, slider or progress bar raises, since none of them has an empty state to hold. (#390) Selectgains the validation and addon surface the other fields already had.select.validandselect.error, both bindableSignals; theon_valid,on_invalidandon_validateevents; andinsert_addon,update_addon,remove_addonandaddons.select.signalalso reads back theSignalthe widget was built with, asNumberField,DateFieldandTimeFieldalready allowed. (#465, #458)
Changed
- A keyword a widget does not recognize is now reported instead of ignored.
bs.TextField(bogus=1)used to construct as if nothing had happened, and so did a typo of a real parameter:bs.DataTable(densty="compact")silently used default spacing. Every widget you place in a layout now raisesTypeErrornaming itself and the keyword —TextField() got unexpected keyword argument(s): bogus. The top-level windows (App,AppShell,Workbench,Window) report it through the toolkit they are built on, so their message names that rather than the window. Check this when you upgrade: an application that starts today can fail to start after the upgrade — but only where a keyword was already doing nothing, so you get a message pointing at the typo instead of a setting that never applied. Placement keywords (grow=,horizontal=,row=,column=and the rest) are unaffected, andChart,MenuButton,Picture,StatusBarandToolbarstill pass extra options through to what they wrap. (#472) - A
SelectButtonbound to aSignalnow carries the option's value, not the label shown for it. Wherever a label differed from its value, seeding the signal with a value displayed that raw value and leftselectionempty for good, while selectingThreeput the label'Three'into the signal wherevaluereported'3'. The signal now carries the value in both directions, andbutton.signalreturns theSignalyou passed rather than a separate one tracking it. Check this when you upgrade if you seed aSelectButtonsignal with anything other than one of its option values — a label, or any other string, now raises; seed it with the option's value instead. This reaches buttons built from plain strings, where label and value are the same text. A button with nosignal=, or one whose seed already names an option, is unaffected. Clearing the button in code leaves an ordinary signal untouched; declare itallow_empty=Trueand the clear reaches it. (#461)
Fixed
- A validation field no longer stops validating when a
customrule cannot judge a value. Acustomrule runs a function you supply, and if it raised — comparing a number against text, or meeting an empty field it did not handle — the exception escaped into the event loop and the field silently kept whatever validity it already had. It now reports the value invalid, and the first raise per rule writes one line to stderr naming the exception; setBOOTSTACK_DEBUGfor the traceback. The field shows "Could not check this value (expected: must be over 5)", carrying themessageyou gave the rule as an expectation rather than as a verdict: a function that raised judged nothing, so your message alone can be false of the value — "must be over 5" on a field holding 6. It is shown alone, as the verdict, only when the function returnsFalse. This also coversvalidate(), your own call and the one aFormDialogsubmit makes for you, which returnsFalserather than raising. Check this when you upgrade if acustomrule is attached to an optional field and your function does not handle an empty value: that field now reports invalid where it previously appeared valid. (#467) - A
Signalbound to aTextAreaorCodeEditornow works in both directions, and.signalgives you the signal back. Both widgets take atextsignal=documented as a two-way binding, but only one direction was wired: the widget followed the signal, while typing left the signal on its old value forever..signalreturnedNonein every case, even when a signal was bound. Edits now travel back as you make them, and.signalreturns what you bound (stillNonewhen nothing is). Writing to a bound signal while aTextArea's placeholder was showing also left the widget stuck — the text appeared, butvaluereturned''andon_input/on_changestopped firing for everything typed afterwards. Both behave normally now. Check this when you upgrade if you subscribe to a signal bound to one of these two widgets: that subscriber starts firing on user edits, where before it only heard writes your own code made. Binding a signal holding something other than text —bs.TextArea(textsignal=bs.Signal(123))— now raisesTypeError, the same refusal single-line fields have always given. (#486) - A
Selectbound to aSignalnow tracks the selection, not the text on screen. Seeding the signal with an option's value displayed that raw value instead of the option's label, and setting it later moved the displayed text without moving the selection — so the field showed one option whilevalueandselectionreported the previous one, no change event fired, and it did not correct itself. That second half applied to plain string options too. The signal now carries the option's value in both directions, matchingvalue=and thesignal=onNumberField,DateFieldandTimeField. Two things to check when you upgrade: what aSelectwrites into its signal changes from the option's label to its value, andtextsignal=on aSelectnow raises instead of being silently discarded. Clearing the field leaves an ordinary signal untouched; declare itallow_empty=Trueand the clear reaches it.SelectButtonhad the same defect and is fixed with it. (#458) - A validation rule on a
Selectnow has somewhere to report.Selectacceptedadd_validation_rule()and ran the rule, but exposed novalidorerrorto read the outcome from and emitted novalid/invalidevents, so a failing rule could reach neither your code nor the screen. It now carries the field family's full validation surface (seeAdded), sobs.Label(textsignal=select.error)works andon_valid/on_invalidfire as they do onTextField. The rules you attach today run exactly as before — this only gives their outcome somewhere to go. Check this when you upgrade:Selectnow shares the family'sadd_validation_rule(), so passing anything other than a rule-type string raisesTypeErrorinstead of being accepted and silently ignored. (#465) - A modal
bs.Windownow hands the grab back to whatever was modal underneath it. Opening a modal window from inside another modal — an "Advanced…" button on a dialog, say — left the dialog underneath on screen and still blocking the code that opened it, but no longer modal: the user could click straight past it into the main window. A closing modal window now returns the grab to its previous holder, and as the same kind, so an application-modal window underneath stays application-modal rather than quietly narrowing. This was the same defect fixed for dialogs in0.3.1, on the one path that fix did not cover; a window with no modal opener, and any non-modal window, are unaffected. (#444) - A
SelectButtonno longer reports the same selection twice. Every change firedon_changetwo times, so a handler that saves a record, sends a request or increments a counter did all of it twice for one selection. This applied to everySelectButton, whichever way the options were written, and both to a selection made from the menu and to one set in code. A selection is now announced exactly once; the event, its payload and its timing are unchanged. (#476) - A
TimeFieldbound to aSignalno longer reports a change on startup. Seeding the field from its signal announced a change event while the field was being built, for a time nobody had picked — so an application reacting to a time change ran that reaction once at startup. A handler registered on the line after the constructor still received it, because the event waited in the queue until the application began running.TimeField(signal=…)is now silent, matchingTimeField(value=…)and the already-silentNumberFieldandDateField. Every change after construction is announced exactly as before. (#459) DataTable'scontext_menusoption now works. It was documented and shown in the widget guide but had no effect: every table offered both right-click menus whatever you asked for, socontext_menus="none"still opened the column-header menu and the row menu. The option is now honored as documented —'all'(the default),'headers'or'rows'for one menu only,'none'for neither — and a misspelled value is reported rather than ignored.on_row_right_clickfires no matter which menus you turn off: the option chooses which menus the table offers, not whether a right-click reaches your code. (#456).signalno longer looks like it might hand back nothing on seven widgets.TextField,PasswordField,PathField,SpinnerField,Checkbox,SwitchandToggleButtondocumented.signalas returning aSignalorNone, and none of them can returnNone— they create a signal on first read whether or not you bound one, so a type checker made you guard a case that cannot happen. The seven now say what they do. Widgets that really can returnNoneare unchanged and still say so:TextArea,CodeEditor,NumberField,DateField,TimeField,SelectandSelectButton. Behavior is identical either way. (#460)
What's Changed
- Run the test suite on every push and pull request (#380) by @israel-dryer in #451
- fix(datatable): honor the documented context_menus option (#456) by @israel-dryer in #457
- fix(select): bind signal= to the option's value, not the entry text by @israel-dryer in #462
- docs(audit): measure the wrapper/internal parameter delta (#463) by @israel-dryer in #464
- test(harness): drain the event queue in the scene reset (#449) by @israel-dryer in #470
- fix(select): restore the field family's validation surface (#465) by @israel-dryer in #471
- Reject an unrecognized keyword at the layout seam (#472) by @israel-dryer in #473
- fix: bind SelectButton's signal to the option's value (#461); silence TimeField's seed emit (#459) by @israel-dryer in #475
- fix: SelectButton announces a selection once, not twice (#476) by @israel-dryer in #478
- Let a Signal hold an empty value (#390) by @israel-dryer in #480
- fix(window): restore the modal grab a bs.Window displaced (#444) by @israel-dryer in #485
- fix(widgets): .signal never returns None on seven widgets (#460) by @israel-dryer in #487
- fix(textarea): bind textsignal in both directions and return it from .signal (#486) by @israel-dryer in #489
- publisher: remove legacy class and all references. by @israel-dryer in #492
- colorutils: migrate callers of legacy colorutils to use style utilities by @israel-dryer in #493
- Chore/collapse capabilities veneer by @israel-dryer in #494
- fix(validation): a custom rule that raises no longer leaves the field stale, and no longer reports a false message (#467) by @israel-dryer in #501
Full Changelog: v0.3.2...v0.4.0