Why Does Perspective Dashboard Lose Widget Settings?

Patricia Callen9 min read
HMI ProgrammingOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

After saving the complete widget configuration as JSON and restoring it as structured data, each Perspective Dashboard can retain its layout and per-widget settings across session restarts. The critical design rule is to store configuration with the widget instance instead of associating it with an array index or a shared view parameter.

Why do the usual fixes fail?

Several tempting fixes address the visible symptom without repairing the data path.

  • Changing the widget view binding: A binding can make an iframe display the entered URL, but that does not prove the new value was written into props.widgets. The displayed value and the persisted dashboard configuration are separate signals.
  • Using the array index as widget identity: An index identifies a current position, not a durable widget instance. Removing widget A shifts later entries. Logic that still treats the old index as widget B can copy A's src value into B.
  • Matching parameter names: Giving both the view and the widget a parameter named src establishes a compatible interface. It does not automatically create a reverse write from the view into props.widgets[n].viewParams.src.
  • Tuning the database conversion: Repeatedly wrapping and unwrapping the same object will not repair an incorrect instance association. Serialization preserves whatever object graph it receives, including configuration assigned to the wrong widget.
  • Saving only the visible value: Persisting an iframe URL separately from the widget configuration leaves layout, size, position, header, and other viewParams disconnected from the instance that owns them.

Look at the value path first. Confirm where the operator entry lands, where the Dashboard records it, and what the database receives. Persistence cannot correct a value that never reached the intended widget configuration.

What is the actual configuration signal chain?

The designer populates availableWidgets with reusable widget definitions. When an operator adds one, the Dashboard copies that configuration into props.widgets. The in-use entry then carries the view path, view parameters, minimum size, header and body configuration, and the automatically updated position.

For a configurable widget, isConfigurable enables the pencil control while the Dashboard is in edit mode and the widget is selected. Activating that control sets the configuring parameter passed to that widget's view to true. The view must use that state to expose its configuration interface. The parameter indicates configuration mode; it is not an instance identifier and does not persist another parameter by itself.

Signal Source Wrong-value symptom
props.widgets Dashboard entries created and updated at runtime Layout returns to defaults after restart when the array is not stored and restored
viewParams.src The specific in-use widget configuration The iframe displays a value, but the widget record remains unchanged
configuring Dashboard pencil action for the selected configurable widget The configuration UI opens, but code mistakes the Boolean for widget identity
Array index Current ordering of props.widgets Deleting one widget shifts entries and causes settings to appear on another widget
name Widget configuration Duplicate or unstable names prevent reliable instance lookup
Stored JSON text Serialized complete widget array Assigning undecoded text on startup produces a string instead of widget objects

Trace one URL from the input field through the selected widget's viewParams, then through the serialized payload, database row, decoded startup result, and restored iframe. A break at any stage explains the symptom more directly than changing unrelated bindings.

What causes settings to move after a widget is deleted?

Deletion changes the shape of the in-use array. If widget A occupies index 0 and widget B occupies index 1, deleting A makes B the new index 0. Any separate array, tag, binding, or script that still associates index 0 with A now targets B. The resulting value transfer looks like a Dashboard defect, but the association failed because position was used as identity.

Use the unique widget name carried by each WidgetInUseConfig as the durable lookup key. Treat rowStart, rowEnd, columnStart, columnEnd, and array index as mutable layout data. Moving, resizing, adding, removing, and bin packing can change those values.

If operators can create multiple instances from one available definition, each in-use instance needs a distinct name before external code uses that name as a key. The deciding test is simple: inspect props.widgets after adding two instances and compare their names. If they are not distinct, assign and retain an installation-defined instance key within the configuration rather than falling back to the index.

Keep the iframe URL in that instance's viewParams.src. Do not place all widget URLs in a parallel string array unless every operation updates both collections atomically by the same stable key. Parallel collections multiply the number of failure paths during deletion and reordering.

How should the widget view update its own configuration?

  1. Declare configuring and src in the available widget's viewParams. Set isConfigurable so the Dashboard can expose the pencil action.
  2. Add the widget and inspect its entry under props.widgets. Confirm that its viewPath, unique name, and viewParams identify the intended instance.
  3. Enter edit mode, select the widget, and activate the pencil. Verify that the view receives configuring=true and switches to its configuration presentation.
  4. When the operator accepts a URL, write it to the src parameter belonging to that same in-use instance. A local display binding alone is insufficient; the value must appear under that widget's viewParams.src in props.widgets.
  5. Locate an instance by its stable name or stored instance key when a parent script must modify the array. Read the current array, find the matching entry, update only its viewParams, and write the updated array back as one operation.
  6. Cancel configuration without committing when the operator rejects the edit. This prevents a partially entered value from triggering the persistence script.

Measure the array after the commit. If the iframe changes but props.widgets does not, the view is consuming a temporary or shared value. If the wrong array entry changes, the lookup still depends on index, current selection, or another non-durable signal.

