When customizing XAMLs in UBIK you have the possibility to access the other levels of data throughout the hierarchy, such as accessing the children or documents below children in a view. However, this functionality is often misused, impacting performance. This article provides an introduction to NextLevel (and similar bindings), as well as outlines the correct usage in order to mitigate performance issues.
The ContentViewModel(s)
To understand NextLevel, it is important to understand the various content viewmodels visible when navigating through UBIK.
The ContentViewModel is the basis for every viewmodel that deals with a single object in UBIK. Several viewmodels derive from this one and inherit all of its functionalities, but the data they deliver varies based on their intended usage.
The above image is a mockup that shows Developer Mode for Level 1 and Level 3 superimposed over each other. It attempts to illustrate the relationship between the ContentPageViewModel, essentially the single object represented by a 'page' in UBIK, and the individual ContentListItemViewModels which represent each of its children.
When browsing to an object, the user is initially met with the ContentPageViewModel. This contains all of the data available to that object, which can also be housed within sub-viewmodels, such as the MROViewModel found on MRO-related objects. One collection found on the ContentPageViewModel is the Children.Items, a collection of all of the objects found 'beneath' this object, on the next hierarchy level. In the standard UI, these are displayed in a child list, however, the items displayed in this list are not fullly fledged objects with all the same functionality as the current 'parent' object, but limited representations of each child.
The ContentListItemViewModel was designed to present the only most significant information of a child object, such as properties (like, IsLocked) and MetaProperties, data (like Title, and SubTitle) and metadata (like added Classifications). This way, the work required to load children while navigating is reduced. Among other things, Children and Document collections are examples of data not delivered to the ContentListItemViewModel.
What is: NextLevel (and other similar bindings)
Accessible from: ContentListItemViewModel
At times, a use-case may require that the UI is customized to show additional data that is not readily available on the ContentListItemViewModel, such as children or documents of the items in the child list. For this reason, it was made possible to access the full data of a child item, while remaining on the parent level, by using the NextLevel binding.
NextLevel is available on ContentListItemViewModel, generally by customizing the item template for the list that displays the children of the current parent object. Using this binding generates a PreviewPageViewModel for that view object, which mimics navigating to those children items and loading their full data, whilst remaining at the current object.
However, any time NextLevel is invoked, the entire data structure for that child is loaded, even if only a single property or next-level child count is displayed in the UI. When used in a ListView item template, the load is multiplied times the number of children present under the parent object.
What is: ParentLevel
Accessible from: ContentViewModel, (therefore inherited by ContentPageViewModel, ContentListItemViewModel)
From any object, it is possible to view data of its parent object, using the ParentLevel binding. This creates a PreviewPageViewModel for the parent object, allowing binding to any of its data in the same way as using NextLevel allows for binding to a child's data.
Note that use of ParentLevel bindings is currently not recommended, due to inefficient processing of parent objects when this request is made.
Example of usage:
What is: LinkedLevel
Accessible from: PropertyViewModel (therefore inherited by SingleOwnerPropertyViewModel)
LinkedLevel differs from the previous examples by not being found on the ContentViewModel, but rather on the viewmodel of an individual property item, specifically, a GUID type. The SingleOwnerPropertyViewModel for a Guid property delivers limited data, such as the Value (GUID), and a readable text string as a DisplayValue. Therefore, if it is required to show more specific data from the linked object, the LinkedLevel binding is necessary.
Example of usage:
The above (when used on a content area or item) will show the value of a property (MP_PROPERTY) that is located on an object linked to the current object via GUID property (LK_LINKED_OBJECT).
NextLevel Properties and Commands
NextLevel and CachedNextLevel
Although NextLevel is often used in customizing, it is actually not recommended and should not be used, due to the potentially significant performance impact of loading multiple levels of data (described in the section Why is NextLevel not recommended?). Instead, UBIK offers the safer alternative, CachedNextLevel, which presents NextLevel data only when it has been explicitly loaded, using the command described in the next section; the LoadNextLevelCommand.
Data can be loaded to the CachedNextLevel in two ways;
- When the LoadNextLevelCommand is explicitly triggered.
- When any NextLevel binding is present in the UI.
The loaded CachedNextLevel data can also be cleared in two ways;
- By explicitly triggering the ClearNextLevelCommand.
- By navigation, which clears the NextLevel data cache.
LoadNextLevelCommand
When a customizing requires showing more data from the children objects than the ContentListItemViewModel allows, the recommended implementation is to use the LoadNextLevelCommand. This generates the additional PreviewPageViewModels on demand, therefore reducing the server workload that would occur simultaneously and perhaps unnecessarily. The suggested usage is some kind of trigger control, such as a button or toggle, that triggers the command and generates the PreviewPageViewModel data, and at the same time, reveals the custom data.
An example of a 'double level view' (which is the colloquial term given to customizings where NextLevel.Children are shown under children objects) can be found below.
ClearNextLevelCommand
The ClearNextLevelCommand can be used to explicitly clear the NextLevel cached data. However, usage of this command is optional and not necessarily required, as simultaneos loading of multiple NextLevel data is what impacts performance on the UBIK client.
Performance Impact & Recommended Usage
Why is NextLevel not recommended?
The performance impact of NextLevel stems from the work done in the background to load a page.
Every time the user navigates to an object, UBIK asks the webservice to deliver the data for that content page. If any NextLevel bindings are present in a customized UI, additional requests are made to load the 'page' for each child. This means that when NextLevel is used on a child item template, the number of webservice requests is equal to the child count (which could be hundreds) + parent object, as opposed to only loading the parent object, which is UBIK's regular behavior.
The situation is worsened when the NextLevel bindings are used in the UI of one or more objects that are routinely used as intermediate points in a hierarchy navigation. For example, if the users are very often clicking to navigate through a hierarchy 4 levels away from the root object, having NextLevel bindings on the UI of objects between these 4 levels may significantly increase the time spent waiting for each of those levels to load. Once the additonal webservice requests have been triggered, they are queued and processed sequentially. This means that browsing through multiple levels, each with NextLevel bindings, can trigger exponential numbers of webservice requests, all of which affects how soon server content arrives on the page.
However, at the same time, we cannot deny that certain use-cases require that child data be displayed that is simply not directly accessible on the ContentListItemViewModel. The next points outline alternate approaches that can deliver equivalent functionality, without compromising on performance.
Showing Children / Documents under a Child
This customizing is sometimes referred to as a 'Double Level View'.
The worst case scenarios described above are driven by having many objects to load simultaneously. Therefore, an alternative approach is recommended, where additional requests to load child data are explicitly triggered by the user, keeping the waiting time for each individual request relatively low.
In general, it is recommended that loading of additional data levels is never done automatically, and instead, should be a deliberate action taken by the user, such as by pressing a button to load the NextLevel for a specific child.
The following example is taken from the UBIKChildItem template. The ... symbol signifies that the rest of the standard template still applies, but has been removed for this example.
UWP
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:behaviors="using:UBIK.WinX.Behaviors"
xmlns:controls="using:UBIK.WinX.Controls"
<!-- Adapted from UBIKChildItem or equivalent xaml -->
<Grid x:Name="RootGrid" ...>
<Grid.Resources>
<tpl:ChildItemTemplateSelector x:Key="ChildItemTemplateSelector" />
</Grid.Resources>
<!-- Child Item / CachedNextLevel Children -->
<Grid.RowDefinitions>
<RowDefinition Height="64" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
...
<!-- CONTENT -->
<Grid Padding="16" ColumnSpacing="16">
...
<Button
Grid.Column="1"
HorizontalAlignment="Right"
Content="LOAD"
Command="{Binding LoadNextLevelCommand}" >
<interactivity:Interaction.Behaviors>
<behaviors:PreventEventBubblingBehavior />
</interactivity:Interaction.Behaviors>
</Button>
</Grid>
<Grid Grid.Row="1" Background="{StaticResource UBIKBorderColor}" MinHeight="1">
<controls:SelectionBoundListView
x:Name="NextLevelChildListView"
ItemTemplateSelector="{StaticResource ChildItemTemplateSelector}"
ItemsSource="{Binding CachedNextLevel.Children.Items}"
.../>
</Grid>
...
</Grid>
Notes:
- In this example, a second, expandable row expands from 1px height (to function as a divider) to host the CachedNextLevel.Children.Items list, when it is loaded.
- The Button triggers the LoadNextLevelCommand, and the additional interactivity prevents the regular NavigateToChildrenCommand from being triggered by interacting with the child item.
- The controls:SelectionBoundListView binds to the CachedNextLevel.Children.Items as its source, and therefore only shows content when the LOAD button is pressed.
- The parent ListView (in the ChildArea) must use the property ItemContainerStyle="{StaticResource UBIKLightListItem}" to allow for resizable child.
Xamarin
<Grid Margin="2">
<Grid.RowDefinitions>
<RowDefinition Height="64" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
...
<StackLayout Grid.Column="3"... >
...
<Button
Text="LOAD"
Command="{Binding LoadNextLevelCommand}" >
</Button>
</StackLayout>
<controls:SfListViewExt
x:Name="NextLevel_Child_List_View"
IsVisible="{Binding CachedNextLevel, Converter={StaticResource NullToNotBool}}"
Grid.Row="1"
Grid.ColumnSpan="4"
HeightRequest="120"
ItemsSource="{Binding CachedNextLevel.Children.Items}"
.../>
</Grid>
Notes:
- In this example, a second, expandable row hosts the CachedNextLevel.Children.Items list, when it is loaded.
- In Xamarin, ListViews take all available space. Therefore the second ListView;
- must be constrained by a HeightRequest attribute.
- should also have an IsVisible attribute, to hide it when the CachedNextLevel is null.
- The Button triggers the LoadNextLevelCommand.
- The controls:SfListViewExt binds to the CachedNextLevel.Children.Items as its source, and therefore only shows content when the LOAD button is pressed.
- The parent ListView (in the ChildArea) must contain the property AutoFitMode="DynamicHeight" to allow for resizable children.
Showing Properties and Property details
It is not necessary to use NextLevel Properties are now attached directly to the ContentViewModel, grouped under several useful collections, such as;
- Properties.VisibleItems - all delivered Properties that are not set as not visible in the data model.
- Properties.EditableItems - Properties that can be edited by the user.
- Properties.ImportantItems - Properties that exceed the Priority Threshold defined in the Profile or Settings page.
- Properties.ImportantLiveItems - important Properties that are specifically Live Value items.
Developer Mode can be used to inspect the full range of properties and commands available to a single Property, and the following are examples of bindings that can be used.
Output label of Selective List integer Property
Properties backed by a Selective List may deliver an integer in their Values[MP_PROPERTY] binding. Using the below example, it is possible to display the human-readable label associated with the specific Selective List item.
Trigger a Command on a Property
The following example triggers the deletion of a property value. When applied to the item template of a child item, this button will delete the value of a property on the child item.
Content="Clear Value"
Command="{Binding Properties.EditableItems[MP_PROPERTY].DeleteValueCommand}">
</Button>
Data Engineering for Best Performance
At times, NextLevel is used because of a lack of knowledge of dedicated functionality already built into UBIK. Below are a few examples of common usages of NextLevel, and recommended alternatives.
Child / Document Count
If a NextLevel binding is used to display a count of children or documents on an item in the child list, it can easily be replaced with a ChildInfo classification, that can easily be configured to display the number of Documents, or Non-document children.
In addition to being more performant, these bindings are more reliable, as they are calculated on the server instead of relying on local data.
Once the classification is added to the data model, the following bindings can be used:
- ClassificationHandler.ChildInformation - can be used to see whether any data is delivered (such as with a NullToColConverter).
- ClassificationHandler.ChildInformation.NumberOfNonDocumentChildren - gives the child count.
- ClassificationHandler.ChildInformation.NumberOfDocuments - gives the document count.
Examples of these bindings can be found in UBIK standard templates, such as in the UBIKObjectIndicators template found in UBIKThemes.