[Feedback] NEW Perspective Dashboard

The Perspective Dashboard component is finally here! Allow your users to add, remove, resize, move, and configure widgets pre-defined by you, the designer, all during runtime.

Overview:

The dashboard component uses a grid system based off of CSS grid specifications to position and place widgets. As the designer, you control the general layout of the grid by specifying the responsive mode, row and column count, cell size (fixed mode only), corresponding row and column gaps, and the widgets available to the user. A user has the ability to toggle a dashboard’s editing state at runtime, and when in edit mode, decide what widgets they want, where they want them, and how they want them configured. Truly, the magic of the dashboard component lies in the runtime user interaction and a widget’s isConfigurable** property. Both are described in detail below.

Properties:

  • grid: fixed | stretch - A mode which defines the responsive behavior of the grid and its cells. In stretch mode, the grid’s dimensions are restricted to the full dimensions of its containing element, and its cells consume one free unit of space, effectively growing and shrinking with the containing element. In fixed mode, the grid’s dimensions can be greater or less than the full dimensions of its containing element, and its cells are given a static size, effectively creating a scrollable grid when cells overflow beyond the containing elements dimensions.

  • isEditing: boolean - Controls the runtime edit mode of the dashboard component. Stays in sync with the edit/play toggle control located at the bottom of the component.

  • editingToggle: boolean - When disabled, hides the built in edit/play toggle control located at the bottom of the component. Disable this if you’d like to implement your own toggle that updates the isEditing prop in a controlled fashion.

  • fixed: FixedModeConfig - Visible when the grid mode is fixed.

    • cellSize: number - Width and height of a grid cell. Exclusively for fixed mode.
    • rowCount: number - The number of rows in the grid.
    • columnCount: number - The number of columns in the grid.
    • rowGutterSize: number - The gap size between grid rows.
    • columnGutterSize: number - The gap size between grid columns.
  • stretch: StretchModeConfig - Visible when the grid mode is stretch.

    • rowCount: number - The number of rows in the grid.
    • columnCount: number - The number of columns in the grid.
    • rowGutterSize: number - The gap size between grid rows.
    • columnGutterSize: number - The gap size between grid columns.
  • widgets: Array<WidgetInUseConfig> - An array of configuration objects for widgets currently in use.

    • WidgetInUseConfig -

      • name - A unique widget name.
      • viewPath - Path to a view.
      • viewParams - Parameters passed to the view at the specified path.
      • isConfigurable** - Whether this widget is configurable during runtime. If enabled, and in dashboard edit mode, and the widget is selected, presents the user with a configure icon (pencil) in which when active sets a configuring boolean view parameter passed to the specified view to true. The view can then use this configuring parameter to go into ‘configuring’ mode (designed by you), allowing users to configure the widget. The purpose of this configuring parameter is to avoid having to make a separate widget for various configurations of the same view. (See configuring a widget below under interaction)
      • header: WidgetHeaderConfig - Configuration object for the widget header.
        • enabled: boolean - When enabled renders the widget header.
        • title: string - The header title to display.
        • style: StyleObject - Style to be applied the widget header.
      • body: WidgetBodyConfig - Configuration object for the widget body.
        • style: StyleObject - Style to be applied the widget body.
      • minSize: WidgetSizeConfig - Specifies the widgets minimum allowable size. Users may not resize widgets below these dimensions.
        • columnSpan: number - The minimum allowable columns that this widget may span.
        • rowSpan: number - The minimum allowable rows that this widget may span.
      • position: WidgetPositionConfig - A widget position object that is automatically updated whenever a widget is added, resized, or moved.
        • rowStart: number - The top position of the widget.
        • rowEnd: number - The bottom position of the widget.
        • columnStart: number - The left position of the widget.
        • columnEnd: number - The right position of the widget.
    • style: StyleObject - Style to be applied the widget.

  • availableWidgets: Array<AvailableWidgetConfig> - An array of widgets as configuration objects that are available to the user. When a widget is added to the dashboard via the add widget modal, this configuration object is copied to the widgets in use array, and act as the widgets defaults.

    • AvailableWidgetConfig -

      • viewPath - Path to a view.
      • viewParams - Parameters passed to the view at the specified path.
      • isConfigurable** - Whether this widget is configurable during runtime. If enabled, and in dashboard edit mode, and the widget is selected, presents the user with a configure icon (pencil) in which when active sets a configuring boolean view parameter passed to the specified view to true. The view can then use this configuring parameter to go into ‘configuring’ mode (designed by you), allowing users to configure the widget. The purpose of this configuring parameter is to avoid having to make a separate widget for various configurations of the same view. (See configuring a widget below under interaction)
      • defaultSize: WidgetSizeConfig - Specifies the widgets default size adding a widget with no size specified.
        • columnSpan: number - The default columns that this widget will span.
        • rowSpan: number - The default rows that this widget will span.
      • minSize: WidgetSizeConfig - Specifies the widgets minimum allowable size. Users may not resize widgets below these dimensions.
        • columnSpan: number - The minimum allowable columns that this widget may span.
        • rowSpan: number - The minimum allowable rows that this widget may span.
      • category: string | number - Groups this widget in the specified category in the add widget modal.
      • name: string | number - A unique name to provide this widget. This name is used in the add widget modal. If no name is specified, its value will be blank. It is essentially a required property.
      • header: WidgetHeaderConfig - Configuration object for the widget header.
        • enabled: boolean - When enabled renders the widget header.
        • title: string - The header title to display.
        • style: StyleObject - Style to be applied the widget header.
      • body: WidgetBodyConfig - Configuration object for the widget body.
        • style: StyleObject - Style to be applied the widget body.
      • style: StyleObject - Style to be applied the widget.

