Function expressions in variable replacements

This post is an introduction to the enhancements made to the variable replacement functionality in Smart Office 10.2. The variable replacement syntax now supports function expressions that adds the possibility to modify values during a variable replacement.

Function expressions are currently supported in many places that uses variable replacements. Some examples are M3 toolbox shortcuts, Mashup parameters and links, Mashup controls such as WebBrowser and FileBrowser and data service parameters. Smart Links do not support function expressions in the current 10.2 release but this has been addressed and support for function expressions in Smart Links will ship in the next fix pack for Smart Office 10.2.

Background

The variable replacement syntax has been available in Smart Office for a long time and is used to build string values from template strings that contain variables. This functionality is used in many places in Smart Office and a common scenario is to build link URIs with query parameters using replacement variables. A replacement variable is enclosed in either curly brackets {VARIABLE} or angle brackets <VARIABLE> depending on where it is used. In some cases both types of brackets are supported.

One limitation of the variable replacement syntax is that the value cannot be changed in any way during the replacement. We have received a couple of requests for more functionality when using variable replacements and the function expressions is an attempt to solve that. An example requirement is to be able to do a substring operation on an item number and then use the substring in a search.

Introduction

Before getting into more details I will just show an example expression and explain the different parts of the expression.

Example 1:
{=Substring(GetValue(‘ITNO’), 2)}

The expression is enclosed in curly brackets that marks it as a variable replacement. In some cases you might need to use angle brackets instead but everything else in the expression stays the same. In subsequent examples I will use curly brackets.

The first difference compared to a normal variable replacement is the equals sign following the opening bracket. The equals sign indicates that the content should be interpreted as an expression rather than a variable name. If the equals sign is missing the content will be used as variable name, even if it happens to be an expression.

Next are two nested function calls to two of the built-in functions called Substring and GetValue. The GetValue function will be evaluated first and it will simply get the value for “ITNO” field. The Substring function will be evaluated next and it will take the output of the GetValue function and create a substring from index 2. The substring will then be used as the replacement value. If the value for ITNO was “XY12345” the result of the expression would be “12345”.

To further explain how the GetValue function works it should be noted that the two expressions below are equivalent. It does not make sense to use an expression with the GetValue function if no other function is used though, in that case the basic replacement syntax is more efficient.

Example 2:
{ITNO}
{=GetValue(‘ITNO’)}

Expression syntax

The expression syntax is quite powerful but there are also a lot of limitations so I’ll try to define what is possible and what’s not.

  • An expression may contain one or more function calls.
  • Function calls can be nested but not chained.
  • The supported data types for hardcoded parameters are String, int and bool.
  • The result of an expression must be a string, use ToString if a function returns something that is not a string.
  • Strings must be quoted using single quotes.
  • Other data types are supported as parameters if they are the return value from another function.
  • No other language features such as properties, indexers or operators are supported.
  • Built-in functions can be used without qualification.
  • Any static method with a supported function signature can be used with a fully qualified class name.

If you find these restrictions too limiting or have suggestions for improvements let us know in the comments below.

The following example shows some deeper function nesting. The example URL encodes the query parameter for a Google search URI.

Example 3:
{=Concat('https://www.google.com/search?q=', UrlEncode(GetValue('Query')))}

Expression functions

The variable replacement expressions can use a set of built-in functions that are defined in the Mango.Core.Util.ExpressionFunctions class. If you have access to the Smart Office SDK documentation you can find the documentation for all functions there. I have also added a short summary of all functions in the end of this post.

Most of the built-in functions are just convenience wrappers for existing .NET framework methods. There are a methods for string manipulation, data type conversions, encoding / decoding etc. If you are missing some functions and there is a static method available in the .NET framework, the Smart Office framework or your own feature code, you can use that as long as the method signature is supported. Below are some examples using the built-in functions. Note that some of the examples with only hardcoded parameters don’t really make sense and are just intended to show how they work. In most cases you would have at least one call to the GetValue function.

Example 4:
{=Concat('Hello', 'World')}
{=ToUpper(GetValue('NAME'))}
{=Replace('ABC123', '123', 'DEF')}
{=Replace(GetValue('NAME'), ' ', '_')}
{=Substring('XZ9876', 0, 2)}
{=ToString(Equals('x', 'y'))}
{=ToShortDateString(GetNow())}

There is also a set of functions specific to Smart Office. The functions that are specific to Smart Office are GetValue, GetUser, GetUserWithDomain, GetUserContextValue and GetProfileValue. The most important function is GetValue that is used to get a named value in the same way you do with a standard variable replacement.

