User Interface Extensibility Framework Development Practices

Overview

Local development folder

M-Files supports the use of a local folder for developing User Interface Extensibility Framework applications.

Asynchronous API programming

Whilst M-Files COM API calls are typically synchronous (i.e. the application doesn’t continue until the API call completes), the M-Files Vault object exposes a property named Async which can be used to make asynchronous calls. Using asynchronous calls - rather than synchronous calls, which wait for the process to finish before continuing - ensures that the user interface doesn’t appear to “lock”, and the user experience remains fluid.

Using asynchronous calls is currently optional when targeting the M-Files Desktop client. It is mandatory when targeting M-Files Classic Web.

Debugging

Whilst the majority of code which executes within a User Interface Extensibility Framework application cannot be directly debugged, JavaScript code that executes within a dashboard can be debugged. More information can be found on the Debugging UIX applications page.

Event Registration and Entry Points

The User Interface Extensibility Framework is largely event-driven; each module must register a callback function for the events it wishes to be notified about. Reacting to events requires registration of handler functions. These handler functions are automatically called by the framework when the appropriate events fire.

In addition, modules and dashboards require correctly-named methods to exist to be informed when they start.

Platform Targeting

Whilst User Interface Extensibility Framework applications typically target the M-Files Desktop client, since M-Files 11.2.4320.49 it has been possible to additionally target the M-Files Classic Web. More information can be found on the Platform Targeting page.

The version of the User Interface Extensibility Framework that is available in the M-Files Classic Web is a subset of that available in the Desktop client. Additionally, M-Files API support is limited.

Deployment

User Interface Extensibility Framework applications are deployed using the M-Files Admin tool.

Tips and Tricks

Exception handling within the User Interface Extensibility Framework

Additional information on exception handling can be found within the UI Extensibility Framework documentation. This includes sections on throwing custom exceptions and interacting with exceptions that are returned from API calls.

Additionally, the Asynchronous API Programming page contains a section on dealing with exceptions from asynchronous API calls.

Optional parameters

When calling an API method that defines optional parameters, values for all the optional parameters must be provided. For example: a call to GetProperties defines one mandatory and one optional argument, however both must be provided when being called from JavaScript:

// Do we need to update from the server?
var updateFromServer = false;

// Get the property values.
var propertyValues = vault.ObjectPropertyOperations.GetProperties(objVer, updateFromServer);

An example of using this in API calls is shown in the following section.

Using DataFunctionCall in search queries

Creating search conditions from within the User Interface Extensibility Framework can be cumbersome, as the methods involved often define many optional parameters that must be provided when using them from JavaScript.

One such optional argument is the DataFunctionCall argument on SetPropertyValueExpression. The data function call can be used to modify the expression result for matching purposes, and common usage scenarios are explained on this page.

Below is an example of providing an empty DataFunctionCall object when executing a search. This is done when the result does not need to be modified prior to matching.

// Create a search condition.
var searchCondition = new MFiles.SearchConditon();

// Set up the expression for property 456.
searchCondition.Expression.SetPropertyValueExpression(456, // The ID of the property to match.
	MFParentChildBehaviorNone, // No special parent/child behaviour required.
	new MFiles.DataFunctionCall()); // Do not modify the value prior to matching.

// Set up the condition (equals).
searchCondition.ConditionType = MFConditionTypeEqual;

// Set up the value to match against.
searchCondition.TypedValue.SetValue(MFDatatypeInteger, 1);

Below is an example of using DataFunctionCall to search for a document that was created at some point in 2018.

// Create our search conditions.
var searchConditions = new MFiles.SearchConditions();

// Add an object type filter.
{
	// Create the condition.
	var condition = new MFiles.SearchCondition();

	// Set the expression.
	condition.Expression.SetStatusValueExpression(MFStatusTypeObjectTypeID, new MFiles.DataFunctionCall());

	// Set the condition.
	condition.ConditionType = MFConditionTypeEqual;

	// Set the value.
	condition.TypedValue.SetValue(MFDatatypeLookup, MFBuiltInObjectTypeDocument);

	// Add the condition to the collection.
	searchConditions.Add(-1, condition);
}

// Created by (year) = 2018
{
	// Create the DataFunctionCall.
	var dataFunctionCall = new MFiles.DataFunctionCall();
	dataFunctionCall.SetDataYear();

	// Create a search condition.
	var searchCondition = new MFiles.SearchConditon();

	// Set up the expression.
	searchCondition.Expression.SetPropertyValueExpression(20, // The built-in "created" date property.
		MFParentChildBehaviorNone, // No special parent/child behaviour required.
		dataFunctionCall); // Use the "year" data function call declared above.

	// Set up the condition (equals).
	searchCondition.ConditionType = MFConditionTypeEqual;

	// Set up the value to match against.
	searchCondition.TypedValue.SetValue(MFDatatypeInteger, 2018);
}