Interaction:

Arguably the ‘fun part’ of the dashboard component. Users can add, remove, move, resize, and configure pre-defined widgets at runtime both on desktop and mobile devices. There may be some minor variances in how a user can interact with their dashboard between desktop and mobile devices (explained below), but the principle is still the same. We use a bin packing algorithm to do our best to ensure widgets do not overlap when being added, resized, and moved. If there happens to be no space for a widget, it will overlap others so that it is placed within the grid.

Adding a widget

There are two ways a user can add a widget, first, by effectively clicking a single grid cell, and second, by dragging a grid cell to create an add widget overlay. Both result in displaying the add widget modal which provides a searchable list of all of the available widgets a user may add. Effectively dragging a grid cell will create an add widget overlay that specifies the desired dimensions of the widget to add. If the desired widget position overlaps other widgets. The overlapped widgets will be moved to any available space.

Note that when adding a widget, if the desired dimensions are less than the configured minimum dimensions, the desired dimensions will get overridden by the minimum dimensions. If a single grid cell is effectively clicked, the configured default dimensions will be applied, if and only if, the default meet the required minimum dimensions, otherwise the minimum dimensions are applied. By default, the minimum and default dimensions for a widget are 1x1.

On mobile devices, activating a grid cell requires a long-press of about a second. Once a grid cell is activated, you can then drag to create the add widget overlay.

Removing a widget

To remove a widget, select the widget and effectively click the x icon in the top right corner of the widget. You will then be prompted with a confirmation modal.

Moving a widget

To move a widget, first select (effectively click) the widget so that it becomes active (indicated by the dashed blue border). After selecting the widget, drag the widget to the desired position. As you move the widget any overlapped widgets will be packed into the first available space.

Resizing a widget

To resize a widget, simply select the widget you’d like to resize and drag one of the resize handles. If, while resizing, the widget overlaps other widgets, the overlapped widgets will be packed into the first available space.

Configuring a widget

To configure a configurable widget, select the widget you’d like to configure. Then, effectively click the edit icon (pencil) in the top right corner of the widget. When the widget is configuring, the widgets border will change colors from blue to orange, and the configuring view param passed to the specified view will be true.

Toggling editing mode

The dashboard component has a built in edit/play toggle located at the bottom of the component. Users can enter into and out of editing mode by effectively clicking this control. You can also remove this control entirely and implement your own by configuring the editingToggle component property. See the properties section above for more details.

Populating Widgets and Saving Runtime Edits

You may notice that the runtime edits a user makes to their dashboard do not persist when the session restarts. The current solution here is to add a property change script on the widgets prop to listen for changes and then write that value back to a database along with any user information derived from the active session. The value of the widgets prop will be an array of QualifiedValues, which you’ll need to handle accordingly.

In similar fashion, consider adding an onStartup event action that will query the database and then populate the widgets prop with the users last saved configuration and optionally populate the availableWidgets prop (possibly for varying user roles).

