
This is a Servoy Tutorial on visibleDataprovider and enabledDataprovider, the two properties that shipped in Servoy 2025.06 and quietly changed how I write form logic. For as long as I can remember, deciding whether a button shows or a field is enabled has meant writing imperative code in event handlers: a line in onShow, another in onRecordSelection, maybe a third buried in a validation handler. The 2025.06 release lets me stop doing that. Component visibility and enabled state become a data binding, the same way a field value is a data binding, and the platform keeps them up to date for me.
This tutorial leans on the Typed Solution Model tutorial (#20), where JSPermission shows up, for the role-based example. If you have ever maintained a form where the show/hide logic was spread across three event handlers and you could never quite remember which one fired first, this one is for you.
The Problem: Show/Hide Logic Scattered Across Handlers
Let’s imagine you are maintaining a CRM order form. There is an approve button that should only appear for users with the approver permission, and only when the selected order is still pending. There is a price field that should be disabled once the order is locked. Where does all that logic live today?
If you wrote this form before 2025.06, the answer is “in several places, and good luck remembering all of them.” Here is the kind of thing I mean:
/** * Update component visibility and enabled state for the current record. * @author Gary Dotzlaw * @since 2026-06-01 * @private */function _refreshComponentStates() { try { /**@type {JSRecord<db:/myserver/crm_order>}*/ const rOrder = foundset.getSelectedRecord();
/**@type {Boolean}*/ const bIsPending = rOrder !== null && rOrder.order_status === 'pending';
elements.btn_approve.visible = security.hasPermission(JSPermission.Approvers) && bIsPending;
elements.fld_price.enabled = rOrder !== null && rOrder.is_locked !== 1; } catch (oError) { application.output( '_refreshComponentStates: ' + oError.message, LOGGINGLEVEL.ERROR ); }}That helper is not the problem. The problem is everywhere you have to call it. You call it in onShow, because the form might open on a record that is already pending or locked. You call it in onRecordSelection, because the moment the user clicks a different row the button has to reevaluate. You call it after any save, and you call it in the data-change handler on order_status, because editing the status inline has to flip the button right away.
Miss any one of those call sites and you get a stale UI. The classic bug is the approve button that is correct on form open and after a record selection, but goes stale when a user edits the status inline from pending to approved, because nobody wired _refreshComponentStates() into the data-change event. The logic was right. The wiring was incomplete. That is the entire failure mode of imperative show/hide code, and it shows up on a Monday morning when a user approves something that was already approved.
Are you going to keep hunting for every event handler that needs to re-call the refresh helper every time you add a new condition? No, of course not. There is a better way now.
The New Properties: visibleDataprovider and enabledDataprovider
In Servoy 2025.06, every component that has a visible property now also has a visibleDataprovider property, and every component that has an enabled property now also has an enabledDataprovider. The release notes put it plainly: “All components with existing visible and enabled properties now have corresponding data provider properties: visibleDataprovider, enabledDataprovider” [1].
The important word is dataprovider. These are not brand-new property types invented for the spec file. They are dataprovider-typed slots auto-added next to the existing visible and enabled properties [4]. Anything a dataprovider slot accepts works here: a column on the form’s table, a form variable, a global variable, or a calculation [3]. Whatever that dataprovider returns is treated as the on/off state for the component.
There is one rule you have to keep in your head, and the release notes are precise about it. The final state of the component is the combination of the static property and the dataprovider’s returned value. From the release notes: “A component’s visibility or enabled state is now data-driven, with the final state determined by combining the standard property and the data provider’s returned value” [1]. The documented word is combining, and in practice that combination behaves as a logical AND. If the static visible property is true and the visibleDataprovider returns true, the component shows. If either one is false, it hides. So if you bind a visibleDataprovider and the component refuses to appear, the first thing to check is that the plain visible property has not been left at false in the form editor.
The stated purpose, again from the release notes, is “dynamic component states managed by calculations or database values, removing the need for manual toggling through scripts” [1]. That last clause is the whole point: the manual toggling is exactly the _refreshComponentStates() mess from the previous section. I am going to lead with visibleDataprovider, the path I trust the most, and cover enabledDataprovider after, with one honest caveat.
Calculations: Computing Visibility From Record State
The reactive piece, the thing that actually removes the onShow boilerplate, is the calculation. A calculation in Servoy is a function defined on a table that returns a value computed from the record. Here is the approve-button condition rewritten as a calculation that returns a boolean (or, more precisely, a truthy or falsy value):
/** * Whether the approve button should be visible for this order. * Returns a truthy value when the order is pending. Bind this calculation * to btn_approve.visibleDataprovider. * @author Gary Dotzlaw * @since 2026-06-01 * @public * * @return {Boolean} true when the order is pending */function canShowApprove() { return order_status === 'pending';}That is the entire calculation. No event handler. No call site to remember. To use it, you open btn_approve in the form editor and set its visibleDataprovider to canShowApprove. From that point on, the button shows exactly when the order is pending, and it updates on its own when the status changes.
The reason it updates on its own is the calculation evaluation model. A calculation runs in the scope of an individual record, so every other column on that record is directly available by name, which is why order_status appears bare inside canShowApprove() with no foundset.getSelectedRecord() ceremony [2]. More importantly, an unstored calculation is recomputed whenever its inputs change [2]. Bind it to visibleDataprovider and the platform recomputes it the moment order_status changes, then shows or hides the button to match. That is the onRecordSelection call, the data-change call, and the post-save call, all collapsed into one declaration you never have to wire by hand.
Put another way: the three handlers from the problem section, the onShow, the onRecordSelection, and the onDataChangeStatus calls that each re-ran _refreshComponentStates(), all collapse into one calculation bound once in the form editor.
// AFTER (2025.06 plus): one calculation, bound once in the form editor.// btn_approve.visibleDataprovider = canShowApprovefunction canShowApprove() { return order_status === 'pending';}The condition lives in one place, the calculation, and the binding lives in one place, the component property. That is the elegant, maintainable, and extendable version of the same behaviour.
One note on stored versus unstored calculations, because it matters here. An unstored calculation is the one you want for reactive UI, because it reevaluates live as the user edits the record [2]. A stored calculation persists its value to a column and recomputes on save, which suits an input that is expensive to compute or needs to be queryable, but it does not update mid-edit. For a button that should react to an inline status edit, use an unstored calculation.
Global Variables: App-Wide Toggles Without a Calculation
Not every visibility rule is per-record. Sometimes you want an app-wide switch: a feature flag, a “show admin tools” toggle, a beta-features gate that one value controls on every form in the solution. Globals are in calculation scope [2], so a calculation can read them, but for the simplest case you can skip the calculation entirely.
A global variable is itself a dataprovider. That means you can bind visibleDataprovider directly to a global boolean with no calculation in between:
// In a global scope, for example scopes globals:/**@type {Boolean}*/var bShowAdminTools = false;Bind btn_admin.visibleDataprovider directly to globals.bShowAdminTools in the form editor, and the admin button on every form that uses this binding tracks that one variable. Flip the global in code, for example when an administrator logs in, and every bound component reacts:
/** * Enable the app-wide admin tools toggle for the current session. * @author Gary Dotzlaw * @since 2026-06-01 * @public */function enableAdminTools() { try { globals.bShowAdminTools = security.hasPermission(JSPermission.Administrators); } catch (oError) { application.output( 'enableAdminTools: ' + oError.message, LOGGINGLEVEL.ERROR ); }}Set that global once at login, and every component bound to it across the solution is correct from that point forward. No per-form onShow wiring, no broadcast event to fan out a visibility change. The binding is the broadcast. That is pretty sweet for feature flags, where the alternative used to be a loop over open forms poking elements.x.visible one component at a time.
If the app-wide rule is more than a single boolean, for example “show this for administrators OR when beta mode is on,” reach for a calculation that reads the globals rather than expressing the logic in the dataprovider slot directly. The calculation has the globals in scope, can combine them however you like, and still reevaluates reactively when its global inputs change.
A Realistic Scenario: Role-Based UI for a CRM Form
Let’s pull the pieces together. A CRM order form where the approve button is gated on both a permission and the record state, and where the gate lives in one calculation rather than smeared across event handlers.
The permission side uses JSPermission, the typed permission enum from the typed solution model tutorial. The wrinkle worth knowing is that a calculation runs in record scope, and security.hasPermission() is available there because the security API is global. So the whole condition fits in a single calculation:
/** * Whether the current user may see the approve button on this order. * True only when the user holds the Approvers permission AND the order * is still pending. Bind to btn_approve.visibleDataprovider. * @author Gary Dotzlaw * @since 2026-06-01 * @public * * @return {Boolean} true when an approver views a pending order */function canShowApprove() { /**@type {Boolean}*/ const bIsApprover = security.hasPermission(JSPermission.Approvers);
/**@type {Boolean}*/ const bIsPending = order_status === 'pending';
return bIsApprover && bIsPending;}Bind btn_approve.visibleDataprovider to canShowApprove, and the button respects both the role and the record state with no event-handler wiring at all. A non-approver never sees it. An approver sees it only while the order is pending, and it disappears on its own the moment the status moves off pending, because the calculation reevaluates when order_status changes [2].
This is also where the typed permission enum earns its keep. Because the calculation uses JSPermission.Approvers rather than the string 'Approvers', the editor warns at design time on a typo. A security gate that silently fails because somebody misspelled a permission string is exactly the kind of bug you do not want hiding inside a visibility calculation.
One caution on related records. A calculation can reach related records, because relations are in calculation scope [2], so a calculation that checks order_to_customer.is_vip works fine. What I do not do is bind a related column directly into the visibleDataprovider slot. If the visibility depends on a related record, route it through a calculation and let the calculation do the relation traversal. The calculation is the supported, reactive path for anything beyond a plain column or global.
enabledDataprovider: Same Idea, One Honest Caveat
Everything above works the same way for enabledDataprovider. If you want the price field disabled once an order is locked, you write a calculation:
/** * Whether the price field should be editable for this order. * False once the order is locked. Bind to fld_price.enabledDataprovider. * @author Gary Dotzlaw * @since 2026-06-01 * @public * * @return {Boolean} true when the order is not locked */function canEditPrice() { return is_locked !== 1;}Bind fld_price.enabledDataprovider to canEditPrice and the field enables and disables itself as the lock flag flips. The combination rule is the same: the field is editable only when both the static enabled property and the calculation return true.
Here is the honest caveat I promised. visibleDataprovider is the path I have tested most and trust most. In the official 2025.6.0 forum thread, a real user confirmed visibleDataprovider working at runtime on a button, while reporting that enabledDataprovider did not initially take effect on that same button [5]. That is one data point from early adoption, not a documented limitation, and it may be component-specific or already resolved on your point release. The practical takeaway is simple: lead with visibleDataprovider, and when you bind enabledDataprovider, verify the behaviour on your specific component and Servoy version before relying on it. Do not assume parity across every component just because the property exists.
When to Stay Imperative
Declarative data binding wins when the rule is “compute a boolean from state.” It is not the answer to everything, and reaching for it in the wrong place makes code worse. Keep the imperative scripting when:
- The condition has side effects. A calculation must stay pure: compute a value and return it. If deciding visibility means logging an audit entry, loading related data, or calling a service, that work belongs in an event handler, not a calculation.
- The logic is genuinely sequential or multi-step. Some conditions read better as a named method with a few intermediate steps than as a single returned expression. If you are fighting to cram branching logic into one
return, a regular function is clearer. - You are toggling many unrelated properties together. When one event flips a dozen components in concert, a single
onRecordSelectionblock can read more clearly than a dozen separate calculations.
Bottom-line: use the data binding for the common case, and keep the script for the genuinely procedural work. The two coexist on the same form without any conflict.
What Comes Next
visibleDataprovider and enabledDataprovider are one piece of a larger shift in 2025.06 toward declarative form authoring. The next tutorial covers the Events Manager, the other half of that shift, which does for UI event wiring what these dataproviders do for component state: it moves handler registration and deregistration out of onShow and onHide and into a declaration the platform manages for you. The same instinct that made you stop hand-wiring visibility refreshes will make you want to stop hand-wiring event listeners.
Closing
That concludes this Servoy tutorial on visibleDataprovider and enabledDataprovider, the data-driven component properties that landed in Servoy 2025.06. The headline is that component visibility and enabled state are now a binding, the same way a field value is a binding, and the platform keeps them current for you. The scattered onShow, onRecordSelection, and data-change wiring that used to keep a button in sync collapses into one calculation bound once in the form editor. It does not look dramatic in a release-note bullet, but it removes an entire category of stale-UI bug from every form you touch from here forward. I hope you enjoyed it, and I look forward to bringing you more Servoy tutorials in the future.
References
[1] Servoy, “Servoy 2025.06 Release Notes,” Servoy Documentation, 2025. https://docs.servoy.com/release-notes/release-notes/2025.06
[2] Servoy, “Calculations,” Servoy Documentation, 2025. https://docs.servoy.com/guides/develop/application-design/data-modeling/databases/tables/calculations
[3] Servoy, “Scripting Forms,” Servoy Documentation, 2025. https://docs.servoy.com/guides/develop/programming-guide/scripting-the-ui/scripting-forms
[4] Servoy, “Property Types for components and services,” Servoy Documentation, 2025. https://docs.servoy.com/reference/servoy-developer/component_and_service_property_types
[5] Servoy Community, “Servoy 2025.6.0,” Servoy Forum, Jun 2025. https://forum.servoy.com/t/servoy-2025-6-0/24018
Building production AI, or modernizing a legacy system?
That is the kind of work we do at Dotzlaw Consulting. Book a free 20-minute intro call and tell us what you are trying to build, or what is slowing you down.