Jump to: navigation, search

Object hierarchy in XAML: NextLevel, ParentLevel, LinkedLevel


Revision as of 12:25, 17 July 2023 by REP (Talk | contribs) (Why is NextLevel not recommended?)

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.

IC Hint square.pngThe following sections are described using NextLevel as this is the most common binding, however, other similar hierarchy manipulations exist, such as ParentLevel or LinkedLevel, which shall be individually described further on.


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. As the 'baseclass' for object viewmodels in UBIK, this viewmodel exposes the most important properties (like IsLocked or BranchSyncState), commands, object data (like Title and SubTitle), MetaData (like Classifications), and certain collections (like PossibleChildren or (Meta)Properties that have been added in UBIK Studio). Several viewmodels derive from this baseclass and inherit all of its functionalities, but each is configured to deliver additional data. What each viewmodel delivers is based on their intended usage, and this is why, at times, customizings require expanding the scope of delivered data (for example, through use of NextLevel) to present something that is not delivered by default.


When browsing to an object, the user is initially met with the ContentPageViewModel, a highly expanded derivate of the ContentViewModel. This expanded viewmodel forms the base for an entire object-page in UBIK. It contains all of the data available to that object, which can also be housed within sub-viewmodels, such as the MROViewModel found on task-related objects, or the GeoDataViewModel. If the object has any children, these will be delivered by the ContentPageViewModel as the Children.Items collection; a collection of all objects (that are not documents) found 'beneath' this object, on the next hierarchy level, displayed as a child list in the standard UI. However, it is important to note that the child items displayed in this list are not fully-fledged objects with all the same functionality as the current 'parent' object, but limited representations of each child.


If the object has any children, these are shown as a list of ContentListItemViewModel items. This viewmodel derives from the ContentViewModel to present the only most significant information of each child object. This way, the work required to load a page (including the list of its children) is reduced. Among other things, the Children and Document collections are examples of data not delivered to the ContentListItemViewModel, and this is why it is not immediately possible to show the children and documents of each child list item in a standard UI.


Over time, these viewmodels evolve to meet the needs of projects and customers. For example, with the release of UBIK 4.0, the Properties collections were moved to the ContentViewModel (thereby being made available on all derived viewmodels) after customizing of items to present specific Property values (previously requiring NextLevel bindings) became more common.


ContentViewModeldiagram.jpg

The above image is a mockup that shows Developer Mode Level 1 and Level 3 superimposed over each other. It attempts to illustrate the relationship between the ContentPageViewModel, essentially the single 'parent' object that forms the basis for a single 'page' in UBIK, and the individual ContentListItemViewModels, which represent each of its children in a limited way.


What is: NextLevel (and other similar bindings)

What is: NextLevel

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 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.

IC Attention.pngNot all objects have a link to a parent object, for example, Root and Infrastructure objects don’t have parents. Also, an object which is otherwise nowhere to be found in the hierarchy can be delivered as a child of a query. Finally, an object may be delivered as a child under various parent objects. In any of these cases, it is therefore ineffective to bind to the ParentLevel, or may return no 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:

TextBlock Text="{Binding ParentLevel.Title}" />

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:

TextBlock Text="{Binding Properties.VisibleItems[LK_LINKED_OBJECT].LinkedLevel.Properties.VisibleItems[MP_PROPERTY].DisplayValue}}" />

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).

Why is NextLevel not recommended?

The performance impact of NextLevel comes from the work done in the background to load an object.

Every time the user navigates to an object, UBIK asks the webservice to deliver the data for that content page, and it is designed to quickly deliver all the data for the current object. To maximize efficiency, and because any object could theoretically have hundreds of children objects, each child item is not delivered with its full scope of data, but rather, as the ContentListItemViewModel, designed to provide only the most commonly needed information.

However, if any NextLevel bindings are present in a customized UI, additional requests are made to also load the entire 'page'-worth of data for each child. This means that when NextLevel is used on a child item template, the number of webservice requests that are queued is equal to the parent object + child count (which could be hundreds), and the client must process each in turn. This is opposed to UBIK's regular behavior, which is optimized to only load the scope of one object, at one time.


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, imagine a use-case where the user often has to navigate through the hierarchy to find an object they need to work on, which is 4 levels away from the root object. Any 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, because once the additional 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, some of which may not even be required, and together, all these add up to increase the time spent until 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.

Recommended Usage

The following sections document the best ways to bind to various kinds of data from the children objects.

NextLevel Properties and Commands

In some cases, there may be no way around generating PreviewPageViewModels for the children under an object, such as if the user wants to browse their children or document items, without navigating into these objects. The following commands outline the best way to implement this behavior with least impact to performance.

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 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.

The CachedNextLevel is a cache of NextLevel data that has been loaded. Simply accessing this data is 'safe' in terms of client performance, because, as described earlier, it is the simultaneous loading of multiple hierarchies that causes performance to suffer. Therefore, the method used to fill this cache is where customizers should be careful, as data can be loaded to the CachedNextLevel in two ways;

  • When the LoadNextLevelCommand is explicitly triggered (ie., the correct way).
  • When any NextLevel binding is present anywhere in the currently loaded UI templates (ie. the 'bad' way).


The LoadNextLevelCommand technique is recommended because it is a way of triggering loading requests that is controlled and contained. The second technique is problematic because it will occur on any currently active template (such as ContentArea, ChildArea, ChildItem, etc., with item templates being the worst scenario), that binds to NextLevel;

  • without the user knowing: the UI is simply unresponsive while loading,
  • without being in their control: there is no way to not trigger the loading, if they are currently not interested in the NextLevel data, and
  • potentially for a large number of objects: if the NextLevel binding exists on the item template used by many children items, increasing unresponsive time.


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 in the How-Tos below.

ClearNextLevelCommand

The loaded CachedNextLevel data can also be cleared in two ways;

  • By explicitly triggering the ClearNextLevelCommand.
  • By navigation, which clears the NextLevel data cache.


The ClearNextLevelCommand can be used to explicitly clear the NextLevel cached data. However, usage of this command is optional and not necessarily required, because, as previously stated, accessing the already-loaded data does not impact performance. This command is additionally not required because navigating away from a page clears the cache automatically.

How To:

Show NextLevel Children/Documents

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:tpl="using:UBIK.WinX.Templates"
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

<!--  Adapted from UBIKChildItem or equivalent xaml -->
<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.


Show NextLevel 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.

Show 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.

IC Attention.pngSince v4.0, it is no longer required to use NextLevel to to display data from the property item view model. Bindings referencing NextLevel.PropertyItems are obsolete and should be removed from all customizings.


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.

Text="{Binding Properties.VisibleItems[MP_STATUS_PROPERTY].DisplayValue}"

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.

<Button
   Content="Clear Value"
   Command="{Binding Properties.EditableItems[MP_PROPERTY].DeleteValueCommand}">
</Button>
IC Attention.pngThe above xaml is a UWP example, on Xamarin the button text attribute is Text, instead of Content.