// Execute the search (assuming we have a valid, started shellFrame reference).
shellFrame.ShellUI.Vault.Async.ObjectSearchOperations.SearchForObjectsByConditionsEx(searchConditions,
	MFSearchFlagNone, false, 20000, 120, function(results)
{
	shellFrame.ShowMessage("There were " + results.Count + " objects.");
});

More information on using DataFunctionCall with the M-Files API can be found on its dedicated page.

Instantiating M-Files API objects

M-Files API objects can be instantiated in the following way:

 // Create an instance of ObjVer (https://developer.m-files.com/APIs/COM-API/Reference/index.html#MFilesAPI~ObjVer.html).
var objVer = new MFiles.ObjVer();

This is useful when calling API methods that define optional parameters, where JavaScript requires a non-null value to be passed.

For example, the following C# could be used to create a new object:

// Create the collection of property values for a new object.
var propertyValues = new PropertyValues();

// Add the class property.
{
	var propertyValue = new PropertyValue()
	{
		PropertyDef = (int)MFBuiltInPropertyDef.MFBuiltInPropertyDefClass
	};
	propertyValue.TypedValue.SetValue(
		MFDataType.MFDatatypeLookup,
		(int)MFBuiltInDocumentClass.MFBuiltInDocumentClassOtherDocument);
	propertyValues.Add(-1, propertyValue);
}

// Add the name or title property.
{
	var propertyValue = new PropertyValue()
	{
		PropertyDef = (int)MFBuiltInPropertyDef.MFBuiltInPropertyDefNameOrTitle
	};
	propertyValue.TypedValue.SetValue(
		MFDataType.MFDatatypeText,
		"hello world");
	propertyValues.Add(-1, propertyValue);
}

// Create the new object.
vault.ObjectOperations.CreateNewObjectEx(
	(int) MFBuiltInObjectType.MFBuiltInObjectTypeDocument,
	propertyValues);

However, within JavaScript the various optional arguments for CreateNewObjectEx must be provided for the call to work.

// Create the collection of property values for a new object.
var propertyValues = new MFiles.PropertyValues();

// Add the class property.
{
	var propertyValue = new MFiles.PropertyValue();
	propertyValue.PropertyDef = MFBuiltInPropertyDefClass;
	propertyValue.TypedValue.SetValue(
		MFDatatypeLookup,
		MFBuiltInDocumentClassOtherDocument);
	propertyValues.Add(-1, propertyValue);
}

// Add the name or title property.
{
	var propertyValue = new MFiles.PropertyValue();
	propertyValue.PropertyDef= MFBuiltInPropertyDefNameOrTitle;
	propertyValue.TypedValue.SetValue(
		MFDatatypeText,
		"hello world");
	propertyValues.Add(-1, propertyValue);
}

// Create the new object.
shellFrame.ShellUI.Vault.ObjectOperations.CreateNewObjectEx(
	MFBuiltInObjectTypeDocument,
	propertyValues,
	new MFiles.SourceObjectFiles(),
	false, // Zero files, so it isn't a single-file-document.
	true, // Check it in.
	new MFiles.AccessControlList() );

Whilst it is generally good practice to use the asynchronous programming approach, note that SourceObjectFiles could not be used asynchronously on the desktop as it does not support cloning. Implicit wrappers are available to support the use of CreateNewObject on M-Files Classic Web.

Referencing enumerated values

The M-Files API contains a number of enumerated values (e.g. MFBuiltInObjectClass, MFBuiltInObjectType, MFBuiltInPropertyDef, or MFObjectWindowMode).

When using the API from within a scripting environment (e.g. VBScript or within User Interface Extensibility Framework modules ) it is typically not required to provide the name or namespace of the enumeration as part of the call. For example, the following code is broadly equivalent in all languages:

// Declare a property value for the class property.
var propertyValue = new PropertyValue()
{
	PropertyDef = (int)MFBuiltInPropertyDef.MFBuiltInPropertyDefClass
};
' Declare a property value for the class property.
Dim objPropertyValue
Set objPropertyValue = CreateObject("MFilesAPI.PropertyValue")
objPropertyValue.PropertyDef = MFBuiltInPropertyDefClass
// Declare a property value for the class property.
var propertyValue = new MFiles.PropertyValue();
propertyValue.PropertyDef = MFBuiltInPropertyDefClass;

If an enumerated value is not available within the User Interface Extensibility Framework, you must instead reference it by its integer value. In the example above, the value of MFBuiltInPropertyDefClass can be found on the MFBuiltInPropertyDef documentation, and could be instead hard-coded as the ID 100, as below.

// Declare a property value for the class property.
var propertyValue = new MFiles.PropertyValue();
propertyValue.PropertyDef = 100; // MFBuiltInPropertyDef.MFBuiltInPropertyDefClass

Raising and handling exceptions

Exceptions within User Interface Extensibility Framework applications can be thrown/reported using the ICommonFunctionsThrowError and ReportException methods.

More information on handling M-Files exceptions, and raising your own using the standard M-Files error dialog, please see the Error Handling in UI Extensibility Applications page on the official documentation.