<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/" xmlns:discourse="http://discourse.org/rss/modules/discourse/">
<channel>
<title>Angular on Roque</title>
<link>https://j-roque.com/tags/angular/</link>
<description>Recent content in Angular on Roque</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<managingEditor>joao.roque.94@gmail.com (João Roque)</managingEditor>
<webMaster>joao.roque.94@gmail.com (João Roque)</webMaster>
<copyright>© 2026 João Roque</copyright>
<lastBuildDate>Fri, 24 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://j-roque.com/tags/angular/index.xml" rel="self" type="application/rss+xml"/>
<item>
<title>Understanding UI Pages: How They Really Work</title>
<link>https://j-roque.com/posts/20260724-howuipageswork/</link>
<pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate>
<author>joao.roque.94@gmail.com (João Roque)</author>
<guid>https://j-roque.com/posts/20260724-howuipageswork/</guid>
<description>Anyone can drag a widget onto a UI Page. Far fewer people wire it so it survives contact with 10,000 real records. This is what a UI Page actually is, and the mistakes that show up in nearly every implementation.</description>
<content:encoded>&lt;p&gt;&lt;img src="https://j-roque.com/posts/20260724-howuipageswork/featured.png" alt="Understanding UI Pages: How They Really Work" /&gt;&lt;/p&gt;&lt;p&gt;UI Pages a low code designer to build your MES cockpits.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Overview
&lt;div id="overview" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#overview" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;We will address what a &lt;strong&gt;UI Page&lt;/strong&gt; is made of, how its pieces connect, and through a live build of a real page where implementations quietly go wrong. First, a tour of the vocabulary (Builder, Widgets, Tasks, Properties, Links, Converters, Layouts), followed by a from-scratch build of a two-level grid, and closing with a set of best practices earned the hard way, on real projects.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Four Kinds of UI Page
&lt;div id="the-four-kinds-of-ui-page" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-four-kinds-of-ui-page" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;UI Page&lt;/strong&gt; is a configurable entity, built from widgets, properties, and data sources, that displays information and reacts to user input and system triggers.&lt;/p&gt;
&lt;p&gt;MES supports four shapes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cluster&lt;/strong&gt; — a navigation panel on the left, a detail panel on the right. Think of the Step View or the Material view: click an item on the left, its details render on the right.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Page&lt;/strong&gt; — the most common type. A blank canvas you populate by dragging and dropping elements to build interaction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wizard&lt;/strong&gt; — a sequence of steps that collects and prepares data before handing it off to a single transaction service call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step&lt;/strong&gt; — a custom, reusable unit you build once and drop into any Wizard.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class="relative group"&gt;The Vocabulary of the Builder
&lt;div id="the-vocabulary-of-the-builder" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-vocabulary-of-the-builder" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Builder&lt;/strong&gt; is the design surface itself: a palette of widgets on one side, a canvas to drop them on, and a mechanism — &lt;strong&gt;Links&lt;/strong&gt; — to wire them together.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Builder"
src="https://image.j-roque.com/posts/20260724-howuipageswork/builder.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Widget&lt;/strong&gt; is any visual element that takes input from, or shows output to, the user. Widgets connect to other elements (most commonly a data source) and carry their own individual configuration.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Widget"
src="https://image.j-roque.com/posts/20260724-howuipageswork/widget.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Task&lt;/strong&gt;, what used to be called a Data Source in earlier versions, is how a UI Page actually fetches data to feed its widgets. As of v11, Tasks are grouped into distinct categories: &lt;code&gt;Data Source&lt;/code&gt;, &lt;code&gt;Transaction&lt;/code&gt; (Business Workflows, service calls), &lt;code&gt;Core&lt;/code&gt;, &lt;code&gt;KPI&lt;/code&gt;, &lt;code&gt;Area&lt;/code&gt;, &lt;code&gt;Material&lt;/code&gt;, and &lt;code&gt;UI Page&lt;/code&gt; (for publish/subscribe behavior between pages).&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Task"
src="https://image.j-roque.com/posts/20260724-howuipageswork/task.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Property&lt;/strong&gt; belongs to the page itself and persists across saves — effectively a page-scoped variable used to initialize, receive, or store data that needs to move between elements.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Property"
src="https://image.j-roque.com/posts/20260724-howuipageswork/properties.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;An &lt;strong&gt;Action Button&lt;/strong&gt; lives on the page&amp;rsquo;s ribbon rather than the canvas, but is configured exactly like a Button widget. And a &lt;strong&gt;Link&lt;/strong&gt; is the wiring that makes all of the above talk to each other: connect one component&amp;rsquo;s output to another&amp;rsquo;s input, and the page reacts.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Action Button"
src="https://image.j-roque.com/posts/20260724-howuipageswork/actionbutton.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;Converters: Bridging Type Mismatches
&lt;div id="converters-bridging-type-mismatches" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#converters-bridging-type-mismatches" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Links only work cleanly when the output type on one side matches the input type on the other. When they don&amp;rsquo;t, for example a widget outputs a string, but the target expects a boolean, a &lt;strong&gt;Converter&lt;/strong&gt; sits in between and reshapes the value.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Converter"
src="https://image.j-roque.com/posts/20260724-howuipageswork/converter.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Version 11 organizes converters into categories, and the walkthrough of the documentation surfaced a handful worth knowing by name rather:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transformation&lt;/strong&gt; — e.g. &lt;code&gt;AnyToAnyProperty&lt;/code&gt;, which pulls a single named property off an object (grab a row&amp;rsquo;s &lt;code&gt;Id&lt;/code&gt; column, for instance), and &lt;code&gt;AnyToEmptyArray&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object Handling&lt;/strong&gt; — array length, filter value, map value — the converters you reach for constantly when shaping a payload before it hits a DEE.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logical&lt;/strong&gt; — &lt;code&gt;IsEqual&lt;/code&gt;, &lt;code&gt;IsFalse&lt;/code&gt;, &lt;code&gt;IsNotNull&lt;/code&gt;, and similar boolean operators, usable directly without writing an expression.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entity&lt;/strong&gt; — most notably &lt;code&gt;LoadEntity&lt;/code&gt;, which takes an ID and resolves the full entity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource&lt;/strong&gt; and &lt;strong&gt;Material&lt;/strong&gt; converters, and more specialized ones layered on top of those.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Later on we will use &lt;code&gt;AnyToAnyProperty&lt;/code&gt; to real use twice.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Layouts and Overriding Native UI Actions
&lt;div id="layouts-and-overriding-native-ui-actions" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#layouts-and-overriding-native-ui-actions" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;Layout&lt;/strong&gt; governs how a page&amp;rsquo;s disposition changes with screen size — how many columns the Builder shows, whether side panels are visible, and how they&amp;rsquo;re arranged. New layouts aren&amp;rsquo;t hardcoded; they&amp;rsquo;re added as entries in the native &lt;code&gt;UIPageLayouts&lt;/code&gt; smart table, where you set a description and the column configuration to display in the Builder.&lt;/p&gt;
&lt;p&gt;Native UI Actions, the built-in views the product ships, like the Fab Explorer&amp;rsquo;s step view, can be overridden the same declarative way, through entries in the &lt;code&gt;UIPageContext&lt;/code&gt; smart table, keyed by the action&amp;rsquo;s identifier.&lt;/p&gt;
&lt;p&gt;Create a custom page, add an entry in &lt;code&gt;UIPageContext&lt;/code&gt; pointing the native step-view action at it, set the custom page as effective, and refreshing Fab Explorer now renders the custom page instead of the product default. Worth flagging: not every native UI Action can be fully replaced this way, some resist a complete override.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Override Product Page"
src="https://image.j-roque.com/posts/20260724-howuipageswork/override_product_page.gif"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;Presets: One Page, Many Contexts
&lt;div id="presets-one-page-many-contexts" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#presets-one-page-many-contexts" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;UI Page Presets&lt;/strong&gt; solve a specific, recurring problem: you want the same page reused across many contexts, each with different data, without building a dedicated page per context. The classic case is a dashboard button injected per-resource, same page design, different resource passed through as context depending on which button was clicked.&lt;/p&gt;
&lt;p&gt;In the demo we will build exactly that: a preset scoped to &lt;code&gt;Menu Entry&lt;/code&gt;, targeting the Manufacturing page, configured to inject one quick-access button per resource. Each button, when clicked, passes that resource&amp;rsquo;s identity into the page as context; a &lt;code&gt;GetObjectByName&lt;/code&gt; service-call Task resolves the full entity from that name, and the result feeds an Entity Details widget. Critically, each injected button can be scoped independently by role — in the demo, one resource&amp;rsquo;s button was restricted to a &lt;code&gt;Cookie Manager&lt;/code&gt; role while another was left open to all users.&lt;/p&gt;
&lt;p&gt;The advantage over the old approach is real: previously, supporting this pattern meant a dedicated page per context, built and wired by hand. With presets, you build the page once, and the preset configuration decides how many buttons appear and who can see each one, no additional customization per resource.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Presets"
src="https://image.j-roque.com/posts/20260724-howuipageswork/presets.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;Anatomy of a Real UI Page: Building a Two-Level Sliding Grid
&lt;div id="anatomy-of-a-real-ui-page-building-a-two-level-sliding-grid" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#anatomy-of-a-real-ui-page-building-a-two-level-sliding-grid" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s imagine the user wants have a list of filters (container, facility, state) and a list view with two levels: Container View and Material By Slot Number. The user must be able to view the container detail info in the right side (Name; Orientation, Used Positions). We will add a sliding grid to show, green if the percentage is above 50% and red if it&amp;rsquo;s less.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Level 0: filters, a query, and clean naming
&lt;div id="level-0-filters-a-query-and-clean-naming" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#level-0-filters-a-query-and-clean-naming" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;The first widget dropped onto the canvas was a Filter widget — deliberately renamed to &lt;code&gt;ContainerFilterWidget&lt;/code&gt; rather than left at its default &lt;code&gt;FilterWidget1234&lt;/code&gt;. That naming discipline isn&amp;rsquo;t cosmetic; it&amp;rsquo;s what keeps a page&amp;rsquo;s link graph legible once it has more than a handful of widgets on it.&lt;/p&gt;
&lt;p&gt;The Sliding Grid itself was configured in lazy (&amp;ldquo;on demand&amp;rdquo;) mode, with two levels defined and single-selection mode enabled — a direct consequence of the requirement that selecting a container has to populate the right panel. A Query task, &lt;code&gt;GetContainerInfo&lt;/code&gt;, supplied the first level&amp;rsquo;s columns (name, orientation, user position, total position), and the Filter widget&amp;rsquo;s output was linked straight into the query&amp;rsquo;s filter input, no converter needed, since both sides were already the same object type.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When creating task query make sure you select the appropriate retrieval time (Retrieve Data on Start/ Retrieve Data on Changes), as this impacts the responsiveness of the page.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Build level 0"
src="https://image.j-roque.com/posts/20260724-howuipageswork/build_UI_0.gif"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Adding Filters"
src="https://image.j-roque.com/posts/20260724-howuipageswork/adding_filters.gif"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 class="relative group"&gt;Level 1: a DEE as a data source, and the converter that fixes it
&lt;div id="level-1-a-dee-as-a-data-source-and-the-converter-that-fixes-it" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#level-1-a-dee-as-a-data-source-and-the-converter-that-fixes-it" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;The second level deliberately used a different kind of Task, a &lt;strong&gt;DEE Action&lt;/strong&gt; instead of a query, specifically to show both paths side by side. The DEE, already written beforehand, took a container ID as input and returned a data table of the materials, products, flows, and steps associated with it.&lt;/p&gt;
&lt;p&gt;Wiring it up surfaced two real, live bugs, the kind every implementation runs into at least once.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First bug:&lt;/strong&gt; clicking a level-0 row triggered the level-1 fetch, but the DEE failed immediately. Opening the browser dev tools showed why: instead of receiving the container&amp;rsquo;s ID, the DEE was receiving the &lt;em&gt;entire row object&lt;/em&gt; from the sliding grid&amp;rsquo;s &amp;ldquo;fetch level&amp;rdquo; event. The fix was a converter — &lt;code&gt;AnyToAnyProperty&lt;/code&gt; — inserted on that link, extracting just the row&amp;rsquo;s &lt;code&gt;Id&lt;/code&gt; field, then a &lt;code&gt;SetMapValue&lt;/code&gt; converter injecting it into the DEE&amp;rsquo;s input as a new key, &lt;code&gt;ContainerId&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second bug:&lt;/strong&gt; with the ID now flowing correctly, the DEE executed successfully — visibly, in the dev tools&amp;rsquo; network response — but the grid still rendered no rows. The mismatch here was shape, not identity: the DEE returned a dictionary, while the Sliding Grid expected an array of a specific type. Same converter category solved it — another &lt;code&gt;AnyToAnyProperty&lt;/code&gt;, this time pulling the &lt;code&gt;Result&lt;/code&gt; field out of the dictionary into the shape the grid needed.&lt;/p&gt;
&lt;p&gt;Neither bug was exotic. Both are the direct, unglamorous consequence of connecting two components that don&amp;rsquo;t share a type — which is the entire reason Converters exist as a first-class concept in the Builder rather than an escape hatch.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Build level 1"
src="https://image.j-roque.com/posts/20260724-howuipageswork/level1_UI.gif"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 class="relative group"&gt;The right panel: entity details wired to selection
&lt;div id="the-right-panel-entity-details-wired-to-selection" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-right-panel-entity-details-wired-to-selection" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;The right-hand panel used an Entity Details widget bound to a Property — &lt;code&gt;Container&lt;/code&gt;, typed as a reference to the Container entity. Selecting a row on the Sliding Grid fires a selection-changed event, which writes the selected container into that property; the property, in turn, pushes straight through to the Entity Details widget. No query, no DEE, no round-trip, the selected row &lt;em&gt;is&lt;/em&gt; the entity, just handed off through a property acting as a pipe.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Right Panel"
src="https://image.j-roque.com/posts/20260724-howuipageswork/level2_rightpanel.gif"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 class="relative group"&gt;The pagination bug hiding behind a page that &amp;ldquo;looked done&amp;rdquo;
&lt;div id="the-pagination-bug-hiding-behind-a-page-that-looked-done" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-pagination-bug-hiding-behind-a-page-that-looked-done" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;At this point the page rendered correctly, filtered correctly, drilled down correctly, and populated the right panel correctly. But it can have a serious problem: the grid&amp;rsquo;s pagination was happening entirely client-side.&lt;/p&gt;
&lt;p&gt;That means on first load, the query fetches &lt;em&gt;every matching row&lt;/em&gt;, and the browser slices it into pages after the fact. With ten rows in a demo, that&amp;rsquo;s invisible. With three thousand, five thousand, or ten thousand containers, which is the actual scale most of these grids run at in production, that&amp;rsquo;s a page load that silently pulls the entire table across the wire before showing you the first ten rows.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A grid that &amp;ldquo;just works&amp;rdquo; against ten demo rows can still be pulling the entire table on every load — the browser hides that cost by paginating locally, right up until someone opens it against real production data.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The fix: add two integer properties to the page, &lt;code&gt;PageNumber&lt;/code&gt; and &lt;code&gt;PageSize&lt;/code&gt;. Wire the Sliding Grid&amp;rsquo;s page-change and page-size-change events into those properties, and wire those same properties &lt;em&gt;forward&lt;/em&gt; into the query as filter parameters, so the query itself only ever asks the database for the page currently being viewed. The query&amp;rsquo;s total-record count feeds back into the grid so it can render the correct number of pages. Get this chain right, and the database only ever returns the rows actually on screen; get it wrong, and the grid is a convenient UI sitting on top of an unbounded query.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Server Side Loading"
src="https://image.j-roque.com/posts/20260724-howuipageswork/level2_serversideloading.gif"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 class="relative group"&gt;Template columns without hardcoded styles
&lt;div id="template-columns-without-hardcoded-styles" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#template-columns-without-hardcoded-styles" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;The last piece of the requirement, a capacity column rendered as a green or red progress indicator using a custom column template with conditional syntax referencing the row&amp;rsquo;s data (&lt;code&gt;user positions&lt;/code&gt; over &lt;code&gt;total positions&lt;/code&gt;, colored green above 50%, red at or below it). The one deliberate choice worth calling out: the demo used the product&amp;rsquo;s existing CSS classes and CSS variables for the coloring, rather than hardcoded style values. Hardcoded styles silently diverge the moment the page renders under a different visual theme; variables inherit whatever theme is active automatically.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Custom Column"
src="https://image.j-roque.com/posts/20260724-howuipageswork/level2_customcolumn.gif"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;# if (data.UsedPositions != undefined &amp;amp;&amp;amp; data.TotalPositions != undefined) { #
    &amp;lt;div&amp;gt;
        &amp;lt;div style=&amp;#34;display: flex; flex-direction: row;width:100%;height:100%;align-items: center&amp;#34;&amp;gt;
            &amp;lt;div class=&amp;#34;progress&amp;#34; style=&amp;#34;position: relative;width:100%;margin-bottom:0px;background-color:transparent;&amp;#34;&amp;gt;
                &amp;lt;div class=&amp;#34;progress-bar&amp;#34; role=&amp;#34;progressbar&amp;#34; aria-valuenow=&amp;#34;#: ${ percentageProperty} #&amp;#34; aria-valuemin=&amp;#34;0&amp;#34;
                aria-valuemax=&amp;#34;100&amp;#34; style=&amp;#34;background-color:#: (UsedPositions / TotalPositions &amp;gt; 0.5 ) ?
                &amp;#34;var(--color-green)&amp;#34; : &amp;#34;var(--color-dark-red)&amp;#34; # ;position: absolute;width:#: UsedPositions #%&amp;#34;&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div style=&amp;#34;position: absolute;width: 100%;color:var(--styleColor004);font-weight: bold;&amp;#34;
                align=&amp;#34;center&amp;#34;&amp;gt;#: UsedPositions #/#: TotalPositions #&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
