Alfred Desktop uses the Spring.NET framework for dependency injection.
Starting with Fred 3.1, it’s possible to reconfigure parts of Alfred Desktop with a Spring.NET configuration loaded from the repository.
This can be used to customize, for instance, the context menu to your users’ satisfaction. The configuration is server side and can be Alfred Desktop version dependable. It will be applied to every user connecting to that repository.
The configuration file must be placed in /Company Home/Data Dictionary/Fred/Config/[Fred version]/*-custom-context.xml.
Different configurations can be used for different Alfred Desktop/Fred versions. The configuration of the most specified matching version will be used.
Example: When 3 folders, “3”, “3.1” and “3.1.127” exist in /Company Home/Data Dictionary/Fred/Config/ and connect with Fred 3.1.127. Then the configuration file inside folder “3.1.127” will be used. If a different Fred 3.1 is connecting then the “3.1” configuration will be used. The configuration file inside folder “3” will be used if the Alfred Desktop/Fred version is in the version 3 range but differs from 3.1.
Note: {note} Leading zero’s have to be ignored when creating version folders.

There are multiple uses of context menus in Alfred Desktop. All these menus are defined in spring configuration, but not all menus can be customized on the server side spring configuration. For instance, the menu bar at the top of the screen is already visible before connecting to a repository. Customizing this menu in the server-side spring configuration won’t have any effect. Never the less, the right click context menu is fully customizable.
In Fred 3.1 it is necessary to fully override the view.mediator.[TYPE] object id, this is not advised to do so in any other version of Alfred Desktop/Fred. Therefore it is recommended that a separate 3.1 spring configuration folder is created for these kinds of customizations. In future versions of Alfred Desktop it will be possible to append commands to the already existing context menu, or remove them by extending the command builder objects. This will be further clarified in later version documentations.
<objects xmlns="http://www.springframework.net">
<object id="view.mediator.folder"
type="Xenit.Fred.Session.Controller.Browser.ItemMediators.FolderItemMediator"
parent="view.mediator.abstract-reponode"
singleton="false">
<property name="IconProvider" ref="view.item-icon-providers.folder" />
<property name="Commands">
<list element-type="Xenit.Fred.View.CommandBuilder.ICommandBuilder, Xenit.Fred.View" merge="true">
<ref object="command.builder.open-folder" />
<ref object="command.builder.refresh" />
<ref object="command.builder.copy" />
<ref object="command.builder.cut" />
<ref object="command.builder.new-folder" />
<ref object="command.builder.new-document" />
<ref object="command.builder.paste" />
<ref object="command.builder.paste-internal" />
<ref object="command.builder.show-in-browser" />
<ref object="command.builder.zoom-view" />
<ref object="command.builder.edit-metadata" />
<ref object="command.builder.delete" />
<ref object="command.builder.rename-reponode" />
<ref object="command.builder.favorites-add" />
<ref object="command.builder.acl" />
</list>
</property>
</object>
</objects>
These changes made to the commands list will apply on the context menu of folders.
Commands are defined with command builders.
Reference list of command builders:
Note: {note} adding command builders which are not relevant for the node in question won’t be visible in the context menu, this is explained in title 6.2.3.1 Evaluators.
Reference list of configurable item mediator types:
“Search document” and “search folder” are separated from the generic “document” mediator and the “folder” mediator. The reason for this is that the context menu of the search results differ from the context menu in the contents view workspace. This implementation might change in future versions of Alfred Desktop.
Custom commands can be used to extend the context menu with customized functionalities.
The following properties can be set for commands:
These customized functionalities are for example: opening a web browser tab.
When a command inherits from the webbrowser-abstract, the web browser
tab will be opened when this command is used. The command can be further
configured to specify the URL for the web browser tab. The property
External can be set to true to open the URL in the default web browser.
<object id="command.builder.admin"
parent="command.builder.webbrowser-abstract">
<property name="Title" value="server details" />
<property name="Name" value="server details" />
<property name="Group" value="admin" />
<property name="UrlPattern" value="{url:schema}://{url:host}:{url:port}/{url:context}
/service/api/server?a=false&alf_ticket={auth:ticket}" />
<property name="External" value="false" />
</object>
Evaluators can be used to specify whether the command is visible or disabled for the node in question.
Evaluators are configured as list-properties with name “Executable” or “Relevant” in the command-builder. When an executable evaluator returns false, then the item will be grayed out in the context menu. When a relevant evaluator returns false, then the item won’t be visible in the context menu.
Note: {note} The evaluators defined in the spring configuration are applied after the built-in evaluators on command itself.
Example usage: the property ‘Executable’ defined on a command-builder will grey out the command in the context-menu, when (one of) the evaluators returns false. In this example, the context-menu-item will be greyed out of the node is-not or does-not-inherit-from the Alfresco type cm:content.
<property name="Executable">
<list element-type="ICommandEvaluator">
<object parent="command.eval.node-type-required">
<property name="Type" value="cm:content"/>
</object>
</list>
</property>
Summary of evaluator types:
When you create a document from a template by default you see a list of documents in the context menu.

This presentation is handy when you don’t have a lot of possible document templates. When you do have a lot of document template you might prefer a template picker with a search box.

If you want to use the document template picker with a search box you will need a custom spring configuration. The following piece of code will enable the document template picker with each box.
<objects xmlns="http://www.springframework.net">
<object id="command.builder.new-document"
parent="command.builder.abstract"
type="Xenit.Fred.Session.Controller.DocumentTemplates.Command.NewDocumentCommandBuilder, Xenit.Fred.Session.Controller">
<property name="Title" value="New Document" />
<property name="AppMessageBus" ref="service.app-message-bus" />
<property name="PermissionService" ref="repository.service.permissions" />
<property name="TemplateService" ref="repository.service.template" />
<property name="NodeService" ref="repository.service.node" />
<property name="DictionaryService" ref="repository.service.dictionary" />
<property name="IconService" ref="view.item-icon-service" />
<property name="RecordsManagementService" ref="repository.service.records-management" />
<property name="TemplateCommandBuilder" ref="command.builder.document-template" />
<property name="TemplatesPickerControllerBuilder">
<object type="Xenit.Fred.View.ControlMediator.Builder.ControllerBuilder, Xenit.Fred.View">
<property name="Id" value="control.document-template.filter-picker"/>
</object>
</property>
</object>
</objects>
It is also possible to further configure the document template picker to classify the templates in categories.

The following configuration enables a document template picker with templates classified in categories.
<objects xmlns="http://www.springframework.net">
<object id="command.builder.new-document"
parent="command.builder.abstract"
type="Xenit.Fred.Session.Controller.DocumentTemplates.Command.NewDocumentCommandBuilder, Xenit.Fred.Session.Controller">
<property name="Title" value="New Document" />
<property name="AppMessageBus" ref="service.app-message-bus" />
<property name="PermissionService" ref="repository.service.permissions" />
<property name="TemplateService" ref="repository.service.template" />
<property name="NodeService" ref="repository.service.node" />
<property name="DictionaryService" ref="repository.service.dictionary" />
<property name="IconService" ref="view.item-icon-service" />
<property name="RecordsManagementService" ref="repository.service.records-management" />
<property name="TemplateCommandBuilder" ref="command.builder.document-template" />
<property name="TemplatesPickerControllerBuilder">
<object type="Xenit.Fred.View.ControlMediator.Builder.ControllerBuilder, Xenit.Fred.View">
<property name="Id" value="control.document-template.filter-picker"/>
</object>
</property>
</object>
<object id="control.document-template.filter-picker"
parent="control.mediator.filter-picker"
singleton="false">
<property name="PickerController" ref="control.mediator.categorytemplates" />
</object>
</objects>
A feature introduced in Alfred Desktop 3.6 is a custom dialog control. It allows users to fill in a form in a dialog and send the inputs of that form to web script in Alfresco. The dialog can be opened by choosing the command from the context menu, if it is configured correctly.

A forms configuration is required to design the form you want to see in the dialog.
Example Forms Configuration:
- Id: example:sample-dialog
Evaluator: string-compare
Version: 1
Forms:
- Fields:
- Id: content
Fields:
- Id: input1
Text: Input Text Field 1
Parameters:
template: gui.controlfactory.property.textbox
- Id: input2
Text: Input Text Field 2
Parameters:
template: gui.controlfactory.property.textbox
Parameters:
endPoint: /s/uri/{space}/{store}/{guid}/createFolderStructure
requestMethod: POST
invalidate: assocs
For each field: You have to set the “template” in the “Parameters”. As of Alfred Desktop 3.6 only text fields are supported so the template “gui.controlfactory.property.textbox” is the only valid one. In the future other templates (for example date picker fields) will also be supported.
For earch set: In “Parameters” you have to define the endpoint to which you would like to send your inputs. You do this by setting the “endPoint”. The inputs are sent as a JSON array in the same sequence as they are defined in the forms configuration. This is important to notice because you will have to take this into account in the web script that is being called. For the example forms-configuration this would mean that the JSON array being sent to the web script contains 2 entries, the first being input1 and the second being input2. The value of “requestMethod” can be set to “GET” or “POST”. This signifies the http request method you want to use. “invalidate” is used to set the cache invalidation. The value of “invalidate” can be set to “none”, “node”, “assocs” or “both”. “none” is the default value and will not do anything. “node” is used to invalidate the node metadata in the Alfred Desktop cache. This is to make sure the metadata of that particular node are up-to-date. “assocs” is used to invalidate the child associations of the node in the Alfred Desktop cache. This is to make sure that the child associations of the particular node are up-to-date. “both” is a combination of “node” and “assocs”.
The file in which you made this configuration can have whichever name you want as long as it ends with “forms-config.yaml”.
In order to see the dialog you have to configure a command that opens it up. The configuration happens by overwriting the spring configuration of Alfred Desktop.
Example spring configuration:
<objects xmlns="http://www.springframework.net">
<object id="command.builder.open-sample-dialog-box"
parent="command.builder.open-dialog-box">
<property name="Title" value="This a sample dialog box" />
<property name="ID" value="example:sample-dialog" />
</object>
</objects>
Here the “ID” value has to be set to the id you set in the forms configuration (see the id in the previous example).
Beside the browse and search tab, it is possible to configure other default tabs. These defaults tabs will open automatically when connection to the repository is made. For example, the web browser tab that hosts a third party application can be added which will from then on open automatically and will no longer be closable.
Example default tabs
<object id="bootstrap.controllers"
type="Xenit.Fred.Session.Bootstrap.BootstrapControllers, Xenit.Fred.Session"
singleton="true">
<property name="ControllerHost" expression="@(application-controller).PrimaryWindowController.MainController.TabController" />
<property name="ControllerList">
<list element-type="IController">
<ref object="controller.session.browser.main" />
<ref object="controller.session.search.main" />
<ref object="my-custom-controller" />
</list>
</property>
</object>
Each workspace has its own set of default columns. These sets of default columns can be configured for each separate workspace.
At the moment it is necessary to override the existing defined workspace to change the default columns property. This is subject to change in one of the next versions of Alfred Desktop, so be advised to only apply this configuration in a “3.1” version specified configuration folder.
The following examples also show how to define your own columns with properties from a custom document model and use them as a default column.
<object id="workspace.browser.contents.default"
type="Xenit.Fred.Session.Controller.Browser.Workspace.FolderContentsListViewWorkspace"
singleton="false">
<property name="NodeService" ref="repository.service.node" />
<property name="DictionaryService" ref="repository.service.dictionary" />
<property name="ConfigService" ref="repository.service.config" />
<property name="NodeCacheService" ref="repository.service.nodecache" />
<property name="PathProvider" ref="path-provider.reponode-or-default" />
<property name="RefreshCommandBuilder" ref="command.builder.refresh" />
<property name="ColumnService" ref="view.service.column-state" />
<property name="FormsConfigService" ref="repository.service.formconfig" />
<property name="TranslationService" ref="service.translation" />
<property name="ControlFactory" ref="gui.controlfactory" />
<property name="ViewArtifactFactory" ref="view.service.item-factory" />
<property name="ColumnService" ref="view.service.column-state" />
<property name="ShowColumnsMenuBuilder" ref="command.builder.show-forms-columns-menu" />
<property name="ShowColumnSetsMenuBuilder" ref="command.builder.show-forms-column-sets-menu" />
<property name="DefaultColumns">
<list element-type="Xenit.Fred.View.ColumnBuilder.IColumnBuilder, Xenit.Fred.View">
<ref object="view.column.node.property.name" />
<ref object="view.column.node.type" />
</list>
</property>
</object>
<object id="view.column.node.property.custom-property"
parent="view.column.node.property"
singleton="true">
<constructor-arg name="title" value="Custom property" />
<constructor-arg name="propertyQName" value="cm:exampleProperty" /> <!--custom property-->
<property name="Renderer" ref="view.renderer.node.property" />
</object>
You can add custom sets of virtual folders to the tree view. These virtual folders are predefined searches where the results will be shown as if it is a folder. They are useful when a specific search is done often, by multiple users.

To add this feature, a ‘filtered-view’ object should be defined in the Spring.NET configuration, with parent id “workspace.browser.foldertree.filtered-view”. The property “Name” defines the display name in the left panel (Invoicing in the figure above). The “Folder” property defines from where in the repository Alfred Desktop should load the XML files with search-definitions.
Additionally, two workspaces need to be configured: one for the left folder-tree panel and one for the folder-contents panel in the middle.
To add the new workspaces to the workspace providers we need to override both providers (tree and content). As stated before, the xml definitions can change with new releases of Alfred Desktop, so it would be a good practice to store these config changes in a ‘3.1’ configuration folder in the repository.
Use the following Spring configuration to enable Virtual Folders.
<!-- Filter Service -->
<object id="service.my-custom-filter" parent="service.filtered-view-base" singleton="true">
<property name="Name" value="New filter" />
<property name="Folder" value="/Company Home/Data Dictionary/Fred/Filtered Views" />
</object>
<!-- Folder Tree Workspaces -->
<object id="workspace.my-custom-filter.foldertree"
parent="workspace.browser.foldertree.filtered-view"
singleton="false">
<property name="FilteredViewService" ref="service.my-custom-filter" />
</object>
<!-- Folder Contents Workspaces -->
<object id="workspace.my-custom-filter.contents"
parent="workspace.browser.contents.filtered-view"
singleton="false">
<property name="FilteredViewService" ref="service.my-custom-filter" />
</object>
<!-- Override Workspace Providers -->
<object id="workspace-provider.browser.foldertree"
parent="workspace.provider.base" singleton="false">
<property name="Workspaces">
<list element-type="Xenit.Fred.View.Logic.Workspace.IViewWorkspace,
Xenit.Fred.View">
<ref object="workspace.browser.foldertree.browser" />
<ref object="workspace.browser.foldertree.workingcopies" />
<ref object="workspace.browser.foldertree.savedsearches" />
<ref object="workspace.browser.foldertree.favorites" />
<ref object="workspace.my-custom-filter.foldertree" />
</list>
</property>
</object>
<object id="workspace-provider.browser.contents"
parent="workspace.provider.base" singleton="false">
<property name="Workspaces">
<list element-type="Xenit.Fred.View.Logic.Workspace.IViewWorkspace,
Xenit.Fred.View">
<ref object="workspace.browser.contents.favorites" />
<ref object="workspace.my-custom-filter.contents" />
<ref object="workspace.zoom.contents.category" />
<ref object="workspace.browser.contents.default" />
<ref object="workspace.browser.contents.peerassocs" />
<ref object="workspace.browser.contents.workingcopies" />
</list>
</property>
</object>
The configuration for these filters is the same as the saved searches. So the easiest way to create a new filter is to do the search in Alfred Desktop, add the columns and save the search with the “include columns” checkbox checked. Then go to “File” -> “Open Settings Location” and open savedsearches.xml. Search for your recently added saved search and copy the “savedsearch” tag to a new XML file, save this new XML file in the repository in the folder you specified in the Sprint.NET configuration file (example: /Company Home/Data Dictionary/Fred/Filtered Views).
The icon attribute in the “savedsearch” tag can be any icon inside the “[Alfred Desktop install directory]/images/icon16”-folder.
Here is an example of a saved search which can be used as a Virtual Folder
<savedsearch type="path" name="Example Filter" icon="searchplain.png">
<sortoptions>
<displaycolumns>
<column id="Name" name="Name" index="0" />
<column id="Info" name="Info" index="1" />
<column id="example-prefix:exampleProperty" name="Example property" index="4">
<type>property</type>
<qname>example-prefix:exampleProperty</qname>
<renderer>view.renderer.node.property</renderer>
<width>150</width>
</column>
<column id="Location" name="Location" index="5" />
</displaycolumns>
<columntosort id="Name" name="Name" order="Ascending" />
</sortoptions>
<searchnode>
<compositenode searchoperator="SearchOperatorAnd">
<searchtermtype qname="{http://www.xenit.eu/model/example-model/1.0}exampleType" />
<searchtermaspect qname="{http://www.xenit.eu/model/example-model/1.0}exampleAspect" />
<searchtermall value="*" />
</compositenode>
</searchnode>
</savedsearch>
Date ranges for date properties can be configured using a start date and an end date.
Here is an example of a Virtual Folder with a date range.
<savedsearch type="path" name="test" id="1666bd8e-ca19-47f9-a6f2-de714dfbd530" icon="searchplain.png">
<sortoptions>
<displaycolumns>
<column id="cm:name" name="Name" index="0">
<type>property</type>
<qname-enum>PROPERTY_NAME</qname-enum>
<qname>cm:name</qname>
<fixed>True</fixed>
<width>150</width>
<renderer>view.renderer.node.property.name</renderer>
</column>
<column id="Info" name="Info" index="1">
<width>150</width>
</column>
<column id="Location" name="Location" index="2">
<width>150</width>
</column>
</displaycolumns>
<columntosort id="cm:name" name="Name" order="Ascending" />
</sortoptions>
<searchnode>
<compositenode searchoperator="SearchOperator:AND">
<searchtermpath name="Company Home" nodereference="workspace://SpacesStore/e05799e7-601c-4253-915f-16e5c3bc1825" />
<compositenode searchoperator="SearchOperator:OR">
<searchtermtype qname="{http://www.alfresco.org/model/content/1.0}folder" />
<searchtermtype qname="{http://www.alfresco.org/model/content/1.0}content" />
</compositenode>
<searchtermdatetime startdate="2017-09-07T00:00:00+02:00" enddate="2017-09-08T23:59:59+02:00" qname="{http://www.alfresco.org/model/content/1.0}created" />
</compositenode>
</searchnode>
</savedsearch>
You can also set a date range without a start date. The resulting start date will be today - 30 days.
Here is an example of a Virtual Folder with a date range with only an end date.
<savedsearch type="path" name="test" id="1666bd8e-ca19-47f9-a6f2-de714dfbd530" icon="searchplain.png">
<sortoptions>
<displaycolumns>
<column id="cm:name" name="Name" index="0">
<type>property</type>
<qname-enum>PROPERTY_NAME</qname-enum>
<qname>cm:name</qname>
<fixed>True</fixed>
<width>150</width>
<renderer>view.renderer.node.property.name</renderer>
</column>
<column id="Info" name="Info" index="1">
<width>150</width>
</column>
<column id="Location" name="Location" index="2">
<width>150</width>
</column>
</displaycolumns>
<columntosort id="cm:name" name="Name" order="Ascending" />
</sortoptions>
<searchnode>
<compositenode searchoperator="SearchOperator:AND">
<searchtermpath name="Company Home" nodereference="workspace://SpacesStore/e05799e7-601c-4253-915f-16e5c3bc1825" />
<compositenode searchoperator="SearchOperator:OR">
<searchtermtype qname="{http://www.alfresco.org/model/content/1.0}folder" />
<searchtermtype qname="{http://www.alfresco.org/model/content/1.0}content" />
</compositenode>
<searchtermdatetime enddate="2017-09-08T23:59:59+02:00" qname="{http://www.alfresco.org/model/content/1.0}created" />
</compositenode>
</searchnode>
</savedsearch>
It is also possible to omit the end date. The resulting end date will be today + 1 day.
Here is an example of a Virtual Folder with a date range with only a start date.
<savedsearch type="path" name="test" id="1666bd8e-ca19-47f9-a6f2-de714dfbd530" icon="searchplain.png">
<sortoptions>
<displaycolumns>
<column id="cm:name" name="Name" index="0">
<type>property</type>
<qname-enum>PROPERTY_NAME</qname-enum>
<qname>cm:name</qname>
<fixed>True</fixed>
<width>150</width>
<renderer>view.renderer.node.property.name</renderer>
</column>
<column id="Info" name="Info" index="1">
<width>150</width>
</column>
<column id="Location" name="Location" index="2">
<width>150</width>
</column>
</displaycolumns>
<columntosort id="cm:name" name="Name" order="Ascending" />
</sortoptions>
<searchnode>
<compositenode searchoperator="SearchOperator:AND">
<searchtermpath name="Company Home" nodereference="workspace://SpacesStore/e05799e7-601c-4253-915f-16e5c3bc1825" />
<compositenode searchoperator="SearchOperator:OR">
<searchtermtype qname="{http://www.alfresco.org/model/content/1.0}folder" />
<searchtermtype qname="{http://www.alfresco.org/model/content/1.0}content" />
</compositenode>
<searchtermdatetime startdate="2017-09-07T00:00:00+02:00" qname="{http://www.alfresco.org/model/content/1.0}created" />
</compositenode>
</searchnode>
</savedsearch>
In the configuration of a virtual folder you can use the {username} keyword as value for a search property. This keyword is a placeholder for the username of the user that is currently logged in.
This virtual folder will search for all documents that have been created by the current user.
<savedsearch type="path" name="sample_username_placeholder" id="1666bd8e-ca19-47f9-a6f2-de714dfbd530" icon="searchplain.png">
<sortoptions>
<displaycolumns>
<column id="cm:name" name="Name" index="0">
<type>property</type>
<qname-enum>PROPERTY_NAME</qname-enum>
<qname>cm:name</qname>
<fixed>True</fixed>
<width>150</width>
<renderer>view.renderer.node.property.name</renderer>
</column>
<column id="Info" name="Info" index="1">
<width>150</width>
</column>
<column id="Location" name="Location" index="2">
<width>150</width>
</column>
</displaycolumns>
<columntosort id="cm:name" name="Name" order="Ascending" />
</sortoptions>
<searchnode>
<compositenode searchoperator="SearchOperatorAnd">
<searchtermtype qname="{http://www.alfresco.org/model/content/1.0}content" />
<searchtermproperty value="{username}" qname="{http://www.alfresco.org/model/content/1.0}creator" />
</compositenode>
</searchnode>
</savedsearch>
You can access the zoomed view by selecting the “zoom” option from the context menu by right clicking on a folder. This view will show that folder as a virtual root.
Within this zoomed view you can browse the folder tree but you can also filter on categories. Only nodes of the selected category will be shown. The categories you can choose from are configurable. By default it uses the standard category tree from Alfresco (“General classifiable”).
Creating a custom category tree requires a custom category aspect in your content model and some Spring configuration.
Here is an example of a content model with a custom category aspect.
<?xml version="1.0" encoding="UTF-8"?>
<model name="fredExp:model" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<description>My custom document model</description>
<author>author</author>
<version>0.1</version>
<imports>
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" />
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm" />
</imports>
<namespaces>
<namespace uri="http://www.xenit.eu/fred/customcategory/model/0.1" prefix="custcat" />
</namespaces>
<aspects>
<aspect name="custcat:myCustomCategoriesAspect">
<title>My custom categories aspect</title>
<parent>cm:classifiable</parent>
<properties>
<property name="custcat:myCustomCategoriesProperty">
<title>My custom categories property</title>
<type>d:category</type>
<mandatory>true</mandatory>
<multiple>false</multiple>
<index enabled="true">
<atomic>true</atomic>
<stored>true</stored>
<tokenised>false</tokenised>
</index>
</property>
</properties>
</aspect>
</aspects>
</model>
Here is an example of the Spring configuration needed for a custom category tree
<object id="path-provider.category-repo-node"
type="Xenit.Fred.Session.ControllerPath.Category.CategoryPathProvider">
<property name="DictionaryService" ref="repository.service.dictionary" />
<property name="NodeService" ref="repository.service.node" />
<property name="ConfigService" ref="repository.service.config" />
<property name="CategoryService" ref="repository.service.category" />
<property name="CategoryPropertyQname" value="{http://my.custom.namespace}myCustomCategoriesProperty" />
</object>
<object id="workspace.zoom.tree.zoom"
type="Xenit.Fred.Session.Controller.ZoomBrowser.Workspace.ZoomCategoryTreeWorkspace"
parent="workspace.session.base"
singleton="false">
<property name="CategoryService" ref="repository.service.category" />
<property name="DictionaryService" ref="repository.service.dictionary" />
<property name="Classification" value="{http://my.custom.namespace}myCustomCategoriesAspect" />
</object>
Alfred Desktop version 3.7.4 introduces this feature. It allows you to configure an endpoint that is called for a certain column. The value of the column shows the resulting response.
To enable this feature you need a part forms configuration and Spring.NET configuration.
The forms configuration is used to tell Alfred Desktop that the “view.renderer.node.bulk” renderer has to be used to render the column value. This renderer will make sure that the values of the columns are retrieved in one call to the custom endpoint. This is done for efficiency reasons.
- Id: default-column-list
Evaluator: string-compare
Version: 1
Forms:
- Fields:
- Id: System
Appearance: section
Text: columns with value based on custom endpoint
TextSourceId: System Properties
Fields:
- Id: CustomEndpointColumn
Parameters:
renderer: "view.renderer.node.bulk"
Text: Custom endpoint
In this case there is one column configured with id “CustomEndpointColumn”.
The Spring.NET configuration is used to configure the endpoint, the http method and the field id (the id that you configured in the forms configuration).
<object id="view.column.bulk.populator.custom-endpoint"
type="Xenit.Fred.Session.Controller.DelayedBulkPopulators.Builders.DelayedBulkCustomEndpointPopulatorBuilder, Xenit.Fred.Session.Controller"
singleton="false">
<property name="FieldId" value="CustomEndpointColumn" />
<property name="Url" value="{url:base}/service/pathToYourWebscript" />
<property name="HttpMethod" value="Post" />
<property name="BulkColumnPopulatorService" ref="repository.service.column-populator.bulk" />
<property name="UrlAddressabilityService" ref="repository.service.url" />
</object>
The Spring.NET object now also has to be injected into the browser overview and the search overview.
<object id="control.mediator.searchresults"
parent="controller.session.repo-node-view"
type="Xenit.Fred.Session.Controller.Search.ControlMediator.SearchListControlMediator, Xenit.Fred.Session.Controller"
singleton="false">
<property name="PersistColumnState" value="true" />
<property name="ColumnService" ref="view.service.column-state" />
<property name="ShowColumnsMenuBuilder" ref="command.builder.show-forms-columns-menu" />
<property name="ShowColumnSetsMenuBuilder" ref="command.builder.show-forms-column-sets-menu" />
<property name="GroupByMenuBuilder" ref="command.builder.group-by-menu" />
<property name="SearchService" ref="repository.service.search" />
<property name="NodeService" ref="repository.service.node" />
<property name="DefaultColumns">
<list element-type="Xenit.Fred.View.ColumnBuilder.IColumnBuilder, Xenit.Fred.View">
<ref object="view.column.node.property.name" />
<ref object="view.column.node.type" />
<ref object="view.column.node.location" />
</list>
</property>
<property name="MediatorMapping">
<name-values merge="true">
<add key="IDocumentMediator" value="view.mediator.search.document" />
<add key="IFolderItemMediator" value="view.mediator.search.folder" />
<add key="IRecordCategoryItemMediator" value="view.mediator.search.record-category" />
<add key="ExtendedNodeInfoRefItemMediator" value="view.mediator.extended-node-reference" />
</name-values>
</property>
<property name="ColumnPopulatorBuilders">
<dictionary key-type="string" value-type="Xenit.Fred.Session.Controller.DelayedBulkPopulators.Builders.IDelayedBulkBuilder, Xenit.Fred.Session.Controller">
<entry key="Location">
<object parent="view.column.bulk.populator.location"/>
</entry>
<entry key="CustomEndpointColumn">
<object parent="view.column.bulk.populator.custom-endpoint"/>
</entry>
</dictionary>
</property>
</object><br /> <object id="controller.session.browser.folder-contents"
parent="controller.session.repo-node-view"
type="Xenit.Fred.Session.Controller.Browser.FolderContentsController, Xenit.Fred.Session.Controller"
singleton="false">
<property name="NodeService" ref="repository.service.node" />
<property name="WorkspaceProvider" ref="workspace-provider.browser.contents" />
<property name="PersistColumnState" value="true" />
<property name="ItemSorter" ref="view.sorting.layered.type-priority" />
<property name="ColumnPopulatorBuilders">
<dictionary key-type="string" value-type="Xenit.Fred.Session.Controller.DelayedBulkPopulators.Builders.IDelayedBulkBuilder, Xenit.Fred.Session.Controller">
<entry key="Location">
<object parent="view.column.bulk.populator.location"/>
</entry>
<entry key="CustomEndpointColumn">
<object parent="view.column.bulk.populator.custom-endpoint"/>
</entry>
</dictionary>
</property>
</object>
In the example above we injected the Spring.NET object we just made (
view.column.bulk.populator.custom-endpoint) as an entry in the
“ColumnPopulatorBuilders” dictionary. The key of the entry is the id of
the field (CustomEndpointColumn) we configured in the forms
configuration.
The endpoint being called should be able to handle a specific structure in the json body of the request and it should return a json body structured in a certain way in the response. This endpoint most likely has to be custom made. Have a look at the following requirements.
The request body has to contain an array of node references. Take the following json code as an example.
{
"nodeRefs" : [
"workspace://SpacesStore/86252def-efe1-440a-ac87-b3ebda9d7628",
"workspace://SpacesStore/1ad281d6-52c1-46c7-a806-8cf1ac857b07"
]
}
The response body has to look similar to this one.
{
"workspace://SpacesStore/86252def-efe1-440a-ac87-b3ebda9d7628": "fred-planning-2015-Q1.xls",
"workspace://SpacesStore/1ad281d6-52c1-46c7-a806-8cf1ac857b07": "fred-planning.xls"
}
It should return a mapping between the nodes in the request and the value that should be rendered in the column that you configured before.
The preview panel is used to show the preview of the content of a document. For folders there is no preview page so no preview will be available. However, for folders it is possible to configure the panel to navigate to a different web page.
The configuration only consists of Spring.NET. First you have to configure a controller for the view panel. You can use the following example and change a few properties. The property “AspectToContainerViewUrlDictionary” contains a mapping between aspects and urls. When you select a folder and have the preview panel open, Alfred Desktop will iterate of the dictionary defined here. The view panel will then navigate to the url corresponding to the first aspect in the dictionary that is found on the folder. The property “DefaultContainerViewUrl” is the default url and it will be used if none of the aspects in “AspectToContainerViewUrlDictionary” are found on the folder. The default url will also be used if the property “AspectToContainerViewUrlDictionary” is not defined or is empty. You can also decide to not have a default url. If none of the aspects in the dictionary are found on the folder, the view panel will show the message “Container view not available”.
<object id="controller.session.container-view"
parent="controller.session.node.view.abstract"
type="Xenit.Fred.Session.Controller.Preview.ContainerViewRepoNodeController, Xenit.Fred.Session.Controller"
singleton="false">
<property name="UrlAddressabilityService" ref="repository.service.url" />
<property name="DictionaryService" ref="repository.service.dictionary" />
<property name="AspectToContainerViewUrlDictionary">
<dictionary key-type="string" value-type="string">
<entry key="cm:titled" value="https://www.webpage1.eu/" />
<entry key="cm:generalclassifiable" value="https://www.webpage2.eu/" />
</dictionary>
</property>
<property name="DefaultContainerViewUrl" value="https://xenit.eu/" />
<property name="Title" value="Container View"/>
</object>
You also have to tell Alfred Desktop that for folders it has to use the controller (controller.session.container-view) we have just configured. Make a reference to it in the property “ContainerViewController”.
<object id="controller.sidepanel.preview"
parent="control.mediator.abstract"
type="Xenit.Fred.Session.Controller.Browser.SidePanelNodeViewController, Xenit.Fred.Session.Controller"
singleton="false">
<property name="PreviewController" ref="controller.session.preview" />
<property name="ContainerViewController" ref="controller.session.container-view" />
<property name="NodeCacheService" ref="repository.service.nodecache" />
<property name="RenditionService" ref="repository.service.rendition" />
<property name="SettingsService" ref="service.settings" />
</object>
You also want to be able to open the container view in another tab from the context menu on a folder. First you have to make sure that the command uses the controller that was defined in the first code snippet (controller.session.container-view). In the example below you can see that the “ID” property is set to “controller.session.container-view”.
<object id="command.builder.container-view"
parent="command.builder.node.view.abstract"
type="Xenit.Fred.Session.Controller.Preview.Command.ContainerViewRepoNodeControllerCommandBuilder">
<property name="Title" value="Container View" />
<property name="ID" value="controller.session.container-view" />
<property name="Executable">
<list element-type="ICommandEvaluator">
<ref object="command.eval.is-folder" />
<ref object="command.eval.not-available-offline" />
</list>
</property>
</object>
All that is left now is to add the “Container view” command (command.builder.container-view) from the previous code snippet to the commands you can activate on a folder. Add a reference to the command we have just defined to the “Commands” property.
<object id="view.mediator.folder"
type="Xenit.Fred.Session.Controller.Browser.ItemMediators.FolderItemMediator"
parent="view.mediator.abstract-reponode"
singleton="false">
<property name="IconProvider" ref="view.item-icon-providers.folder" />
<property name="RenameCommandBuilder" ref="command.builder.rename-reponode-execute" />
<property name="Commands">
<list element-type="Xenit.Fred.View.CommandBuilder.ICommandBuilder, Xenit.Fred.View" merge="true">
<ref object="command.builder.open-folder" />
<ref object="command.builder.container-view"/>
<ref object="command.builder.refresh" />
<ref object="command.builder.copy" />
<ref object="command.builder.cut" />
<ref object="command.builder.cut-record" />
<ref object="command.builder.copy-browser-link" />
<ref object="command.builder.copy-fred-link" />
<ref object="command.builder.copy-ref" />
<ref object="command.builder.new-folder" />
<ref object="command.builder.new-rm-category" />
<ref object="command.builder.new-rm-folder" />
<ref object="command.builder.new-document" />
<ref object="command.builder.paste" />
<ref object="command.builder.paste-internal" />
<ref object="command.builder.paste-record-internal" />
<ref object="command.builder.paste-internal-link" />
<ref object="command.builder.show-in-browser" />
<ref object="command.builder.zoom-view" />
<ref object="command.builder.toggle-sync" />
<ref object="command.builder.refresh-offline" />
<ref object="command.builder.export-zip" />
<ref object="command.builder.edit-metadata" />
<ref object="command.builder.edit-record-metadata" />
<ref object="command.builder.delete" />
<ref object="command.builder.delete-record" />
<ref object="command.builder.rename-reponode" />
<ref object="command.builder.favorites-add" />
<ref object="command.builder.acl" />
<ref object="command.builder.properties" />
</list>
</property>
</object>
The metadata injector is capable of writing metadata into documents. This allows templates to be filled in with the metadata.
Summary: Alfred Desktop 3.6 and later support Metadata Injector 1.32 and later.
In Alfred Desktop 3.6, we introduced an automated content hashing feature to support checks for changed content. This feature interacts together with the metadata injector, causing endless loop between the two components. In order to resolve this, as of Metadata Injector version 1.32 and later, metadata injection will only be triggered for properties that are part of the configured property mapping. This allows us to exclude the property that contains the content hash value, hereby avoiding processing of updates that will cause the same problem.
Since Fred backend version 3.4, the metadata injection is no longer available in the backend and is shipped as a separated amp. The configuration file for metadata injection can be found at webapps/alfresco/WEB-INF/classes/alfresco/module/xenit-metadata-injection/module-context.xml. You should not make any changes to this file, since all changes will be undone when the alfresco.war file get re-extracted (ex. when a new amp is applied). The configuration file is no longer at webapps/alfresco/WEB-INF/classes/alfresco/ module/xenit-fred/metadata-context.xml, as the metadata-injection is no longer part of the Alfred Desktop/Fred backend amp.
The following document types are supported by the following components
These components are by default allowed in the xenit-metadata-injection/module-context.xml.
<!-- Content Metadata Injectors -->
<beanid="injector.Office"class="eu.xenit.fred.alfresco.metadata.injector.OfficeMetadataInjector"
parent="baseMetadataInjector"/>
<beanid="injector.POI"class="eu.xenit.fred.alfresco.metadata.injector.PoiMetadataInjector"
parent="baseMetadataInjector"/>
<beanid="injector.OpenDocument" class="eu.xenit.fred.alfresco.metadata.injector.OpenDocumentMetaDataInjector"
parent="baseMetadataInjector"/>
You can enable the metadata injection by ensuring the metadata amp is installed and then setting eu.xenit.alfresco.metadata.injector.enabled=true in the alfresco-global.properties.
Every property that needs to be injected into the document is linked with a property-provider. This property-provider is responsible to resolve the values that will be injected in the document.
These properties can be configured by overriding the bean “baseMetadataInjector” in a new ‘metadata-injector-context.xml’ file under the shared/ classpath as shown in the snippet below.
The available PropertyProvider beans are:
Create a new file named ‘metadata-injector-context.xml’ in
{TOMCAT_HOME}/shared/classes/alfresco/extension/
and use the following example snippet:
The metadata injector module context. You can configure the properties that need to be injected here.
<!-- Abstract bean definition defining base definition for all metadata extractors -->
<bean id="baseMetadataInjector"
class="eu.xenit.alfresco.metadata.AbstractMappingMetadataInjector"
abstract="true"
init-method="register">
<property name="registry">
<ref bean="metadataInjectorRegistry"/>
</property>
<property name="dictionaryService">
<ref bean="dictionaryService"/>
</property>
<property name="namespaceService">
<ref bean="namespaceService"/>
</property>
<property name="mappingProperties">
<map key-type="java.lang.String" value-type="eu.xenit.fred.alfresco.metadata.injector.PropertyProvider.IPropertyProvider">
<!-- defaultEmpty will cause the field to be injected as an empty string if the property does not exist -->
<entry key="cm:name" value-ref="eu.xenit.metadata.injector.propertyprovider.defaultEmpty" />
<!-- default property provider will not inject field if it is not found for a specific node -->
<entry key="cm:author" value-ref="eu.xenit.metadata.injector.propertyprovider.default"/>
<entry key="cm:creator" value-ref="eu.xenit.metadata.injector.propertyprovider.default"/>
<entry key="cm:created" value-ref="eu.xenit.metadata.injector.propertyprovider.default"/>
<entry key="cm:modifier" value-ref="eu.xenit.metadata.injector.propertyprovider.default"/>
<entry key="cm:modified" value-ref="eu.xenit.metadata.injector.propertyprovider.default"/>
<entry key="cm:title" value-ref="eu.xenit.metadata.injector.propertyprovider.default"/>
<entry key="cm:versionLabel" value-ref="eu.xenit.metadata.injector.propertyprovider.versionLabel"/>
<entry key="cm:description" value-ref="eu.xenit.metadata.injector.propertyprovider.description"/>
<!-- add your custom properties here, use eu.xenit.metadata.injector.propertyprovider.default provider as the default provider is recommended -->
</map>
</property>
<!-- extra node properties -->
<property name="properties">
<list>
<ref bean="eu.xenit.metadata.injector.propertyprovider.noderef"/>
<ref bean="eu.xenit.metadata.injector.propertyprovider.location"/>
</list>
</property>
</bean>