Custom Share Action UI

Document created by resplin Employee on Jun 6, 2015Last modified by alfresco-archivist on Aug 31, 2016
Version 5Show Document
  • View in full screen mode

Obsolete Pages{{Obsolete}}

The official documentation is at: http://docs.alfresco.com



3.3ExamplesDeveloper Guide

The Alfresco repository comes with a set of predefined rule definitions for automating the flow of content in the repository. Such definitions are rule types, rule conditions and rule actions. When an item is 'created or moved into a folder', 'updated' or 'deleted or moved out from a folder' (each one being a 'rule type') it is tested against certain 'rule conditions' (such as if its name property contains a certain value or if it is of a specific MIME type) and if all those conditions are true a number of 'rule actions' can be performed against the content. Examples of such actions can be to add a specific aspect or move the content to a another folder.

Even though the rule definitions shipped with Alfresco shall cover the basic needs and more, there might be a situation where you want to add a new type of condition to match the content against or a new action to perform if all conditions are true. That is when you need to define a custom rule definition.

To learn more about how to define your own rule condition and actions in the Alfresco Repository, please visit Custom Actions.

To customise the Alfresco Explorer UI, please visit Custom Action UI.

To learn how to setup a custom Alfresco Share UI for rules, please continue to read below.


Share gives the user a default UI


By default Share gives the user a fully working UI even for custom rule types, definitions and actions. This means that if you define a new rule action in the repository a UI will be presented to the user with labels and UI controls of different type depending on which type the parameter has. For example if a parameter is of type d:int a text field that only accepts numbers will be created and if it is of type d:nodeRef another textfield will be rendered that only accepts a string that matches the nodeRef pattern. If a constraint is given for the parameter a drop down list will be created with the values from the constraint and if the parameter ISN'T marked as mandatory an extra 'empty' option will be added as the first option in the dropdown. Depending on the amount of parameter and their types the default ui may be totally sufficient for the tasks but when a condition becomes a bit too complex a UI customisation is often prefered. Lets take a look at the following example.


Comparing Share with and without a customised UI


Below you will find the definitions that is sent from the repo for 2 of the rule definitions that Share is using.







'in-category' from /api/actionconditiondefinitions 'transform' from /api/actiondefinitions

{
   'name' : 'in-category',
   'displayLabel' : 'Has category',
   'description' : 'The rule is applied to all items that has the specified category value.',
   'adHocPropertiesAllowed' : false,
   'parameterDefinitions' :
    [
       {
          'name' : 'category-aspect',
          'displayLabel' : 'Category aspect',
          'type' : 'd:qname',
          'isMultiValued' : false,
          'isMandatory' : true
      },
      {
         'name' : 'category-value',
         'displayLabel' : 'Category value',
         'type' : 'd:noderef',
         'isMultiValued' : false,
         'isMandatory' : true
      }
   ]       
}

{
   'name' : 'transform',
   'displayLabel' : 'Transform and copy content ',
   'description' : 'This will transform the the matched content and copy the result to a specific space',
   'adHocPropertiesAllowed' : false,         
   'parameterDefinitions' :
   [
      {
         'name' : 'mime-type',
         'displayLabel' : 'Mimetype',
         'type' : 'd:text',
         'constraint' : 'ac-mimetypes',
         'isMultiValued' : false,
         'isMandatory' : true
      },
      {
         'name' : 'destination-folder',
         'displayLabel' : 'Destination folder',
         'type' : 'd:noderef',
         'isMultiValued' : false,
         'isMandatory' : true
      },
      {
         'name' : 'assoc-type',
         'displayLabel' : 'Association type',
         'type' : 'd:qname',
         'isMultiValued' : false,
         'isMandatory' : true
      },
      {
         'name' : 'assoc-name',
         'displayLabel' : 'Association name',
         'type' : 'd:qname',
         'isMultiValued' : false,
         'isMandatory' : true
      },
      {
         'name' : 'overwrite-copy',
         'displayLabel' : 'Overwrite copy',
         'type' : 'd:boolean',
         'isMultiValued' : false,
         'isMandatory' : false
      }
   ],
   'applicableTypes' :
    [
       'cm:content'          
   ]       
},