# } #&lt;/code&gt;&lt;/pre&gt;
&lt;h2 class="relative group"&gt;Business Workflows: The Low-Code Way to Build a DEE
&lt;div id="business-workflows-the-low-code-way-to-build-a-dee" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#business-workflows-the-low-code-way-to-build-a-dee" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;A Business Workflow can be standalone, or bound to an operation event (Track In &lt;code&gt;Pre&lt;/code&gt;/&lt;code&gt;Post&lt;/code&gt;, Track Out, Move Next — the native operations MES already exposes). You define its inputs — Material, Resource, whatever the scenario calls for — and drag in prebuilt building blocks like &lt;em&gt;Dispatch and Track In&lt;/em&gt;, wiring each block&amp;rsquo;s required fields back to the workflow&amp;rsquo;s custom inputs. Save it, and the product automatically generates the DEE underneath — test condition, action code, the whole apparatus — without a single line of C# written by hand. Wire an Action Button on a UI Page to trigger it, feed it Material and Resource straight from page properties, and clicking the button runs the generated DEE exactly as if someone had written it.&lt;/p&gt;
&lt;p&gt;Also, these low-code building blocks aren&amp;rsquo;t just an internal shortcut, they can be packaged and delivered to customers directly, letting their own teams compose new behavior out of pre-built, tested &amp;ldquo;Lego pieces&amp;rdquo; without opening a customization request at all. That&amp;rsquo;s the same extensibility philosophy behind DEEs in the first place, just one layer higher and without the code.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Button Biz Workflows"
src="https://image.j-roque.com/posts/20260724-howuipageswork/button_bizworkflows.gif"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;Best Practices
&lt;div id="best-practices" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#best-practices" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The closing list reads like a checklist, but every item traces back to something that actually broke on a real project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Avoid redundant entity loads.&lt;/strong&gt; Reloading an entity through a converter when a dedicated Property already holds it triggers unnecessary, repeated loads every time an action runs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eliminate duplicate progress indicators.&lt;/strong&gt; Multiple spinners on one page read as broken to an operator, even when nothing is actually wrong. Centralize on one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use server-side pagination, not client-side&lt;/strong&gt; — the exact bug the demo walked into.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run data sources only when needed&lt;/strong&gt; — evaluate per Task whether it should trigger on load, on change, or both; triggering more than necessary directly slows the page down.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use CSS variables, not hardcoded grid styles&lt;/strong&gt;, so template columns stay correct across different visual themes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always measure performance against realistic data volumes&lt;/strong&gt;, not the handful of rows a demo happens to use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow a naming convention for widgets and links.&lt;/strong&gt; &lt;code&gt;ContainerFilterWidget&lt;/code&gt; beats &lt;code&gt;FilterWidget1234&lt;/code&gt; the moment a page has more than five links to untangle.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 class="relative group"&gt;Final Thoughts
&lt;div id="final-thoughts" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#final-thoughts" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;What separates a UI Page that survives a real deployment from one that gets rebuilt six months later isn&amp;rsquo;t the widgets chosen. It&amp;rsquo;s whether pagination was wired all the way through to the query, whether the output stayed properly typed at every converter boundary, and whether &amp;ldquo;it works in the demo&amp;rdquo; was ever tested against something closer to the size of the actual database.&lt;/p&gt;
&lt;p&gt;The Builder will let you skip every one of those steps and still show you a page that looks finished. Production data is what actually checks your work.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This blog post was based on a talk by José Novo in 2025-09-11 @CM-Portugal&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded>
<enclosure url="https://j-roque.com/posts/20260724-howuipageswork/featured.png" type="image/png" length="0"/>
<media:content url="https://j-roque.com/posts/20260724-howuipageswork/featured.png" type="image/png" medium="image"/>
</item>
</channel>
</rss>