How do you save the Dashboard configuration to Postgres?

A working Ignition Maker 8.0.15 installation converted the Dashboard array with TypeUtilities.pyToGson, converted that result to JSON text, and sent the text through a string Named Query parameter:

from com.inductiveautomation.ignition.common import TypeUtilities
User = self.session.props.auth.user.userName
dashboardWidgets = self.props.widgets
jsonObjectFull = str(TypeUtilities.pyToGson(dashboardWidgets))
vars = {"user": User, "widgets": jsonObjectFull}
system.db.runNamedQuery("DashboardUpdate", vars)

The conversion to a string is the serialization boundary. It is appropriate when the Named Query parameter accepts a string and the database column stores JSON text or accepts JSON input from text. There is no need to undo str() before the insert. The reverse conversion belongs on the read path.

INSERT INTO dashboards (dashboardid, username, widgets)
VALUES (DEFAULT, :user, :widgets)
ON CONFLICT (username) DO UPDATE SET widgets = EXCLUDED.widgets;

Both :user and :widgets are strings in this configuration. The conflict action gives each username one current dashboard record and replaces its widget payload on later saves.

The property-change script receives props.widgets as an array of QualifiedValues, so conversion must preserve the nested widget objects rather than relying on their diagnostic display text. Store the entire array. Saving only selected fields discards Dashboard-managed position and size changes.

A change listener can fire for additions, removals, moves, resizes, and configuration edits. Keep database writes out of incomplete intermediate edits where possible. Also distinguish startup restoration from an operator edit so assigning the saved array does not immediately overwrite the row with a transient or default state.

How do you restore saved widgets at startup?

  1. Run a startup query for the active username. Apply the same user key used by the upsert; a hard-coded test username must not remain in production logic.
  2. Read the stored widget payload as JSON text.
  3. Decode that text into structured arrays, objects, strings, numbers, and Booleans. Do not assign the raw JSON string directly to props.widgets.
  4. Validate that the top-level decoded value is an array and that each entry contains the fields required by the corresponding in-use widget, including its view path, name, parameters, size, and position data.
  5. Assign the decoded array to props.widgets. Populate availableWidgets separately when available choices vary by user role.
  6. After assignment, inspect the live array and compare the selected instance's viewParams.src with the database payload.

The saved in-use array and the available-widget catalog serve different purposes. availableWidgets defines what an operator may add and supplies defaults. props.widgets represents the instances currently placed on the grid. Restoring only the catalog cannot recreate the operator's dashboard.

Keep the responsive mode in mind during visual checks. In stretch mode, cells grow or shrink with the containing element. In fixed mode, cellSize is static and an overflowing grid can scroll. A layout that appears different at another viewport size may still contain the correct saved row and column positions.

How do you verify the complete fix?

  1. Add two instances of the iframe widget. Give each a different URL and confirm that each value appears in its own viewParams.src.
  2. Record both stable widget names. Move and resize each widget, then confirm that position and span changes occur without changing those identities.
  3. Delete the first widget. Verify that the remaining widget keeps its original name and URL even though its array index may have changed.
  4. Inspect the JSON sent to Postgres. Confirm that it contains the remaining widget, its src, and its updated layout data.
  5. Restart the session. Confirm that startup processing decodes the stored text and assigns a structured array to props.widgets.
  6. Open configuration mode for the restored widget. Confirm that configuring changes for that instance and that accepting another URL updates the same widget entry and database row.
  7. Repeat the test on a mobile device. Activating a grid cell requires a long press of about one second before dragging an add-widget overlay, so distinguish that interaction from a persistence failure.

Also test size constraints. A requested size below minSize is replaced by the minimum. Clicking one cell applies defaultSize only when the default satisfies the minimum; otherwise the minimum applies. By default, both dimensions are 1x1. Overlap can cause the bin-packing logic to relocate widgets, and a widget can overlap others when no free space remains, so verify stored configuration values separately from their temporary visual placement.

Perspective Dashboard persistence FAQ

Can I pass props.widgets to a Named Query without converting it?

A string Named Query parameter needs serialized JSON text. Convert the QualifiedValues array with TypeUtilities.pyToGson and pass its string representation, then decode the returned text before assigning it to props.widgets.

Does matching the src parameter name save the iframe URL?

No. Matching names defines the interface, but the accepted value must be written into the selected instance's viewParams.src. Confirm the change in props.widgets before diagnosing the database.

Can I use the widgets array index as an instance ID?

No. Deleting or reordering widgets changes indexes. Locate each instance by a unique, persistent name or an installation-defined key stored with that widget.

Does a wrong widget value require official support?

Stop local changes when the correct instance entry and serialized database payload are verified, but the Dashboard restores or mutates a different value, or when reproducible placement behavior crosses the configured grid boundaries. Capture the live props.widgets array before and after the action, the stored JSON, the startup result, the product version, and exact reproduction sequence, then escalate through the manufacturer's official support channel.

Back to blog