It’s apparent that this solution requires some effort, so as always we are open to any ideas or suggestions you may have to help make this processes easier. Please, feel free to comment below.

Wrapping Up

Hopefully that’s enough information to get you started. We still have some work to do with this component, including the refinement of the bin packing algorithm, improving user interaction, and preventing widgets from jumping beyond the boundaries of the grid, and fixing any bugs you may encounter. We appreciate your patience as we work towards incremental improvements. As always, If you have any questions or suggestions, feel free to post them here.

14 Likes

This looks great, can’t wait to try it out! Is there a way to save layouts between sessions? Or will it reset to a default view each session?

Hi,

That’s a great question. I forgot to include this in the initial write up. See the section on ‘Populating Widgets and Saving Runtime Edits’. Long story short, currently it’ll require that you save the session information to a database and then reinitialize the information on startup.

-Y

from which version is it available?

https://inductiveautomation.com/downloads/ignition/8.0.5-Nightly

2 Likes

Looks good! I can’t wait for 8.05 to be on a stable build (I mean a “for use in production”).

2 Likes

I’m having trouble getting it to work on iPhone using Chrome or Safari, is there something more I need to tweak?

Perhaps. Can you provided more detail? We did rigorously test on multiple iPhone, iPad, and Android devices before the release without issue. Even so, it wouldn’t be too much of a surprise if you found something given the diversity of devices that are available.

1 Like

Must have been a cacheing issue because I cannot replicate what I saw myself! thanks for the quick response @ynejati

The “style” property (which is valid and can be added manually) does not appear by default in the PROPS sections. I would request that it be added for clarity (and so the shortcut style class chooser icon can be there).

Super awesome! Literally just started building something that would do this. So excited to see it as a standard component.

1 Like

You are completely right. Thank you for the heads up!

Wish there was a demo project or some resources available in the Ignition Exhange!

1 Like

Regarding “Saving Runtime Edits”.

As an alternative to using property change script on the widget props, maybe the following can be utilized:

All configuration changes inside the widget is sent out of the View as an In/out parameter. This way, the user can configure the widget like normally, and the parameters are sent out of the widget in run time.

Then by using a save button, one can simply save the “widget” array JSON directly to the database.
Using a load button or onStartup event, one can populate the dashboard by restoring the the “widget” array.

Or am i missing something here?

No, I don’t think you’re missing anything. That’s definitely another, possibly better, way of going about it. I approve.

2 Likes

I am struggling when trying to message the individual widgets.

Maybe each widget on the dashboard could have its unique ID passed into the widget-view. (The same way you do with the “configuring” parameter.)

This way, I can on the individual widget-view, call a common popup (tagPicker), then message the ‘page’ with the new tag path like this:

system.perspective.sendMessage('updateTagPath', {'widgetID':widgetID,'tagPath':tagPath}, 'page')

The message handler could then check if the widgetID is the correct one.

Here you can see the graphical interface for what i am talking about.

Edit:
I actually solved this by other means. Because only one widget can be in editing mode at any time. Only the widget with active “editing mode” will run the recieve script in the message handler

1 Like

That’s good to know.

Thanks for posting all this stuff. I haven’t had time to play around with this control yet but I’m sure I’ll save time by simply reading what you said.

This looks amazing! We are still using Vision in 7.9. I can’t wait until we can upgrade to 8.

1 Like

Is there a way to dynamically change the header of a widget.
In the example below I configured a widget view for a udt.

image

When I configure the widget I would like to enable the user to enter the header text as shown below
image

Any suggestions on how to do it?

When I first read this I thought to myself “that sounds easy”. Granted, I hadn’t played around with the control yet so I didn’t know what I was doing.

You probably already know this next part but just to make sure we’re on the same page…

You populate the “availableWidgets” property (array) with a list of views you want to use as widgets. When the user creates an instance of the widget at runtime you will see a new widget in the “props.widgets” property (array).

I found that you can bind the title of the widget window to a tag to pass a value into it. The problem at that point is figuring out how to marry your widget instances to the title your user has assigned to them. I feel like there’s a way to do this with a string array tag but I haven’t done much with those so I’m not sure if there’s a limitation there.

It seems like you should be able to create a string array tag with a node for each widget on your form. You might even be able to dynamically add and remove elements from the string array tag as widgets are added. From there it’s just a matter of figuring out how to marry your widgets to your string array which is something I"m not sure how you do (if you can).