Before you start
galSystems with all 14 fields writing back into colSystems, plus the btnAddSystem / btnDeleteRow / lblSystemCount wiring. If any of that is missing, finish Part 1 first. (Wherever a guide page shows <YOUR-SITE-URL> or <IAM-DL-EMAIL>, substitute your own values — this part happens to need neither.)colSystems, which the OnChange write-back keeps current on every edit. That's why every formula on this page is a plain expression over the collection: no gallery state, no surprises.txtAppName is a value you enter — tap the chip to copy it (the ⧉ mark is the reminder), then paste it where the step says. A key cap like Ctrl+A is pressed on the keyboard. Dark boxes are complete formulas with their own Copy button. Paste, never retype.Guidance — every field explains itself
B1Hint text on all ten text inputs
Ten pastes, one per input — the grey guidance a user sees in an empty field. Same motion each time: Tree view → expand galSystems → click the input → property dropdown (top-left, next to the fx bar) → HintText → Ctrl+A → paste (with the quotes).
txtAppName · HintText
"Enter the system/application name"txtVendor · HintText
"Enter the software vendor or service provider"txtOwner · HintText
"Enter the business owner name"txtPurpose · HintText
"Describe the business purpose of the application"txtCustodian · HintText
"Enter the responsible IT contact"txtAdminAccts · HintText
"List administrator accounts used to manage the system"txtPermissions · HintText
"Describe available roles and access levels"txtSupport · HintText
"Enter support email, phone number, or contact details"txtAccessLink · HintText
"Enter the application URL or access portal link"txtNumAccts · HintText
"Enter the total number of active user accounts"HintText, Default, BorderColor). Revisit Part 1's settings step and make sure "Modern controls and themes" is off, then re-insert the input as a classic one.B2Dropdown tooltips — and the empty-until-chosen check
Classic dropdowns have no placeholder, so their guidance rides on Tooltip. Four pastes: Tree view → expand galSystems → click the dropdown → property dropdown → Tooltip → Ctrl+A → paste:
drpIAMRole · Tooltip
"Select IAM involvement level"drpDivision · Tooltip
"Select the applicable division"drpStatus · Tooltip
"Select the application status"drpHandover · Tooltip
"Select the handover status"While you're here, re-check AllowEmptySelection on all four: select drpIAMRole → property dropdown → AllowEmptySelection — the formula bar must read true. If it reads false, Ctrl+A → type true. Repeat for drpDivision, drpStatus and drpHandover.
B3Mark every field required
All 14 fields are mandatory, so all 14 labels say so. The first one, done together: Tree view → expand galSystems → click lblAppName → property dropdown → Text → Ctrl+A → paste:
lblAppName · Text
"Application Name *"Now the other 13, same motion — each chip is the full Text value (tap to copy, quotes included):
lblVendor→"Vendor/Provider Name *"lblOwner→"Business Owner *"lblPurpose→"Application Purpose *"lblCustodian→"IT Custodian *"lblAdminAccts→"Admin Accounts Used *"lblPermissions→"Permissions / Access Levels *"lblSupport→"Support / Vendor Contact *"lblIAMRole→"IAM Role *"lblAccessLink→"Access Link *"lblDivision→"Division *"lblStatus→"Status *"lblHandover→"Handover Status *"lblNumAccts→"Number of Accounts *"
* — count 14 in one row, and the layout hasn't wrapped or clipped anywhere.Validation — the gate and the messages
B4The Submit button and the form-level error label
a) The button. Click scrMain in the Tree view (so the button lands on the screen, not inside the gallery), then + Insert (left rail) → type Button in the search box → click it. Double-click its name in the Tree view and paste btnSubmit. Position it via the Properties pane on the right → Position → X 1160 · Y 712, Size → Width 180 · Height 44. It should sit fully on the dark footer bar next to btnAddSystem — if it doesn't, check that recFooter's Y is 692.
Three properties, each via the property dropdown (top-left, next to the fx bar) → select the property → Ctrl+A → paste (Text keeps its quotes):
- Text
"Submit Declaration"- Fill
gblColorBrand- Color
Color.White
Property dropdown → DisplayMode → Ctrl+A → paste:
btnSubmit · DisplayMode
If(CountRows(colSystems) = 0 || gblSubmitting, DisplayMode.Disabled, DisplayMode.Edit)b) The label. Click scrMain in the Tree view again, then + Insert → type Text label in the search box → click it. Double-click its name in the Tree view and paste lblFormError. Properties pane → Position → X 500 · Y 718, Size → Width 640 · Height 32.
Two properties via the property dropdown → select → Ctrl+A → paste:
- Color
Color.Red- Text
""— for now; B8 wires the real formula
gblSubmitting will lock the button during the SharePoint write in Part 3 — no double-clicks, no duplicate records.B5The validation gate on Submit
One paste — the whole gate. Tree view → click btnSubmit → property dropdown → OnSelect → Ctrl+A → paste:
btnSubmit · OnSelect
Set(gblShowErrors, true);
If(
CountRows(colSystems) > 0 && CountRows(Filter(colSystems, IsBlank(AppName) || IsBlank(Vendor) || IsBlank(BizOwner) || IsBlank(Purpose) || IsBlank(Custodian) || IsBlank(AdminAccts) || IsBlank(Permissions) || IsBlank(SupportContact) || IsBlank(IAMRole) || IsBlank(Division) || IsBlank(Status) || IsBlank(Handover) || IsBlank(AccessLink) || !IsMatch(AccessLink, "^(https?://)?([\w\-]+\.)+[\w\-]+(/\S*)?$") || IsBlank(NumAccts) || !IsMatch(NumAccts, "^\d+$"))) = 0,
Set(gblShowConfirm, true)
)gblShowErrors — the red borders and messages of B6–B8 key off it). Then it counts invalid rows: a row fails if any of the 14 fields is blank, if the Access Link isn't URL-shaped, or if Number of Accounts isn't a whole number. Only a declaration with zero invalid rows sets gblShowConfirm — the confirmation popup you build in Part 3.IsMatch only accepts a pattern typed directly into the formula — never move the regex into a variable "to tidy up"; it will stop compiling. The pattern is deliberately lenient: real inventory entries include bare domains like stc.com.bh, and those should pass.gblShowConfirm flips to true but no popup exists until Part 3. Nothing is broken.gblShowErrors = true, gblShowConfirm = false. Fill one row completely — all four dropdowns chosen, Access Link https://example.com, Number of Accounts 5 — click again → gblShowConfirm = true. Then run OnStart (App → Run OnStart) to reset the flags; it re-seeds the gallery to one blank row, which is expected while building.B6Red borders on all fourteen inputs
Every input signals its own state through BorderColor: resting grey normally, red once gblShowErrors is on and the field is empty (or invalid). The first paste, done together — Tree view → expand galSystems → click txtAppName → property dropdown → BorderColor → Ctrl+A → paste:
txtAppName · BorderColor
If(gblShowErrors && IsBlank(ThisItem.AppName), Color.Red, RGBA(201, 204, 213, 1))The other eleven plain fields, same motion — each is a full paste, no editing. Select the control → property dropdown → BorderColor → Ctrl+A → paste:
txtVendor · BorderColor
If(gblShowErrors && IsBlank(ThisItem.Vendor), Color.Red, RGBA(201, 204, 213, 1))txtOwner · BorderColor
If(gblShowErrors && IsBlank(ThisItem.BizOwner), Color.Red, RGBA(201, 204, 213, 1))txtPurpose · BorderColor
If(gblShowErrors && IsBlank(ThisItem.Purpose), Color.Red, RGBA(201, 204, 213, 1))txtCustodian · BorderColor
If(gblShowErrors && IsBlank(ThisItem.Custodian), Color.Red, RGBA(201, 204, 213, 1))txtAdminAccts · BorderColor
If(gblShowErrors && IsBlank(ThisItem.AdminAccts), Color.Red, RGBA(201, 204, 213, 1))txtPermissions · BorderColor
If(gblShowErrors && IsBlank(ThisItem.Permissions), Color.Red, RGBA(201, 204, 213, 1))txtSupport · BorderColor
If(gblShowErrors && IsBlank(ThisItem.SupportContact), Color.Red, RGBA(201, 204, 213, 1))drpIAMRole · BorderColor
If(gblShowErrors && IsBlank(ThisItem.IAMRole), Color.Red, RGBA(201, 204, 213, 1))drpDivision · BorderColor
If(gblShowErrors && IsBlank(ThisItem.Division), Color.Red, RGBA(201, 204, 213, 1))drpStatus · BorderColor
If(gblShowErrors && IsBlank(ThisItem.Status), Color.Red, RGBA(201, 204, 213, 1))drpHandover · BorderColor
If(gblShowErrors && IsBlank(ThisItem.Handover), Color.Red, RGBA(201, 204, 213, 1))The last two carry their format checks as well — full pastes, no editing:
txtAccessLink · BorderColor
If(gblShowErrors && (IsBlank(ThisItem.AccessLink) || !IsMatch(ThisItem.AccessLink, "^(https?://)?([\w\-]+\.)+[\w\-]+(/\S*)?$")), Color.Red, RGBA(201, 204, 213, 1))txtNumAccts · BorderColor
If(gblShowErrors && (IsBlank(ThisItem.NumAccts) || !IsMatch(ThisItem.NumAccts, "^\d+$")), Color.Red, RGBA(201, 204, 213, 1))gblShowErrors means a user filling the form top-to-bottom is never shouted at mid-entry — red appears only after they try to submit, and clears live as they fix each field (the write-back keeps colSystems current, and these formulas read ThisItem, which is the collection row).B7The per-row error message
Each row card gets one red line at its foot that names every offending field — far friendlier than 14 separate error labels, and far fewer controls in the template. Part 1 didn't create this label, so insert it now — into the gallery template, not onto the screen:
- Click any control inside the first row (e.g.
txtAppName) so the gallery template is active. - + Insert (left rail) → type
Text labelin the search box → click it. - Look at the Tree view: the new label must appear nested under
galSystems. If it sits at screen level instead, delete it, click inside the row again, and re-insert. - Double-click the label's name in the Tree view → paste
lblRowError→ Enter. - Properties pane (right side) → Position → X
16· Y552, Size → Width1294· Height40. Leave Auto height off (it's off by default) — the gallery template is fixed-height and a growing label would spill out of the card. - Property dropdown → Color → Ctrl+A → paste:
Color.Red
Property dropdown → Text → Ctrl+A → paste:
lblRowError · Text
If(!gblShowErrors, "",
With({missing:
Concat(
Filter(
Table(
{f: "Application Name", bad: IsBlank(ThisItem.AppName)},
{f: "Vendor/Provider Name", bad: IsBlank(ThisItem.Vendor)},
{f: "Business Owner", bad: IsBlank(ThisItem.BizOwner)},
{f: "Application Purpose", bad: IsBlank(ThisItem.Purpose)},
{f: "IT Custodian", bad: IsBlank(ThisItem.Custodian)},
{f: "Admin Accounts Used", bad: IsBlank(ThisItem.AdminAccts)},
{f: "Permissions/Access Levels", bad: IsBlank(ThisItem.Permissions)},
{f: "Support/Vendor Contact", bad: IsBlank(ThisItem.SupportContact)},
{f: "IAM Role", bad: IsBlank(ThisItem.IAMRole)},
{f: "Access Link", bad: IsBlank(ThisItem.AccessLink)},
{f: "Division", bad: IsBlank(ThisItem.Division)},
{f: "Status", bad: IsBlank(ThisItem.Status)},
{f: "Handover Status", bad: IsBlank(ThisItem.Handover)},
{f: "Number of Accounts", bad: IsBlank(ThisItem.NumAccts)}
), bad), f, ", ")},
If(missing <> "", "Missing: " & missing & ". ", "") &
If(!IsBlank(ThisItem.AccessLink) && !IsMatch(ThisItem.AccessLink, "^(https?://)?([\w\-]+\.)+[\w\-]+(/\S*)?$"),
"Access Link must be a valid URL (e.g. https://portal.vendor.com). ", "") &
If(!IsBlank(ThisItem.NumAccts) && !IsMatch(ThisItem.NumAccts, "^\d+$"),
"Number of Accounts must be a whole number.", "")
))Then property dropdown → Visible → Ctrl+A → paste:
lblRowError · Visible
gblShowErrors && !IsBlank(Self.Text)Table(…) builds fourteen tiny {field-name, is-it-bad} records, Filter keeps the bad ones, Concat strings their names together — then the two trailing Ifs append the URL and whole-number messages when those fields are filled but wrong. One formula, complete diagnosis.abc in Access Link and the URL message joins on. Fix a named field and tab out — it drops off the list immediately.B8The form-level message, and three finishing touches
a) Wire the footer label from B4. Tree view → click lblFormError → property dropdown → Text → Ctrl+A → paste:
lblFormError · Text
If(!gblShowErrors, "",
With({bad: CountRows(Filter(colSystems, IsBlank(AppName) || IsBlank(Vendor) || IsBlank(BizOwner) || IsBlank(Purpose) || IsBlank(Custodian) || IsBlank(AdminAccts) || IsBlank(Permissions) || IsBlank(SupportContact) || IsBlank(IAMRole) || IsBlank(Division) || IsBlank(Status) || IsBlank(Handover) || IsBlank(AccessLink) || !IsMatch(AccessLink, "^(https?://)?([\w\-]+\.)+[\w\-]+(/\S*)?$") || IsBlank(NumAccts) || !IsMatch(NumAccts, "^\d+$")))},
If(bad > 0, bad & " system record(s) have missing or invalid fields — fix the highlighted rows above.", "")
))lblFormError · Visible
gblShowErrors && !IsBlank(Self.Text)b) Smoother typing. On each of the ten text inputs: select it → property dropdown → DelayOutput → Ctrl+A → type true. The ten: txtAppName · txtVendor · txtOwner · txtPurpose · txtCustodian · txtAdminAccts · txtPermissions · txtSupport · txtAccessLink · txtNumAccts. Validation now re-evaluates a beat after you pause instead of on every keystroke.
c) Guard Delete Row during submission. Tree view → click btnDeleteRow → property dropdown → DisplayMode → Ctrl+A → paste:
btnDeleteRow · DisplayMode
If(gblSubmitting, DisplayMode.Disabled, DisplayMode.Edit)B9The validation test script
A condensed test aimed at everything this page added. Run OnStart first (App → Run OnStart) for a clean single blank row, then F5:
- Empty submit → every field in the row wears a red border, the row foot lists all 14 field names, the footer reads "1 system record(s) have missing or invalid fields…". No popup opens.
- Bad URL → fill the row validly but put
not a linkin Access Link → the row message switches to the URL-specific line; only Access Link stays red-bordered. - Bad number → Number of Accounts refuses letters as you type (its Format is Number); paste
12aanyway → "Number of Accounts must be a whole number." appears. Replace with25. - Live clearing → fix the remaining gaps one by one — each border calms and each name drops off the row message as you tab out; the footer count falls to nothing.
- Valid pass → click Submit Declaration on the fully valid declaration → silence, and the Variables pane shows
gblShowConfirm = true. That silent flag is Part 3's opening move: the confirmation popup, the SharePoint write, and the IAM email.