The in-category rule condition is a quite simple condition that just takes two parameters: category-value and category-aspect. However, even though category-value is defined as a mandatory parameter the value sent to the repo is actually always the same. The problem with category-aspect is that it is of type d:nodeRef requiring a nodeRef string as the value rather than the aspect name (such as 'Classifiable').

The transform rule action is quite a complex action that takes 5 parameters. Just like 'Has category' it has 2 parameters ( association-type and association-name) which are mandatory but the values sent to the server shall always be the same. It also has 1 non mandatory property overwrite-copy of type boolean, which doesn't need to be set, which is rendered as a drop down with the following options 'Yes', 'No' and <empty string>. It also has a destination-folder parameter of type d:nodeRef requiring a nodeRef, rather than a path, to the folder. A value, which of course, is impossible for a normal user to set the value for. The mimetype parameter however looks perfect but that is hardly a comfort for a user that has to set the other values.











Edit mode with customisation: Edit mode without customisation:
Share_Rules_With_Customisation.pngShare_Rules_Without_Customisation.png
Text mode with customisation: Text mode without customisation:
Share_Rules_With_Customisation_text.pngShare_Rules_Without_Customisation_text.png




Making Share's UI more user friendly


Lets use the previous example in a walk through to gain understanding of what and how we can change the rules definition UI in text and edit mode.

First of all you will see that the configuration 'boxes' above are 3 different share components:


  • rule-config-type.get ('When') - Handling configuration of rule type defininitions (aka 'events')
  • rule-config-condition.get ('If all criteria are met' & 'Unless all criteria are met') - Handles configuration of rule action condition definitions (aka 'conditions')
  • rule-config-action.get' ('Perform Action') - Handles configuration of rule action definitions (aka 'actions')



Each of these components has its own config.xml file in which you can do basic configurations such as:


  • Define the rule definitions data source and remove the ones you don't need
  • Order and group options in the rule definition menu
  • Decide from which webscript to load the constraints

It is also possible to do more advanced configuration that affect the UI rendering of each rule definition and its parameters but first lets explain how to customise the just mentioned alternatives alternatives.


Define the rule definitions data source and remove the ones you don't need







rule-config-type.get.config.xmlrule-config-condition.get.config.xml rule-config-action.get.config.xml

<rule-config type='type'>
   ...
   <config-definitions webscript='/api/ruletypes'/>
   ...
</rule-config>

<rule-config type='condition'>
   ...
   <config-definitions webscript='/api/actionconditiondefinitions'>
      <remove>
         <condition name='composite-condition'/>
         <condition name='compare-text-property'/>
         <condition name='compare-integer-property'/>
         <condition name='compare-date-property'/>
      </remove>
   </config-definitions>
   ...
</rule-config>

<rule-config type='action'>
   ...
   <config-definitions webscript='/api/actiondefinitions'>
      <remove>
         <action name='copy-to-web-project'/>
         <action name='transfer-this-tree'/>                                             
         ...
         <action name='compositeRenderingEngine'/>
      </remove>
   </config-definitions>
   ...
</rule-config>

Above are the parts of the config files that lets you define from which webscript url that the rule definitions shall be loaded. This is something that you most likely will NOT change. If you want to add a new rule definition (type, condition or action) you shall do that by configuring the repo's rule config files and your new definitions will automatically appear in the webscript above.

However if you want to remove a definition from the menu you can use the <remove> elements. These elements contain the name of the definitions that you do NOT want to appear in the menu.
To find the name you want to remove: run the url in the webscript attribute in your browser; take a look at the name attributes in the JSON response; add a new element inside the remove element with the name.

