Monday, October 1, 2018

Configuring Alfresco Behaviors for Associations

Alfresco metadata is stored as properties and also associations. Associations show relationships between source and target objects in the Alfresco repository.  When querying metadata via the API, or when viewing the metadata in the Node Browser, properties and associations are separated.

Even when defining an Alfresco behavior, there are differences in how a behavior is defined, depending on whether properties or associations are the source of the behavior action.

There are a number of tutorials or examples in blog articles that describe how to define a behavior in Alfresco, but most focus on behaviors that involve properties and not associations.  In this post, let's look at what is involved in creating a behavior triggered by the addition or deletion of an association.

First, we create a custom content model, called AspectTest.xml, as follows:

<?xml version="1.0" encoding="UTF-8"?>
<model xmlns="http://www.alfresco.org/model/dictionary/1.0" name="aspt:AspectTest">
    <description>Aspect Test</description>
    <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="aspecttest" prefix="aspt"/>
    </namespaces>
 
    <data-types/>
    <constraints/>
    <types/>
    <aspects>
        <aspect name="aspt:ATest">
            <title>ATest Aspect</title>
            <properties>
    <property name="aspt:projManager">
    <title>Project Manager</title>
    <type>d:text</type>
    <index enabled="true">
     <atomic>true</atomic>
     <stored>false</stored>
     <tokenised>both</tokenised>
     <facetable>true</facetable>
    </index>
    </property>
   </properties>
   <associations>
                <association name="aspt:projectManager">
                    <title>Project Manager</title>
                    <source>
                        <mandatory>false</mandatory>
                        <many>true</many>
                    </source>
                    <target>
                        <class>cm:person</class>
                        <mandatory>false</mandatory>
                        <many>true</many>
     </target>
                </association>
   </associations>
            <overrides/>
            <mandatory-aspects/>
        </aspect>
    </aspects>
</model>

Put it into the alfresco/extension/models directory. This defines an association and a text field.
Then create the file to tell Alfresco to use the custom content model. Create the file testaspect-context.xml and place it in the alfresco/extension directory.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
 
<beans>
    <!-- Registration of new models --> 
    <bean id="testAspect.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap">
        <property name="models">
            <list>
                <value>alfresco/extension/model/AspectTest.xml</value>
            </list>
        </property>
    </bean>
 
    <bean id="onDeletePrjMgrProperty" class="org.alfresco.repo.policy.registration.AssociationPolicyRegistration" parent="policyRegistration">
        <property name="policyName">
            <value>{http://www.alfresco.org}onDeleteAssociation</value>
        </property>
        <property name="className">
            <value>{aspecttest}ATest</value>
        </property>
        <property name="associationType">
            <value>{aspecttest}projectManager</value>
        </property>
        <property name="behaviour">
            <bean class="org.alfresco.repo.jscript.ScriptBehaviour" parent="scriptBehaviour">
                <property name="location">
                    <bean class="org.alfresco.repo.jscript.ClasspathScriptLocation">
                        <constructor-arg>
                            <value>alfresco/scripts/onAssocUpdate.js</value>
                        </constructor-arg>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
 
    <bean id="onAddPrjMgrProperty" class="org.alfresco.repo.policy.registration.AssociationPolicyRegistration" parent="policyRegistration">
        <property name="policyName">
            <value>{http://www.alfresco.org}onCreateAssociation</value>
        </property>
        <property name="className">
            <value>{aspecttest}ATest</value>
        </property>
        <property name="associationType">
            <value>{aspecttest}projectManager</value>
        </property>
        <property name="behaviour">
            <bean class="org.alfresco.repo.jscript.ScriptBehaviour" parent="scriptBehaviour">
                <property name="location">
                    <bean class="org.alfresco.repo.jscript.ClasspathScriptLocation">
                        <constructor-arg>
                            <value>alfresco/scripts/onAssocUpdate.js</value>
                        </constructor-arg>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
          
</beans>

This file will wire in the custom model.  It also defines the behaviors. The last two bean definitions define behaviors that will run in Javascript for our newly defined association.  Note the reference to the Java class
org.alfresco.repo.policy.registration.AssociationPolicyRegistration.

The first of these invokes the behavior when associations are deleted, and the second is invoked when associations are added.
The behaviors call the Javascript function.  It is called alfresco/scripts/onAssocUpdate.js.

if (behaviour == null) 
{
    scriptFailed = true;
}

// Check the name of the behaviour
if (behaviour.name == null || behaviour.args[0].getType() != "{aspecttest}projectManager" || 
   (behaviour.name != "onCreateAssociation" && behaviour.name != "onDeleteAssociation") )
{
    scriptFailed = true;
} 
else 
{
 // Check the arguments
 if (behaviour.args == null || ('source' in behaviour.args[0]) == false || ('target' in behaviour.args[0]) == false) 
 {
  scriptFailed = true;
 } 
 else 
 {
  if (behaviour.args.length == 1) 
  {
   var source = behaviour.args[0].source;
   
   if(source!=null)
   {
    var assocs = behaviour.args[0].source.assocs["{aspecttest}projectManager"];
    var textVal = "";
    if(assocs!=null)
    {
     for(var i=0; i<assocs.length; i++)
     {
      if(i!=0) textVal += ",";
      textVal += assocs[i].properties["cm:userName"];
     }
    }
    source.properties["aspt:projManager"] = textVal;
    source.save();
   }
   else
   {
    scriptFailed = true;
   }
  } 
  else 
  {
   scriptFailed = true;
  }    
 }
}

The example Javascript code behavior will concatenate the list of users selected by the association into a text string that is populated into the new text property called aspt:projManager.

One of the peculiarities of Alfresco associations is that they are not searchable by queries.  As a workaround, this behavior will enable the search of user names selected by an association picker and stored the picked results in a text field.  The text field can be searched, even though the association cannot.

That's almost all.  But there is also some code involved in setting up the picker on an Alfresco search form.   For example, we can add the following extension configuration into Share to enable the assignment of our new association and also the display of it on a form.

<alfresco-config>
      <!-- Form configuration section - aspect -->
      <config condition="aspt:ATest" evaluator="aspect">
         <forms>
            <form>
               <field-visibility>
                  <show id="aspt:projectManager" />
               </field-visibility>
               <appearance>
                  <field id="aspt:projectManager">
                     <control template="/org/alfresco/components/form/controls/authority.ftl" />
                  </field>
               </appearance>
            </form>
         </forms>
      </config>
      <!-- Document Library config section -->
      <config evaluator="string-compare" condition="DocumentLibrary">
         <aspects>
            <visible>
               <aspect name="aspt:ATest" />
            </visible>
         </aspects>
      </config>
</alfresco-config>

The picker control for the association in Share will look like this:


And, using the above example picker results, user mjackson can be searched for as follows by searching on the text property which is synced with the values in the association (aspt:projManager:mjackson):

No comments:

Post a Comment