Example 5:
{=GetUser()}
{=GetUserContextValue('M3', 'CurrentCompany')}
{=GetProfileValue('EnterpriseSearch', 'Companion', 'Url')}

To use functions that are not built-in you need to use the fully qualified name of the class. The example below shows how to use the built-in function IsNullOrEmpty compared to using the static method on the .NET String class.

Example 6:
{=ToString(IsNullOrEmpty(''))}
{=ToString(System.String.IsNullOrEmpty(''))}

Expression Tester

One way to test variable replacement expressions is to use them in event parameters in a Mashup. To make this easier I have created a simple Mashup called ExpressionTester where you can interactively modify and test different expressions.

The Expression Tester Mashup contains four fields. The first two are used to define a parameter name and a parameter value. This can be used for testing the GetValue function. If you don’t use the GetValue function in an expression you can skip the first two fields. The third field is for the actual expression. The expression must be tested using curly brackets in the Mashup but you can replace those with angle brackets for use in other places if necessary when the expression works as expected. The final fourth field shows the result of the expression when the Execute button is clicked.

The Mashup starts with some default values and if you just click the Execute button the result should be the text MANGO in uppercase. If it does not work verify that the Smart Office version is 10.2 or later.

This example binds the expression to the event parameter value to make it possible to test different expressions. In normal cases you would just hardcode the expression in the Value attribute.

Note that if a value in a XAML attribute contains only an expression it needs to be escaped using {} so that it is not interpreted as a markup extension. If the expression is used within a string it is not necessary to escape it.

FNCEXPR_01

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:Mango.UI.Controls;assembly=Mango.UI" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI">
   <Grid.Resources></Grid.Resources>
   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="1*" />
   </Grid.ColumnDefinitions>
   <Grid.RowDefinitions>
      <!-- 0 Parameter Name -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="5" />
      <!-- 2 -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="10" />
      <!-- 4 Parameter Value -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="5" />
      <!-- 6 -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="30" />
      <!-- 8 Expression -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="5" />
      <!-- 10 -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="10" />
      <!-- 12 Result -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="5" />
      <!-- 14 -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="5" />
      <!-- 16 -->
      <RowDefinition Height="1*" />
   </Grid.RowDefinitions>
   <Label Grid.Row="0" Content="Parameter Name" />
   <TextBox Grid.Row="2" Name="textBoxName" Text="TEST" />
   <Label Grid.Row="4" Content="Parameter Value" />
   <TextBox Grid.Row="6" Name="textBoxValue" Text="mango" />
   <Label Grid.Row="8" Content="Expression" />
   <TextBox Grid.Row="10" Name="textBoxExpression" Text="{}{=ToUpper(GetValue('TEST'))}" />
   <Label Grid.Row="12" Content="Result" />
   <TextBox Grid.Row="14" Name="textBoxResult" />
   <Button Grid.Row="16" Style="{DynamicResource styleButtonPrimary}" Content="Execute" VerticalAlignment="Bottom" HorizontalAlignment="Right" Name="buttonExecute" IsDefault="True">
      <Button.CommandParameter>
         <mashup:Events>
            <mashup:Event SourceEventName="Click" Target="{mashup:SetProperty ElementName=textBoxResult, Path=Text}">
               <mashup:Parameter SourceKey="{Binding Path=Text,ElementName=textBoxName}" Value="{Binding Path=Text,ElementName=textBoxValue}" />
               <mashup:Parameter SourceKey="Value" Value="{Binding Path=Text, ElementName=textBoxExpression}" />
            </mashup:Event>
         </mashup:Events>
      </Button.CommandParameter>
   </Button>
</Grid>

Basic Mashup Example

This example is similar to the Expression Tester Mashup but with a hardcoded expression which makes a bit more sense for a real scenario. The example demonstrates how to use substring on a parameter in a Mashup event.

