Skip to content

Training Build a Weather Lookup Screen

Craig Scott edited this page Jun 9, 2026 · 2 revisions

Training: Build a Weather Lookup Screen (End to End)

What you'll learn: how to wire an external API into a screen with no data model — a connection, a data flow, and a screen where a user enters a value, clicks a button, and sees a richly formatted result. This is the capstone that ties the connection and flow training into one working application, and it covers the specific nuances that make a model-less, flow-driven screen behave. You need: Administrator or Developer access type in an application (UI label "Tenant"). Time: 30–45 minutes. Example API used: the free, no-auth wttr.in weather service (https://wttr.in/{zip}?format=j1 — ZIP in, JSON out, no key).

The finished Weather Lookup screen: weather on the left, "how it's wired" on the right

The shape of it

Four pieces work together. The user types a ZIP and clicks Get Weather; the button tells the form to load; the form's data transform runs a data flow that calls the weather API and reshapes the response, then builds markdown strings; Markdown Text elements render them.

ZIP input ─▶ Get Weather button ─▶ $components.Form1.fn.loadData()
                                          │
                          Form1 Data Transform
                                          │
                     $executeFlow('weatherLookupFlow', { zip })
                                          │
                          wttr.in  →  reshaped fields  →  weatherMd / howtoMd
                                          │
                       Markdown Text elements render the markdown

No data model is involved — the flow's response feeds the form directly. That single fact drives almost every nuance below.


1. Create the HTTP connection

  1. Go to Left menu → Integrations → Connections+.
  2. Name Weather API, Connector HTTP, Url https://wttr.in. Leave Credentials blank (no auth); Headers can stay {}.
  3. Click the orange +.

