Select metadata edit form based on aspect

2012-05-04

For a client I needed to come up with a solution to use a specific metadata edit form when the document had a certain aspect applied. Reason was that that if it had this aspect, you should only be allowed to edit this subset of metadata, not the default name, title etc (sort of a lockdown). Out of the box you can only configure different forms based on document type so I needed to find a way to see if the document had the aspect applied, and if so show a special form.

In Share, when I need to alter default functionality I start with looking into the page I need to change, and then find out how this page is called, and if I can add parameters or need to change javascript. In this case it is page/site/{site}/edit-metadata that I somehow need to tell to use a different form. This page use template-instances/edit-metadata.xml, and in there is a form component

<!-- Edit Metadata Form -->
<component>
  <region-id>edit-metadata</region-id>
  <url>/components/form</url>
  <properties>
    <itemKind>node</itemKind>
    <itemId>{nodeRef}</itemId>
    <mode>edit</mode>
    <submitType>json</submitType>
    <showCaption>true</showCaption>
    <showCancelButton>true</showCancelButton>
  </properties>
</component>
 

The form is rendered using the webscript /components/form, now I need to find out if there is a way to pass in a form id. I then look in folder site-webscripts/org/alfresco/components/form/ to find out how the form generation works, form.get.html.ftl includes form.lib.ftl and in there I can see that you can pass formId as an argument. I can now either alter template-instances/edit-metadata.xml, or use the new Surf extensibility functionality (from version 4.0). With a surf extension i add to site-data/extension a file called myextension.xml with content

<extension>
     <modules>
         <module>
             <id>ReplaceForm</id>
             <description>Show edit metadata for specific form</description>
             <components>
                 <component>
                     <scope>template</scope>
                     <region-id>edit-metadata</region-id>
                     <source-id>edit-metadata</source-id>
                     <sub-components>
                         <sub-component id="default">
                             <evaluations>
                                 <evaluation id="hide">
                                     <render>false</render>
                                 </evaluation>
                             </evaluations>
                         </sub-component>
                         <sub-component id="edit-metadata-new" index="25">
                             <url>/components/form</url>
                             <properties>
                                 <itemKind>node</itemKind>
                                 <itemId>{nodeRef}</itemId>
                                 <formId>{formId}</formId>
                                 <mode>edit</mode> 
                                <submitType>json</submitType> 
                                <showCaption>true</showCaption>
                                 <showCancelButton>true</showCancelButton>
                             </properties>
                         </sub-component>
                     </sub-components>
                 </component>
             </components>
         </module>
     </modules>
</extension>

I will not go into details about surf extension, instead watch the Alfresco Webinar about Surf extension with David Draper and read the slides. What happens when this customization is applied is that the default component is replaced by one that can take formId as url parameter. You can easily test this (when above extension is applied) by navigating to page edit-metadata and in the end of the url add &formId=doclib-simple-metadata. You will se the form that is normally just used for the popup edit metadata dialog in the full page edit.

So now I need a way to call this page with the formId parameter. The edit-metadata page is opened using a document library action, so I need to append the formId parameter if (and only then) the aspect in question is used. From version 4.0 this is in configuration that you can easily alter. The default config for actions is found in share/WEB-INF/classes/alfresco/share-documentlibrary-config.xml. From there I came up with this config that you can put in share-config-custom.xml

<config evaluator="string-compare" condition="DocLibActions">

  <!-- Action definitions -->
  <actions>
    <action id="document-edit-metadata" type="pagelink" label="actions.document.edit-metadata">
      <param name="page">edit-metadata?nodeRef={node.nodeRef}</param>
      <permissions>
        <permission allow="true">Write</permission>
      </permissions>
      <evaluator negate="true">evaluator.doclib.action.isLocked</evaluator>
      <override>document-edit-properties</override>
    </action>

    <action id="document-edit-metadata-diarie" type="pagelink" label="actions.document.edit-metadata">
      <param name="page"><![CDATA[edit-metadata?nodeRef={node.nodeRef}&formId=diarie]]></param>
      <permissions>
        <permission allow="true">Write</permission>
      </permissions>
      <evaluator>evaluator.isDiarieNotLocked</evaluator>
      <override>document-edit-metadata</override>
      <override>document-edit-properties</override>
    </action>
  </actions>
 
  <!-- Action Group definitions -->
  <actionGroups>
    <actionGroup id="document-browse">
      <action index="130" id="document-edit-metadata" />
      <action index="130" id="document-edit-metadata-diarie" />
    </actionGroup>
    <actionGroup id="document-details">
      <action index="120" id="document-edit-metadata-diarie" />
    </actionGroup>
  </actionGroups>
</config>

It does two things, first I replace the popup metadata edit in browse mode, I want the full page. So that is why the default config for action ‘document-edit-metadata’ is added. I cannot only add this action to Action group definitions, because then you will see both, that is why I redefine the action, but now with an override to hide document-edit-properties if used. Next is to add my new action ‘document-edit-metadata-diarie’ that has the formId parameter. Note that it needs enclose the parameter in CDATA to be valid xml. If the evaluator returns true, then use this one and override the others. So next we need to define the evaluator.isDiarieNotLocked. Default evaluators can be found in share/WEB-INF/classes/alfresco/slingshot-documentlibrary-context.xml. Put yours in a mycustom-context.xml file in the class-path.

<bean id="evaluator.hasdiariefordaspect" parent="evaluator.doclib.action.hasAspect">
  <property name="aspects">
    <list>
      <value>lx:diarieFord</value>
    </list>
  </property>
</bean>

<bean id="evaluator.isNotLocked" parent="evaluator.doclib.action.chainedMatchOne">
  <property name="evaluators">
    <list>
      <ref bean="evaluator.doclib.action.notEditable" />
    </list>
  </property>
  <property name="negateOutput" value="true" />
</bean>

<bean id="evaluator.isDiarieNotLocked" parent="evaluator.doclib.action.chainedMatchAll">
  <property name="evaluators">
    <list>
      <ref bean="evaluator.hasdiariefordaspect" />
      <ref bean="evaluator.isNotLocked" />
    </list>
  </property>
</bean>

So my evaluators does this, evaluator.hasdiariefordaspect checks that is has the required aspect, evaluator.isNotLocked evaluates if it can be edited and is not locked (this one propbably can be defined in a smarter way, but it works), then finally put together in evaluator.isDiarieNotLocked where we need both previous evaluators to be true. If you want to learn more about Share Document library extension read Mike Hatfields blog.

And finally we have customized Share to use a specific form for edit metadata when the document has the selected aspect applied. With only configuration files!

Note that my approach lets user with a bit of knowledge add whatever form id they want in the url, and thus potentially edit hidden metadata. In this use case it was not considered an issue.