FNCEXPR_02

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:Mango.UI.Controls;assembly=Mango.UI" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI">
   <Grid.Resources></Grid.Resources>
   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="1*" />
   </Grid.ColumnDefinitions>
   <Grid.RowDefinitions>
      <!-- 0 Parameter Name -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="5" />
      <!-- 2 -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="10" />
      <!-- 4 Parameter Value -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="5" />
      <!-- 6 -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="30" />
      <!-- 8 Result -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="5" />
      <!-- 10 -->
      <RowDefinition Height="Auto" />
      <RowDefinition Height="5" />
      <!-- 12 -->
      <RowDefinition Height="1*" />
   </Grid.RowDefinitions>
   <Label Grid.Row="0" Content="Parameter Name" />
   <TextBox Grid.Row="2" Name="textBoxName" Text="ITNO" />
   <Label Grid.Row="4" Content="Parameter Value" />
   <TextBox Grid.Row="6" Name="textBoxValue" Text="XY12345" />
   <Label Grid.Row="8" Content="Result" />
   <TextBox Grid.Row="10" Name="textBoxResult" />
   <Button Grid.Row="12" Style="{DynamicResource styleButtonPrimary}" Content="Execute" VerticalAlignment="Bottom" HorizontalAlignment="Right" Name="buttonExecute" IsDefault="True">
      <Button.CommandParameter>
         <mashup:Events>
            <mashup:Event SourceEventName="Click" Target="{mashup:SetProperty ElementName=textBoxResult, Path=Text}">               
            <mashup:Parameter SourceKey="{Binding Path=Text,ElementName=textBoxName}" Value="{Binding Path=Text,ElementName=textBoxValue}" />
               <mashup:Parameter SourceKey="Value" Value="{}{=Substring(GetValue('ITNO'), 2)}" />
            </mashup:Event>
         </mashup:Events>
      </Button.CommandParameter>
   </Button>
</Grid>

MForms toolbox shortcuts

The MForms toolbox is another example of where the function expressions can be used. The example below will do a Google search for the item description (FUDS) converted to lowercase in the MMS001 program. For the toolbox shortcuts you can use either curly brackets or angle brackets.

Example 7:
https://www.google.com/search?q={=ToLower(GetValue('FUDS'))}

FNCEXPR_03

Function reference

For those who do not have access to the Smart Office SDK documentation you can check this function reference. It lists all built-in functions with their parameters and a summary description.


Concat(String, String) Concatenates two specified instances of String.
Contains(String, String) Returns a value indicating whether a specified substring occurs within a string.
EmptyIfNull(String) Converts a null string to an empty string.
EmptyIfWhiteSpace(String) Converts a string that only contains white space to an empty string.
EqualsIgnoreCase(String, String) Determines whether two instances of String object have the same value ignoring case.
EqualsIgnoreCaseInvariant(String, String) Determines whether two instances of String object have the same value ignoring case using the invariant culture.
EqualsInvariant(String, String) Determines whether two instances of String object have the same value using the invariant culture.
GetNow() Gets the current local date and time.
GetNowUtc() Gets the current UTC date and time.
GetProfileValue(String, String, String) Gets a value from the system profile.
GetUser() Gets the current user ID.
GetUserContextValue(String, String) Gets a value from the user context for the specified application.
GetUserWithDomain() Gets the current user ID including the domain.
GetValue(String) Gets a value.
IsNullOrEmpty(String) Indicates whether the specified string is null or an Empty string.
IsNullOrWhiteSpace(String)
Negate(Boolean) Negates a bool value.
NullIfEmpty(String) Converts an empty string to null.
NullIfWhiteSpace(String) Converts a string that only contains white space to null.
ParseBool(String) Converts a string to a bool.
ParseDate(String, String) Converts the specified string representation of a date and time to its DateTime equivalent using the specified format.
ParseDecimal(String) Converts the string representation of a number to its Decimal equivalent.
ParseDecimalInvariant(String) Converts the string representation of a number to its Decimal equivalent using the invariant culture.
ParseDouble(String) Converts the string representation of a number to its double-precision floating-point number equivalent.
ParseDoubleInvariant(String) Converts the string representation of a number to its double-precision floating-point number equivalent using the invariant culture.
ParseFloat(String) Converts the string representation of a number to its single-precision floating-point number equivalent.
ParseInt(String) Converts the string representation of a number to its 32-bit signed integer equivalent.
ParseLong(String) Converts the string representation of a number to its 64-bit signed integer equivalent.
ParseMashupDate(String) Converts the specified string representation of a date in the Mashup format (yyyy-MM-dd).
ParseShort(String) Converts the string representation of a number to its 16-bit signed integer equivalent.
Replace(String, String, String) Returns a new string in which all occurrences of a specified string in a string instance are replaced with another specified string.
Substring(String, Int32, Int32) Retrieves a substring from a string instance. The substring starts at a specified character position and has a specified length.
Substring(String, Int32) Retrieves a substring a string instance. The substring starts at a specified character position and continues to the end of the string.
ToLower(String)
ToLowerInvariant(String)
ToMashupDateString(DateTime) Converts a DateTime to a date string using the Mashup date format (yyyy-MM-dd).
ToShortDateString(DateTime) Converts a DateTime to a short data string.
ToShortTimeString(DateTime) Converts a DateTime to a short time string.
ToString(Object) Converts an object to a string
ToString(Boolean) Converts an object to a string
ToString(Int32) Converts an int to a string
ToString(Int64) Converts an long to a string
ToString(Double) Converts an double to a string.
ToString(Double, String) Converts an double to a string using the specified format.
ToString(Decimal) Converts an decimal to a string.
ToString(DateTime) Converts a DateTime to a string.
ToString(DateTime, String) Converts a DateTime to a date or time on the specified format.
ToStringInvariant(Double, String) Converts an double to a string using the specified format using the invariant culture.
ToStringInvariant(Double) Converts an double to a string using the invariant culture.
ToStringInvariant(Decimal) Converts an decimal to a string using the invariant culture.
ToStringInvariant(DateTime) Converts a DateTime to a date string using the invariant culture.
ToUpper(String) Converts a string to uppercase.
ToUpperInvariant(String)
Trim(String) Removes all leading and trailing white-space characters from a String object.
TrimEnd(String) Removes all trailing occurrences of a set of characters specified in an array from the current String object.
TrimStart(String) Removes all leading occurrences of a set of characters specified in an array from a String object.
UrlDecode(String) Converts a string that has been encoded for transmission in a URL into a decoded string.
UrlEncode(String) Encodes a URL string using UTF-8.