Nuance — why ?format=j1: a plain request to wttr.in returns an ASCII/HTML weather card. Appending ?format=j1 (set later, in the flow's path) forces the structured JSON response we can parse. No API key and no special headers are needed for the j1 format.

2. Build and deploy the data flow

  1. Go to Left menu → Flows & Scripts → Data Flows+. Name it Weather Lookup Flow, Type → Screen, pick a Module, → create. Open the flow designer (⑂).

  2. Drag a Source node (Debugging) on. Set its Payload to {"zip": "90210"} so you can test the flow by itself.

  3. Search the Toolbox for "HTTP", expand Integration, drag the HTTP node on:

    • Connection Weather API, Method Get
    • Path: open the </> code editor and enter the JSONata '/' & zip & '?format=j1' (builds the path from the incoming zip).
  4. Drag a JSONata node (Scripts) on and reshape the bulky j1 payload into clean fields:

    {
      "location": nearest_area[0].areaName[0].value,
      "region": nearest_area[0].region[0].value,
      "tempF": current_condition[0].temp_F,
      "feelsLikeF": current_condition[0].FeelsLikeF,
      "conditions": current_condition[0].weatherDesc[0].value,
      "humidity": current_condition[0].humidity,
      "windMph": current_condition[0].windspeedMiles,
      "windDir": current_condition[0].winddir16Point,
      "forecast": weather.{
        "date": date, "maxF": maxtempF, "minF": mintempF,
        "conditions": hourly[4].weatherDesc[0].value
      }
    }
    
  5. Wire Source → HTTP → JSONata. Run from the Source's orange trigger and confirm the console shows clean weather.

  6. File → Save, then File → Deploy.

⚠️ Biggest gotcha — deploy everything. $executeFlow(...) calls the deployed version of a flow. If the flow (or, later, the screen) is only saved and not deployed, the call returns HTTP 400 / "Error retrieving data" and the flow never runs (you'll see no entry in Data Flow Logs). Deploy the flow now, and deploy the screen at the end.

3. Build the screen

3a. Start from the Form template

Screens & Documents → Screens → +, name it Weather Lookup, pick a Module, open the screen designer, and choose the Form template. When it asks for a data model, cancel the model dialog — you keep the rendered form scaffold without binding it to a model.

Nuance — why the Form template, not Blank. Leaf elements (inputs, buttons, text) only drop reliably into visible, sized containers. An empty Blank-screen container renders as an invisible thin strip and drops miss it. The Form template gives you a rendered scaffold with real drop targets, plus the Form element that will do the data work.

3b. The element layout

Build this structure (the exact tree from the finished screen):

Screen
├─ Container1 (the top action bar)
│   └─ ActionButton  "Get Weather"
└─ Form1            ← the data engine
    └─ Container2
        └─ Container3
            ├─ Number1            ZIP Code   (Data Path: zip)
            └─ Container6  (a ROW container)  ← this creates the two columns
                ├─ Text1   weatherMd   (Format: Markdown)
                └─ Text4   howtoMd     (Format: Markdown)

Nuance — two columns = one row container. The side-by-side "weather | how it's wired" layout is simply a Container set to a row (Container6) holding the two Markdown Text elements. Set the container's flex direction to row (and flexWrap on if you want it to stack on narrow screens). The Get Weather button sits in the top action bar container.

3c. The ZIP input

Drop a Number input into the form. Set Data Path zip, Label ZIP Code. (Dropping it into the form auto-links it to Form1 as its Form Element.) Set its Format to 0 so it shows the raw ZIP (90210) instead of a thousands-separated number (90,210).

Nuance — Number vs Text for ZIP. With Format = 0 a Number input displays cleanly (no comma) and works great for most US ZIPs. It still drops leading zeros, though — a New England ZIP like 02134 becomes 2134. If you need those, use a Text input with a 5-digit pattern, or zero-pad in the flow ($pad($string(zip), -5, '0')).

3d. The Get Weather button

Drop an Action button into the action bar. In Action → Action Steps, add one Transformation step:

$components.Form1.fn.loadData()

Nuance — loadData() is the trigger. Because the data lives on the form (not a table), the button doesn't run a query or call the flow directly — it tells the form to (re)run its own data transform via fn.loadData(). This is the piece that connects "button click" to "fetch weather."

3e. Make the form the data engine

Select Form1:

  • Data → Auto Load: OFF. With no data model, an auto-load would try to build a GraphQL query and error. Off means the form only loads when the button calls loadData().
  • Advanced → Data Transform Remote Execution: OFF. The transform reads $components.Form1.formState.values.zip, which only exists client-side. ($executeFlow itself still runs the flow on the server — only the transform expression evaluates client-side.)
  • Advanced → Data Transform: call the flow with the typed ZIP, then build the markdown. This single transform emits both weatherMd and howtoMd:
(
  $w := $exists($components.Form1.formState.values.zip)
    ? $executeFlow('weatherLookupFlow',
        {'zip': $string($components.Form1.formState.values.zip)})
    : {};

  /* condition -> emoji */
  $icon := function($c){(
    $l := $lowercase($c);
    $contains($l,'thunder') ? '⛈️'
      : ($contains($l,'snow') or $contains($l,'sleet')) ? '❄️'
      : ($contains($l,'rain') or $contains($l,'drizzle') or $contains($l,'shower')) ? '🌧️'
      : $contains($l,'partly') ? '⛅'
      : ($contains($l,'cloud') or $contains($l,'overcast')) ? '☁️'
      : ($contains($l,'sun') or $contains($l,'clear')) ? '☀️'
      : '🌡️'
  )};

  /* a fixed "how this is wired" panel */
  $howto := '## 🛠️ How this screen is wired\n\n'
    & '**1. HTTP Connection** - Weather API pointing at wttr.in, no API key.\n\n'
    & '**2. Data Flow** - Weather Lookup Flow (Screen type): Source to HTTP GET, then a JSONata transform reshapes the response into clean fields plus a weatherMd markdown string.\n\n'
    & '**3. Screen** - from the Form template: a ZIP Number input, a Get Weather button that calls loadData, the Form data transform that runs executeFlow and builds the markdown, and Markdown Text elements that display it.\n\n'
    & '_No data model needed - the flow response feeds the form directly._';

  /* add weatherMd only when we actually got data */
  $weatherObj := $exists($w.location)
    ? $merge([$w, {'weatherMd':
        '# ' & $icon($w.conditions) & ' ' & $w.location & ', ' & $w.region & '\n\n'
        & '## ' & $w.tempF & '°F · ' & $w.conditions & '\n\n'
        & '🌡️ Feels like **' & $w.feelsLikeF & '°F**  ·  💧 Humidity **' & $w.humidity & '%**  ·  💨 Wind **' & $w.windMph & ' mph ' & $w.windDir & '**\n\n'
        & '---\n\n### 📅 3-Day Forecast\n\n'
        & '| Day | Sky | Low | High | Conditions |\n|---|---|---|---|---|\n'
        & $join($w.forecast.('| ' & date & ' | ' & $icon(conditions) & ' | ' & minF & '°F | ' & maxF & '°F | ' & conditions & ' |'), '\n')
      }])
    : $w;

  /* howtoMd is always emitted; weatherMd only after a successful lookup */
  $merge([$weatherObj, {'howtoMd': $howto}])
)

Nuance — the $exists($w.location) guard. When there's no ZIP yet (or the flow returns nothing), the guard returns the bare object so the transform never errors on missing fields. howtoMd is added in the final $merge so the "how it's wired" panel shows regardless.

Nuance — building markdown in JSONata. Concatenate with &, use \n for newlines, and | col | col | rows joined with \n for tables. Emoji and ° render fine. Keep the markdown light on () {} and backticks — the code editor auto-closes those, and a long literal full of them is easy to corrupt; prefer bold/headings/lists and - dashes.

3f. The two Markdown displays

Drop two Display → Text elements into the row container (Container6). For each, set Behavior → Format = Markdown and Data Path to weatherMd and howtoMd respectively.

⚠️ Gotcha — render markdown via a form key, never the element's own transform. A Display→Text element's own Data Transform (returning a standalone string) crashes the screen render. Always produce the markdown as a key in the form's data transform and bind the element's Data Path to that key — exactly how weatherMd/howtoMd work here.

3g. Save, deploy, route

File → Save, then Deploy, and add a Route so users can reach it.

4. Run it

Open the screen, type a ZIP (e.g. 90210), click Get Weather — the flow runs and the formatted weather appears on the left, with the "how it's wired" panel on the right.

Weather result rendered as markdown

Check your work

  • The flow, run from its Source node, returns clean weather fields in the console.
  • On the screen, entering a ZIP and clicking Get Weather renders the location, current conditions, and a 3-day forecast table.
  • If you see "Error retrieving data": confirm the flow and screen are deployed, Auto Load is off, and Remote Execution is off on the form's data transform.
  • If the screen crashes on load: a display element has its own Data Transform — move that logic into the form's data transform and bind the element by Data Path instead.

Nuances at a glance

Nuance What to do
$executeFlow returns 400 Deploy the flow and the screen (not just save).
No data model Put the data transform on the Form; turn Auto Load off.
Button → fetch Action step $components.Form1.fn.loadData().
$components is undefined in the transform Turn Data Transform Remote Execution off.
Markdown won't render / screen crashes Emit a markdown key from the form transform; bind a Format=Markdown Text element — don't use the element's own transform.
Two columns One row Container holding both Text elements.
Drops miss empty containers Build on the Form template scaffold (visible drop targets).
Number input shows 90,210 Set the Number input's Format to 0 (no thousands separator).
Leading-zero ZIPs lost Number input still drops them — use a Text input or zero-pad in the flow.

Things you could add

  • Loading & empty states — show a "Enter a ZIP and click Get Weather" placeholder before the first lookup, and a spinner/disabled button while the flow runs.
  • Input validation — restrict ZIP to 5 digits (Text input + pattern) and handle a not-found ZIP gracefully (wttr.in echoes the nearest area; you can detect a missing current_condition).
  • Units toggle — the j1 payload carries both °F and °C / mph and km/h; add a Switch and pick the fields accordingly.
  • Cache / rate limitswttr.in is free and rate-limited; for anything real, cache results (e.g., store the last lookup) or move to a keyed provider.
  • Reuse the pattern — point a new connection at any no-auth JSON endpoint and repeat: flow reshapes → form transform builds markdown → Markdown Text renders.

Related training

Training: Call an External API with a Flow · Training: Connect to External Systems · Training: Create a Data Flow · Training: Generate a Screen from a Data Model

🏠 Home

Getting Started (14)
Training Guides (52)

Applications

Access & Users

Data Models & Schema

Screens

Weather Lookup Series — guided 3-part build

Data Flows & Integrations

Data, Reporting & Monitoring

Enterprise & Organizations

Platform Concepts & Architecture (10)
Screens & Application Design (17)
Data Models & Schema (8)
Data Flows & Scripting (51)

Designing Flows

Data Flow Nodes

JSONata Reference

Scripting

Integrations & Connectors (30)

General & iPaaS

Plex

EDI

IIoT & Edge Gateway (18)

Physical Device Connectors

Edge Data Connectors

Reporting, Documents & Dashboards (8)
Administration & Access Control (27)
Data Management (8)
Accelerators, Templates & Packages (8)
Design Standards (1)
How-To Guides (8)
FAQ & Troubleshooting (1)
Release Notes (117)

2026

2025

2024

2023

2022

2021

2020

Policies & Company (6)

Clone this wiki locally