Note! The element names inside the remove differ in the different files. This is to make the config files easier to read.  This approach is used in other parts of the config files as well. The name should correspond to the root element's (rule-config) type attribute. In other words, if you would like to remove a definition in rule-config-type.get.config.xml you should create a <type> element.


Order and group options in the rule definition menu







rule-config-type.get.config.xmlrule-config-condition.get.config.xml rule-config-action.get.config.xml

<rule-config type='condition'>
   ...
  <menu>
      <group>
         <type name='inbound'/>
         <type name='update'/>
         <type name='outbound'/>
         <type name='*'/>
      </group>
  </menu>
   ...
</rule-config>

<rule-config type='condition'>
   ...
   <menu>
      <group>
         <condition name='no-condition'/>
      </group>
      <group>
         <property type='d:text' name='*'/>
      </group>
      <group>
         <property dataType='d:int' name='*'/>
         <property dataType='d:long' name='*'/>
         <property dataType='d:double' name='*'/>
         <property dataType='d:float' name='*'/>        
      </group>
      <group>
         <condition name='compare-mime-type'/>
      </group>
      <group>
         <property dataType='d:date' name='*'/>
         <property dataType='d:datetime' name='*'/>
      </group>
      <group>
         <property dataType='d:text' name='cm:created'/>
         <property dataType='d:text' name='cm:modified'/>
      </group>
      <group>
         <property dataType='d:text' name='cm:author'/>
      </group>
      <group>
         <property name='*'/>
      </group>
      <group>
         <condition name='has-tag'/>
         <condition name='in-category'/>
      </group>
      <group>
         <condition name='is-subtype'/>
      </group>
      <group>
         <condition name='has-aspect'/>
      </group>
      <group>
         <condition name='*'/>
      </group>
      <group>
         <item id='show-more'/>
      </group>
   </menu>
   ...
</rule-config>

<rule-config type='action'>
   ...
   <menu>
      <group>
         <item id='select'/>
      </group>
      <group>
         <action name='script'/>
      </group>
      <group>
         <action name='copy'/>
<action name='move'/>
      </group>
      ...
      <group>
         <action name='*'/>
      </group>
   </menu>
   ...
</rule-config>

In this section of the config files you configure the drop down menus aligned to the left in each row. The drop down menus contains rule definitions but also special menu items in the condition and action menus. Lets start with the simplest case first: the rule-config-type.config.xml config file.

