Friday, June 8, 2012

How to add custom Share site presets (the nice way) - Alfresco Hack #5

[See here about my post about the benefits of the b2b marketplace for Alfresco modules and solutions.]
[Check out our newest module for email management and archiving with Alfresco]
[See here about our even newer module for automatic trashcan management with Alfresco]

This blog post will explain in depth a way to add custom site presets to Alfresco share as nice as possible. Nice here means, to play well with others:
  • use the extension mechanism
  • do not overwrite any existing configuration files
  • do not overwrite any Alfresco Spring beans
This way, many modules can be deployed independently to Alfresco without interfering, each providing a new site preset.

Currently, as of Alfresco 4.0dCE and 4.0.1EE it is not possible to add presets through the share extension mechanism alone.

A common requirement of anybody who likes to customize Alfresco share is to provide custom site presets. For example, show the links-Page besides the document library per default. Or to present a specialized page targeting a specific use case. A common requirement for ourselves and the ecm Market vendors as well.


What is a share site preset?


A site preset contains the initial configuration of a site: The site dashboard layout, and any preconfigured site dashlets. The initial set of site pages can be set as well. Examples for existing site pages are the document library page, the calendar, the wiki, the datalist page. It is an easy way to add your custom requirements to a site. So, to sum it up, a site preset is maybe better unterstood as a site type, because it captures all behaviour that makes it special (or different) from others. And of course, multiple sites can be instantiated from the site type.


How to create a share site preset?


The steps are simple as noted below - but to play nice with other, care must be taken to not override files and spring beans:
  1. Create a preset file containing the XML configuration of your site : my-custom-presets.xml. A good starting point is to copy the existing presets.xml and remove all unnecessary configuration 
  2. Create an extension to augment the presets UI dialog controller with your new site data.
  3. Add our new presets file to the list of preset files that the share preset manager recognizes.
Steps 1 and 2 can be done without overwriting any files using the existing share extension mechanism, but step 3 would need to overwrite the presets manager bean named webframework.presets.manager to add the new preset. This is the presets manager bean (it is part of surf located in the spring-surf-1.0.0.jar) as it comes with alfresco:



   <!-- Presets manager - configured to walk a search path for preset definition files -->
   <bean id="webframework.presets.manager" class="org.springframework.extensions.surf.PresetsManager">
      <property name="modelObjectService" ref="webframework.service.modelobject" />
      <property name="searchPath" ref="webframework.presets.searchpath" />
      <property name="files">
         <list>
            <value>presets.xml</value>
         </list>
      </property>
   </bean>

Out of the box, it would be possible to place a file named presets.xml in a package named alfresco/site-data/presets or alfresco/web-extension/site-data/presets - which is resolved by the the search path. But because the file name is fixed to presets.xml it is not possible to deploy multiple presets without overwriting each other. But to the rescue comes the tool I introduced in the blog post Augmenting Alfresco Spring Bean configuration without overwriting beans. This litte class will add the my-custom-presets.xml file the the presets manager bean on startup dynamically, so that the original bean must not be overridden:

<bean id="ecm4u.custom.presets" class="de.ecm4u.alfresco.utils.spring.AddToListPropertyPostProcessor">
    <property name="beanName" value="webframework.presets.manager" />
    <property name="propertyName" value="files" />
    <property name="position" value="-1" />
    <property name="additionalProperties">
        <list>
            <value>my-custom-presets.xml</value>
        </list>
    </property>
</bean>

A module using this tooling plays nice with others, because no overwriting as taken place.

Back to step 2,  creating the share extension to augment the site creation controller with the new site:

Place a file my-custom-extension.xml into alfresco/site-data/extensions/ with the following contents. It is a share extension with a single package customization.


<extension>
    <modules>
        <module>
            <id>my-custom-site-preset</id>
            <auto-deploy>true</auto-deploy>
            <customizations>
                <customization>             <targetPackageRoot>org.alfresco.modules</targetPackageRoot>                <sourcePackageRoot>ecm4u.samples.custom.modules</sourcePackageRoot>
                </customization>
            </customizations>
        </module>
    </modules>
</extension>



To finally make your site preset available for selection in the share create site dialog, a short javascript snippet is needed. Add this snippet to a file named create-site.get.js into a package webscripts.ecm4u.sampels.custom.modules:

if(model.sitePresets) {
    model.sitePresets.push({
        id: "my-custom-preset-id",
        name: "My custom site"
      });
}

Back to step 1, creating the presets description file:

How a presets file might actually look like is shown here. Add this file at alfresco/site-data/presets and name it my-custom-presets.xml:

Please note, that the preset id has to match the id parameter of the script customization above.

<?xml version='1.0' encoding='UTF-8'?>
<presets>
   <preset id="my-custom-preset-id">
      <components>         
         <!-- title -->
         <component>
            <scope>page</scope>
            <region-id>title</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/title/collaboration-title</url>
         </component>
         <!-- navigation -->
         <component>
            <scope>page</scope>
            <region-id>navigation</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/navigation/collaboration-navigation</url>
         </component>
         <!-- dashboard components -->
         <component>
            <scope>page</scope>
            <region-id>full-width-dashlet</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/dashlets/dynamic-welcome</url>
            <properties>
               <dashboardType>site</dashboardType>
            </properties>
         </component>
         <component>
            <scope>page</scope>
            <region-id>component-1-1</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/dashlets/colleagues</url>
            <properties>
               <height>504</height>
            </properties>
         </component>
         <component>
            <scope>page</scope>
            <region-id>component-2-1</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/dashlets/docsummary</url>
         </component>
         <component>
            <scope>page</scope>
            <region-id>component-2-2</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/dashlets/activityfeed</url>
         </component>
      </components>
      <pages>
         <page id="site/${siteid}/dashboard">
            <title>Collaboration lodda  Site Dashboard</title>
            <description>Collaboration lodda site's dashboard page</description>
            <template-instance>dashboard-2-columns-wide-right</template-instance>
            <authentication>user</authentication>
            <properties>
               <sitePages>[{"pageId":"documentlibrary"}, {"pageId":"links"}]</sitePages>
            </properties>
         </page>
      </pages>
   </preset>
</presets>


Quite some lengthy steps, to sum up:

  • a presets file describing your site preset configuration
  • a share extension configuration
  • a spring bean definition to use the Spring AddToListPropertyPostProcessor
But you will be able to let your users to create sites of a new type. New site types are a perfect way to introduce new functionality and use case into Alfresco. And because no files will be overwritten using this approach, it is possible to create different AMP files, each of it containing a new site type.


Let me know if it works - but also if it not works of course;)


One thing that comes to mind - the share extension has to be activated. To activate the module automatically on share startup, add this to your shared/classes/alfresco/web-extension/share-config-custom.xml file:



        <config evaluator="string-compare" condition="WebFramework">
                <web-framework>
                        <module-deployment>
                                <mode>manual</mode>
                                <enable-auto-deploy-modules>true</enable-auto-deploy-modules>
                        </module-deployment>
                </web-framework>
        </config>