7 thoughts on “Function expressions in variable replacements

  1. Jimmy Ehrnström

    Thank you for yet another interesting blog post!

    I tried creating a shortcut on my canvas using one of your examples:
    {=Concat(‘https://www.google.com/search?q=’, UrlEncode(GetValue(‘Query’)))}
    Unfortunately this did not work but gave me an error saying that the given url short did not exist. One adjustment I made was to replace the GetValue function with GetUser().
    Should this be able to work as a canvas shortcut or only from within for example a Mashu?

    Best regards!

    Reply
    1. norpe Post author

      Variable replacement has never been supported for Canvas shortcuts since there never was any data that could be used for the variables. With the function expressions it makes more sense, at least for values such as the current user and perhaps values from the current profile.

      I will add support for this in the next fixpack. If you find any other places where you think this should be supported just add another comment.

      Reply
  2. Johan Lindström

    Thanks for interesting info, I have been able to utilize this on several occasions already, for example to get a year to enter into a new program with dates being YYMMDD
    {=Concat(’20’,Substring(GetValue(‘RGDT’),0,2))}
    In the above example I work with MFormsAutomation to open the program and enter the value in the YEA4 field.
    Somewhat related I have found an issue to work with MFormsAutomation and Swedish characters Å, Ä and Ö. Any ideas on how variable replacement should be done with non standard characters. Using the “UrlEncode” I can convert an Ä into %c3%84 but in the program I have opened this makes no sense.

    Reply
    1. karinpb

      Hi,
      Since the XML is an URL parameter you might need to escape the entire XML (and not individual values). The values within the XML will be fine as long as they don’t brake the XML and Å,Ä,Ö are valid but to escape the XML you must escape the entire value. Eg. mforms://test?xml=parameterValue. The entire parameterValue should be escaped. If you are in a Mashup variable substitution are escaped so perhaps the ÅÖÄ gets escaped twice. The log will show you the automation URL (if you have it on debug). Also consider using bookmarks / adding bookmarks to the program since that is the recommended approach.

      Reply
  3. Jesper

    Hi Karin
    I have a Mashup where I use two Datepicker to filter orderlines by delivery date… from date and to date..
    In the secound datepicker (DateTo) I have added date of today as default date.
    This I have done by adding namespace SYS and UTIL to the mashup
    XAML code looks like this:

    Now in the first datepicker (DateFrom) I want the default date to be 45 days before today (or before DateTo)
    Is this possible In Mashup Designer or do I need SDK?

    Thanks in advance

    Reply
  4. Damian

    Hello,
    Have you ever converted a LMTS field, ie EPOCH time in a XAML mashup to a readable date time format?

    Any help in doing this would be appreciated.
    Thank you
    Damian

    Reply
  5. Len

    Hi,
    Is it possible to round off values in Mashup? Like 18.13853 would be 18.14 if round off to 2 decimal places.

    Thanks,
    Len

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s