The rule-config-type.config.xml contains one group element resulting in all rule types being grouped together into a single group.
This group is made up of type elements. These elements correspond to the rule type definition elements in the JSON response from the rule definition web script (defined in the config-definitions's webscript attribute). It is possible to match the type elements against other attributes as long as they are defined in the JSON response of the referenced config-definitions web script.
The simplest version of this config file could have just contained the <type name='*'/> element, which would have resulted in the same types being displayed, but not being diplayed in the same order as above.

Note about elements with wildcards ('*'):


  • When the menu is being created on the client (rule-config.js) it is done by reading the config from top to bottom in two passes: #1) First all the elements that DO NOT contain a wildcard are used to retrieve definitions that match their attributes. Once a definition matches an element in the config file it is placed in the menu and will not appear again in the menu even though it might match another element further down in the menu. #2) In the second pass only elements WITH wildcards are used to collect definitions and then only definitions that weren't used in the first pass will be included in the menu.
  • Attributes containing the wildcard character may only contain that character. It is currently NOT possible to match against part of an attribute value.
  • We recommend keeping the wildcard elements. New custom rule definitions will not appear in the menu if they do not match any rule definitions. If you are certain you ONLY want specific rule definitions in the menu, then you should remove the wildcard elements. To only remove specific definitions use the <remove> element in the <config-definition> element.




Special menu items


A <group> can contain type, conditionand action elements. These elements correspond against rule definition types. There are also <item> and <property> elements. Lets fist look at the <item> element.


The item elements

To support selections other than rule definitions, <item> elements can also be used. These will appear in the menu using the label defined in the config's *.get.properties file with the key 'menu.item.<id>'. i.e. the <item id='show-more'/>in rule-config-condition.get.config.xml Displays the label using the key 'menu.item.show-more'. Custom logs may also be added, this will be discussed in further detail under the <customise> element.


The property elements

The <property> element is a special element for the rule-config-condition.get component and is not supported elsewhere.
The <property> element allows you to define comparators for properties using the compare-property-value action condition definition.
In other words instead of first choosing the condition definition compare-property-value and then choosing which property, you may choose the property directly.

Just like <condition> elements are being matched against a data collection (the JSON response form the webscript url being defined in the webscript attribute in the <config-definition> element) the <property> elements are as well. However, the property data collection is a bit more complex. For example, if we were to match the menu config against the Data Dictionary in the repository, the menu would contain hundreds of values making it impossible to use. Therefore, only the properties the user has decided he wants to see are placed in the menu. For a new user the default properties are displayed until they have configured it otherwise.

This means that before the menu is created, a user specific data collection is being gathered which then is used when the <property> menu elements are used to construct the menu.
This data collection first uses the default properties (being defined in the <defaults> element) and then adds and removes properties that the user has decided that it wants to see or not to see (using the 'Show more...' menu option in Share). These properties are then loaded from the data dictionary webscripts and passed into the menu creation as a data collection of possible values to display.

Note: This means that even if a property like <property dataType='d:text' name='cm:created'/> which is being exclusively specified in the menu config might not show up because it might not be in the property data collection, since the user might have decided that it not wants to see it.


Decide from which webscript to load the constraints



   <constraints webscript='/api/actionConstraints'/>

Parameters to a rule definition might have defined a constraint attribute with a constraint name. A constraint is a collection of items that are retreived using the webscript defined in the webscript attribute in the <constraints> element and will show as a drop down in the ui. Constraints are currently used by the config for rule conditions and rules actions. It is most likely that you will never need to change this value ('/api/actionConstraints') since it is possible to modify or to configure new constraints in the repo.


Constraints for properties


rule-config-condition.get.config.xml



   <property-evaluators>
      ...
      <property type='d:text'>
         <evaluator name='EQUALS'/>
         <evaluator name='CONTAINS'/>
         <evaluator name='BEGINS'/>
         <evaluator name='ENDS'/>
      </property>
      ...
      <property type='d:date'>
         <evaluator name='EQUALS'/>
         <evaluator name='GREATER_THAN'/>
         <evaluator name='GREATER_THAN_EQUAL'/>
         <evaluator name='LESS_THAN'/>
         <evaluator name='LESS_THAN_EQUAL'/>
      </property>
      ...
      <property type='d:int'>
         <evaluator name='EQUALS'/>
         <evaluator name='GREATER_THAN'/>
         <evaluator name='GREATER_THAN_EQUAL'/>
         <evaluator name='LESS_THAN'/>
         <evaluator name='LESS_THAN_EQUAL'/>
      </property>
      ...
   </property-evaluators>



Properties use a constraint named ac-compare-operations which defines all evaluators needed by the compare-property-value action definition's evaluator property.
However since properties of all types (except mimetype) may be used with the compare-property-value condition definition we need to make sure we aren't using invalid combinations of evaluators and property types.
I.e. we can't compare a property value of type d:date using 'begins with' (BEGINS), which is an evaluator intended to be used on strings (i.e. d:text).

To define which property evaluators to use for a type visit the <property-evaluators> element in rule-config-condition.get.config.xml


Text customisations


After you have created a rule it is possible to view a text description of it on the folder's rule page where all the folder's rules are listed to the left and the selected rule is displayed in detail to the right.
The detailed display is also using the config components to display the rule config definitions. If you don't want to use the default text ui, with all parameter labels and values listed after each other, you can modify the text being displayed by simply editing the config component's .get.properties-file. If you look below you can see how this is done for the rule config condition component:

rule-config-condition.get.properties



customise.compare-property-value.text={name} {param.operation} {param.value}
...
customise.is-subtype.text=Is of type (or sub type) '{param.type}'
customise.has-tag.text=Is tagged with '{param.tag}'
customise.in-category.text=Is in category '{param.category-value}'
...

Looking above you can see that it follows the following pattern: customise.<rule-definition-name>.text=<custom text message goes here>
Lets walk through the examples:


  • customise.compare-property-value.text i.e. = Count equals 2
    • Uses {name} to get the same text that the drop down displays for this definition in the edit ui. (In this case a property name)
    • Uses {param.operation} to get the chosen value from the parameter named operation in the edit ui. (In this case its a parameter with a constraint, so the displayLabel for the selected operation will be displayed instead of the operation id)
    • Uses {param.value} to get the value for the parameter named value in the edit ui. (In this case a simple number given in a text field)
  • customise.is-subtype.text i.e. = Is of type (or sub type) 'Content'
    • Uses {param.type} to get the chosen value for the parameter named type in the edit ui. (In this case a parameter with a constraint, so the displayLabel for the selected type will be displayed instead of rhe type id)
  • customise.has-tag.text i.e. = Is tagged with 'holiday'
    • Uses {param.tag} to get the given value for the parameter named tag in the edit ui. (In this case a string in text field)

This is all that you need to do to display parameters of these simple types, where the value has a constraint or can be displayed directly because it is a value of a simple type such as d:int or d:text.
But what about nodeRef's that shall be resolved to paths or category display names? Well that will require you to 'hook in' some javascript code, which you can read about in the next section.
But the configuration needed in the .get.properties-files are just like for a regular tt>d:int</tt> or d:text.
Take a look at category for example, it's parameter category-value contains a nodeRef to the selected category, but to display it you will continue and use the exact same syntax as before:


  • customise.in-category.text i.e. = Is in category 'Countries'
    • Uses {param.category-value} to get the parameter named category-value in the edit ui.

Note that in this case Javascript code is needed to first resolve the nodeRef to the category display name. This is exactly what has been done for the link-category action definition in the example in the section below.


Advanced customisation


Up until now all the changes has been done using the .get.config.xml or .get.properties-files for each component, requiring no coding. However to go further and add custom ui controls beyond the default ui (such as file-, tag- or category-pickers, hide default values in edit mode etc) you will need to hook in JavaScript code. To do that follow these steps which assumes you want to override the link-category rule action definition:


  • Define and import the custom client side javascript:
    • Create a javascript file named rule-config-action-custom.js in {TOMCAT_HOME}/webapps/share-extension/components/rules/config/
    • Inside your new file create your own javascript class that extends Alfresc.RuleConfigAction. (Look below for code)
    • Take a copy of rule-edit.get.head.ftl and place it in {TOMCAT_HOME}/shared/classes/alfresco/web-exension/site-webscripts/org/alfresco/components/rules/
    • Add the script tag import at the end of the file:
    • Take a copy of rule-config-action.get.config.xml and place it in {TOMCAT_HOME}/shared/classes/alfresco/web-exension/site-webscripts/org/alfresco/components/rules/config/
    • Define your new javascript class as the component to use in the <rule-config> element's component attribute inside the file you just copied.
    • Let the rule config environment know that there is a special customisation made and for which rule action definnitions it shall be used for by adding  <action name='link-category'>LinkCategory</action> inside the <customise> element

Now you have made your own javascript class being loaded and acting as the client side controller for the config component. You have also told it that there is a customisation named LinkCategory, which isn't yet true. So go on and add the following code so your rule-config-action-custom.js looks like this:

rule-config-action-custom.js




/**
* RuleConfigActionCustom.
*
* @namespace YourCompany
* @class YourCompany.RuleConfigActionCustom
*/
(function()
{
   Alfresco.RuleConfigActionCustom = function(htmlId)
   {
      Alfresco.RuleConfigActionCustom.superclass.constructor.call(this, htmlId);

      // Re-register with our own name
      this.name = 'YourCompany.RuleConfigActionCustom';
      Alfresco.util.ComponentManager.reregister(this);

      // Instance variables
      this.customisations = YAHOO.lang.merge(this.customisations, Alfresco.RuleConfigActionCustom.superclass.customisations);
      this.renderers = YAHOO.lang.merge(this.renderers, Alfresco.RuleConfigActionCustom.superclass.renderers);

      return this;
   };

   YAHOO.extend(Alfresco.RuleConfigActionCustom, Alfresco.RuleConfigAction,
   {

      /**
       * CUSTOMISATIONS
       */

      customisations:
      {
     
         LinkCategory:
         {
            text: function(configDef, ruleConfig, configEl)
            {
               // Setting _type to 'category' will make the nodeRef get resolved to the category's name
               this._getParamDef(configDef, 'category-value')._type = 'category';
               return configDef;
            },
            edit: function(configDef, ruleConfig, configEl)
            {
               // Hide all parameters
               this._hideParameters(configDef.parameterDefinitions);
               // ...and add a new parameter who's type will match the category picker renderer defined below
               configDef.parameterDefinitions.push(
               {
                  type: 'arca:category-picker'
               });
               return configDef;
            }
         }

      },

      /**
       * RENDERERS
       */

      renderers:
      {
                
         'arca:category-picker':
         {
            /* Mark this renderer as 'manual' in edit mode so no default ui handling is done */
            manual:
            {
               edit: true
            },
            /* Object to save values in */
            currentCtx: {},
            edit: function (containerEl, configDef, paramDef, ruleConfig, value)
            {
               // Save variables from the last click in the currentCtx object
               this.renderers['arca:category-picker'].currentCtx =
               {
                  configDef: configDef,
                  ruleConfig: ruleConfig,
                  paramDef: paramDef
               };
              
               // Create a category picker
               var picker = new Alfresco.module.ControlWrapper(Alfresco.util.generateDomId());
               picker.setOptions(
               {
                  type: 'category',
                  container: containerEl,
                  value: (ruleConfig.parameterValues ? ruleConfig.parameterValues['category-value'] : null),
                  controlParams:
                  {
                     multipleSelectMode: false
                  },
                  fnValueChanged:
                  {
                     fn: function(obj)
                     {
                        // When picker is closed make sure we save the values from the picker in the hidden form fields
                        var ctx = this.renderers['arca:category-picker'].currentCtx;
                        this._setHiddenParameter(ctx.configDef, ctx.ruleConfig, 'category-aspect', 'cm:generalclassifiable');
                        this._setHiddenParameter(ctx.configDef, ctx.ruleConfig, 'category-value', obj.selectedItems[0]);
                        // Re run validations for the form connected to the config component
                        this._updateSubmitElements(ctx.configDef);
                     },
                     scope: this
                  }
               });
               picker.show();
            }
         }
      }
   });

})();


There are hook points in which you get your custom code runned:


  1. RuleConfig.customisations.<CustomisationName>.edit : function(configDef, ruleConfig, configEl) - Called before the rendering starts. In here you can manipulate the rule definition (or the configDef which is what the object is called when passed into the callback), meaning that you can set the parameter's type to a custom value which you later can provide a renderer for. You can also choose to hide certain parameters or change their value or display label,
  2. Alfresco.RuleConfig.renderers['<parameterTypeName>'].edit: function (containerEl, configDef, paramDef, ruleConfig, value) At parameter rendition time. Each parameter is rendered separately and the renderer method to use is located by looking at the parameter's type. All the default renderes for types such as 'd:text', 'd:int' and so on are already implemented, but if you want a custom rendition of something you will need to come up with your 'own' type.

Note that these methods very well may me be used in text mode as well, but then make sure to place them inside the text mode name space instead of edit.

To go further than this please investigate the source code.

Outcomes