Setting user preferences via JavaScript Console

While upgrading a huge Alfresco system from 3.4 to 4.0 I was asked if it is possible to collapse all sections (excepting the actions) on the document-details page.

The state of these twisters is saved for each user in his preferences (preference: org.alfresco.share.twisters.collapsed), so I wrote a little script using the JavaScript Console:

var docDetailsTwisters = ["DocumentTags","DocumentLinks","DocumentMetadata","DocumentPermissions","DocumentWorkflows","DocumentVersions","DocumentPublishing"];
var nodes = search.luceneSearch('+TYPE:"cm:person"');
logger.log(nodes.length);
for each(var node in nodes) {
    var userid = node.properties.userName;
    logger.log(userid);
    var twisterCollapPref = preferenceService.getPreferences(userid, "org.alfresco.share.twisters.collapsed");
    var newTwisters;
    if (twisterCollapPref.org != null){
      var twistersToAdd = [];
      var twistersSet = twisterCollapPref.org.alfresco.share.twisters.collapsed;
      logger.log("collapsed twisters: " + twistersSet);
      for each (var t in docDetailsTwisters){
        if (twistersSet.indexOf(t) == -1){
          twistersToAdd.push(t);
        }
      }
      newTwisters = twistersSet + (twistersToAdd.length > 0 ? ("," + twistersToAdd.join(",")) : "");

    }
    else{
      newTwisters = docDetailsTwisters.join(",");
    }
  preferenceService.setPreferences(userid, {org :{alfresco : {share : {twisters : {collapsed : newTwisters}}}}});

  logger.log("new collapsed twisters:" + newTwisters);
}

Getting Alfresco Content Types via JavaScript

I was asked if it is possible to access Alfresco’s DictionaryService via JavaScript…Well, not directly using an offical Alfresco JS Root-Object, but it isn’t a big task if you use some functions of the embedded Rhine engine (& of course Florian’s famous Alfresco JavaScript Console http://alfresco.fme.de/JavaScript-Console.923.0.html):

var ctx = Packages.org.springframework.web.context.ContextLoader.getCurrentWebApplicationContext();
var model = Packages.org.alfresco.model.ContentModel;
var dictionaryService = ctx.getBean("DictionaryService");
types = dictionaryService.getSubTypes(model.TYPE_CONTENT, true).toArray();
for each(var type in types){
  print(type);
}

Monitoring Alfresco with New Relic

I came across New Relic a few days ago & decided after a few minutes that I HAVE to try out that cool.

1. Step – the Basics

My first step, adding a New relic agent to a tomcat server that runs Alfresco was pretty easy:

  1. SignUp to New Relic
  2. Download New Relics jar-Files
  3. Deploy them as described here: http://newrelic.com/docs/java/new-relic-for-java

After some minutes I was able to use the New Relic WebClient & try out some basic application monitoring features like:

  • HTTP Response Times
  • SQL Response Times
  • JVM Monitoring (CPU, Heap, GC etc.)
  • Transaction Traces etc

2. Step – Real User Monitoring

But the most impressive feature is their “real user monitoring” – They monitor the end user browser performance by automatically adding a small JS snippet to each HTML page. If you’ve JSPs that may be done automatically, but iId like to monitor an Alfresco Share application.

Hands-on:

1st: A custom Freemarker TemplateMethod to be used within Share (or any other SURF webapp):

package org.springframework.extensions.webscripts;
public class NewRelicMethod extends org.springframework.extensions.webscripts.processor.BaseProcessorExtension
{
	
    public String getBrowserTimingHeader(){
    	return com.newrelic.api.agent.NewRelic.getBrowserTimingHeader();
	}
	
 	public String getBrowserTimingFooter(){
		return com.newrelic.api.agent.NewRelic.getBrowserTimingFooter();
		
	}
}

2nd: Spring Bean definition web-extension/custom-newrelic-context.xml:

<![CDATA[
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
   <bean id="newrelicTemplateExtension" parent="baseTemplateExtension" class="org.springframework.extensions.webscripts.NewRelicMethod">
       <property name="extensionName">
           <value>newrelic</value>
       </property>
   </bean>
</beans>]]>

3rd: Add ${newrelic.browserTimingHeader} & ${newrelic.browserTimingFooter} alfresco/templates/org/alfresco/include/alfresco-template.ftl

<#import "../import/alfresco-common.ftl" as common />

<#-- Global flags retrieved from share-config (or share-config-custom) -->
<#assign DEBUG=(common.globalConfig("client-debug", "false") = "true")>
<#assign AUTOLOGGING=(common.globalConfig("client-debug-autologging", "false") = "true")>
<#-- allow theme to be specified in url args - helps debugging themes -->
<#assign theme = (page.url.args.theme!theme)?html />
<#-- Portlet container detection -->
<#assign PORTLET=(context.attributes.portletHost!false)>

<#-- Look up page title from message bundles where possible -->
<#assign pageTitle = page.title />
<#if page.titleId??>
   <#assign pageTitle = (msg(page.titleId))!page.title>
</#if>
<#if context.properties["page-titleId"]??>
   <#assign pageTitle = msg(context.properties["page-titleId"])>
</#if>

<#--
   JavaScript minimisation via YUI Compressor.
-->
<#macro script type src>
   <script type="${type}" src="${DEBUG?string(src, src?replace(".js", "-min.js"))}"></script>
</#macro>
<#--
   Stylesheets gathered and rendered using @import to workaround IEBug KB262161
-->
<#assign templateStylesheets = []>
<#macro link rel type href>
   <#assign templateStylesheets = templateStylesheets + [href]>
</#macro>
<#macro renderStylesheets>
   <style type="text/css" media="screen">
   <#list templateStylesheets as href>
      @import "${href}";
   </#list>
   </style>
</#macro>

<#--
   Template "templateHeader" macro.
   Includes preloaded YUI assets and essential site-wide libraries.
-->                                                                           
<#macro templateHeader doctype="strict">
<#if !PORTLET>
   <#if doctype = "strict">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
   <#else>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   </#if>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>${msg("page.title", pageTitle)}</title>
   <meta http-equiv="X-UA-Compatible" content="Edge" />
</#if>
    <#if !context.requestContext.passiveMode>
		${newrelic.browserTimingHeader}
    </#if>

<!-- Shortcut Icons -->
   <link rel="shortcut icon" href="${url.context}/res/favicon.ico" type="image/vnd.microsoft.icon" /> 
   <link rel="icon" href="${url.context}/res/favicon.ico" type="image/vnd.microsoft.icon" />

<!-- Site-wide YUI Assets -->
   <@link rel="stylesheet" type="text/css" href="${url.context}/res/css/yui-fonts-grids.css" />
   <#if theme = 'default'>
      <@link rel="stylesheet" type="text/css" href="${url.context}/res/yui/assets/skins/default/skin.css" />
   <#else>
      <@link rel="stylesheet" type="text/css" href="${url.context}/res/themes/${theme}/yui/assets/skin.css" />   
   </#if>
<#-- Selected components preloaded here for better UI experience. -->
<#if DEBUG>
   <script type="text/javascript" src="${url.context}/res/js/log4javascript.v1.4.1.js"></script>
<!-- Common YUI components: DEBUG -->
   <script type="text/javascript" src="${url.context}/res/yui/yahoo/yahoo-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/event/event-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/dom/dom-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/dragdrop/dragdrop-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/animation/animation-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/logger/logger-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/connection/connection-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/element/element-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/get/get-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/yuiloader/yuiloader-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/button/button-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/container/container-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/menu/menu-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/json/json-debug.js"></script>
   <script type="text/javascript" src="${url.context}/res/yui/selector/selector-debug.js"></script>
<!-- YUI Patches -->
   <script type="text/javascript" src="${url.context}/res/yui/yui-patch.js"></script>
   <script type="text/javascript">//<![CDATA[
      YAHOO.util.Event.throwErrors = true;
   //]]></script>
<#else>
<!-- Common YUI components: RELEASE concatenated -->
   <script type="text/javascript" src="${url.context}/res/js/yui-common.js"></script>
</#if>

<!-- Site-wide Common Assets -->
<#if PORTLET>
   <@link rel="stylesheet" type="text/css" href="${url.context}/res/css/portlet.css" />
</#if>
   <@link rel="stylesheet" type="text/css" href="${url.context}/res/css/base.css" />
   <@link rel="stylesheet" type="text/css" href="${url.context}/res/css/yui-layout.css" />   
   <@link rel="stylesheet" type="text/css" href="${url.context}/res/themes/${theme}/presentation.css" />
   <@script type="text/javascript" src="${url.context}/res/js/bubbling.v2.1.js"></@script>
   <script type="text/javascript">//<![CDATA[
      YAHOO.Bubbling.unsubscribe = function(layer, handler)
      {
         this.bubble[layer].unsubscribe(handler);
      }
   //]]></script>
   <@script type="text/javascript" src="${url.context}/res/js/flash/AC_OETags.js"></@script>
   <#-- NOTE: Do not attempt to load -min.js version of messages.js -->
   <script type="text/javascript" src="${url.context}/service/messages.js?locale=${locale}"></script>
   <script type="text/javascript">//<![CDATA[
      Alfresco.constants = Alfresco.constants || {};
      Alfresco.constants.DEBUG = ${DEBUG?string};
      Alfresco.constants.AUTOLOGGING = ${AUTOLOGGING?string};
      Alfresco.constants.PROXY_URI = window.location.protocol + "//" + window.location.host + "${url.context}/proxy/alfresco/";
      Alfresco.constants.PROXY_URI_RELATIVE = "${url.context}/proxy/alfresco/";
      Alfresco.constants.PROXY_FEED_URI = window.location.protocol + "//" + window.location.host + "${url.context}/proxy/alfresco-feed/";
      Alfresco.constants.THEME = "${theme}";
      Alfresco.constants.URL_CONTEXT = "${url.context}/";
      Alfresco.constants.URL_RESCONTEXT = "${url.context}/res/";
      Alfresco.constants.URL_PAGECONTEXT = "${url.context}/page/";
      Alfresco.constants.URL_SERVICECONTEXT = "${url.context}/service/";
      Alfresco.constants.URL_FEEDSERVICECONTEXT = "${url.context}/feedservice/";
      Alfresco.constants.USERNAME = "${user.name!""}";
      Alfresco.constants.SITE = "${(page.url.templateArgs.site!"")?js_string}";
      Alfresco.constants.PAGEID = "${(page.url.templateArgs.pageid!"")?js_string}";
      Alfresco.constants.PORTLET = ${(context.attributes.portletHost!false)?string};
      Alfresco.constants.PORTLET_URL = unescape("${(context.attributes.portletUrl!"")?js_string}");
      Alfresco.constants.JS_LOCALE = "${locale}";
   <#if PORTLET>
      document.cookie = "JSESSIONID=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${url.context}";
   </#if>
   //]]></script>
   <@script type="text/javascript" src="${url.context}/res/js/alfresco.js"></@script>
   <@script type="text/javascript" src="${url.context}/res/js/forms-runtime.js"></@script>
   <@script type="text/javascript" src="${url.context}/res/js/share.js"></@script>
   <@common.uriTemplates />
   <@common.helpPages />
   <@common.htmlEditor htmlEditor="tinyMCE"/>
   
   <!-- Share Preference keys -->
   <script type="text/javascript">//<![CDATA[
      Alfresco.service.Preferences.FAVOURITE_DOCUMENTS = "org.alfresco.share.documents.favourites";
      Alfresco.service.Preferences.FAVOURITE_FOLDERS = "org.alfresco.share.folders.favourites";
      Alfresco.service.Preferences.FAVOURITE_SITES = "org.alfresco.share.sites.favourites";
      Alfresco.service.Preferences.IMAP_FAVOURITE_SITES = "org.alfresco.share.sites.imapFavourites";
      Alfresco.service.Preferences.COLLAPSED_TWISTERS = "org.alfresco.share.twisters.collapsed";
      Alfresco.service.Preferences.RULE_PROPERTY_SETTINGS = "org.alfresco.share.rule.properties";
   //]]></script>

<!-- Template Assets -->
<#nested>
<@renderStylesheets />

<!-- Component Assets -->
${head}

<!-- MSIE CSS fix overrides -->
   <!--[if lt IE 7]><link rel="stylesheet" type="text/css" href="${url.context}/res/css/ie6.css" /><![endif]-->
   <!--[if IE 7]><link rel="stylesheet" type="text/css" href="${url.context}/res/css/ie7.css" /><![endif]-->
<#if !PORTLET>
</head>
</#if>
</#macro>


<#--
   Template "templateHtmlEditorAssets" macro.
   Loads wrappers for Rich Text editors.
-->
<#macro templateHtmlEditorAssets>
<!-- HTML Editor Assets -->
   <script type="text/javascript" src="${page.url.context}/res/modules/editors/tiny_mce/tiny_mce${DEBUG?string("_src", "")}.js"></script>
   <@script type="text/javascript" src="${page.url.context}/res/modules/editors/tiny_mce.js"></@script>
   <@script type="text/javascript" src="${page.url.context}/res/modules/editors/yui_editor.js"></@script>
</#macro>


<#--
   Template "templateBody" macro.
   Pulls in main template body.
-->
<#macro templateBody>
<#if !PORTLET>
<body id="Share" class="yui-skin-${theme} alfresco-share">
</#if>
   <div class="sticky-wrapper">
      <div id="doc3">
<#-- Template-specific body markup -->
<#nested>
      </div>
      <div class="sticky-push"></div>
   </div>
</#macro>


<#--
   Template "templateFooter" macro.
   Pulls in template footer.
-->
<#macro templateFooter>
   <div class="sticky-footer">
<#-- Template-specific footer markup -->
<#nested>
   </div>
<#-- This function call MUST come after all other component includes. -->
   <div id="alfresco-yuiloader"></div>
   <#-- In portlet mode, Share doesn't own the <body> tag -->
   <script type="text/javascript">//<![CDATA[
      Alfresco.util.YUILoaderHelper.loadComponents(true);
      if (Alfresco.constants.PORTLET)
      {
         YUIDom.addClass(document.body, "yui-skin-${theme} alfresco-share");
      }
   //]]></script>
 <#if !context.requestContext.passiveMode>
	${newrelic.browserTimingFooter}
 </#if>
<#if !PORTLET>
</body>
</html>
</#if>
</#macro>

–> You’ll get some nice charts like these:

Alfresco Share datalist extensions – now Open Source!

I’ve developed some extensions to the default Alfresco Share datalists some months ago. My former blog post (http://blog.alfrescian.com/?p=95) gained a lot of interests at Alfresco & also in the Alfresco Community.

As part of my discussions with fme’s customer and Alfresco regarding a contribution of this extensions one of Alfresco’s Solution Engineers did a code review and was very satisfied with it: https://twitter.com/#!/rjmfernandes/status/136437605981097985

Finally fme’s customer agreed to make this extensions available under Open Source (Apache License) here: http://code.google.com/p/fme-alfresco-extensions/wiki/DatalistExtension

This gives the whole Alfresco community the opportunity to use & improve the extensions we made. So, please feel free to contact me if you like to contribute back some additional features or if you like to sponsor the further development (e.g. an upgrade to 4.0).

Alfresco Share Cluster – tweaks

I recently faced an upgrade from a single node Alfresco into a high available (HA) configuration (1 Load-Balancer, 2 Alfresco Nodes, 2 Share Nodes) using Alfresco Enterprise 3.4.2 (including kerberos SSO etc.).

The Basic steps (@see Kevs blog post http://blogs.alfresco.com/wp/kevinr/2011/07/28/using-apache-to-load-balance-alfresco-share-with-an-alfresco-repository-cluster-and-clustering-alfresco-share/) went well & my new system performed well. But then I realized that changes to a Share dashboard weren’t visible on the other Share instance. I forget to add the following configuration (“custom-slingshot-application-context.xml”):

<bean id="webframework.slingshot.persister.remote" class="org.springframework.extensions.surf.persister.PathStoreObjectPersister" parent="webframework.sitedata.persister.abstract">
      <property name="store" ref="webframework.webapp.store.remote" />
      <property name="pathPrefix"><value>alfresco/site-data/${objectTypeIds}</value></property>
      <property name="noncachableObjectTypes">
         <set>
            <value>page</value>
            <value>component</value>
         </set>
      </property>
   </bean>

But now my Share instances were very slow (dashboard layout changes weren’t visible on the other Share instance either). Loading a dashboard in the browser took up to 8s – without that cache disabling ~2s. Thus I made some tests with a vanilla 3.4.2 & 3.4.6 system and observed the same behavior (not as slow because I didn’t created a few hundred sites & >2000 users in my local system).

First of all I backported the 4.0.c org.springframework.extensions.webscripts.RequestCachingConnector that improved the whole Share performance a little bit. Afterwards I thought about the situations when a cached SURF object must be invalidated and reloaded via its remote store. AFAIK the only moment is when a dashboard configuration is changed on another Share instance. As my configuration only incorporates 2 Share instances I developed the follwing simple tweak to notify the other Share instance if a dashboard config is changed & the Surf object cache should be invalidated:

    re-enable caching for component & pages

  1. add a new HTTP endpoint (used to notify the other Share instance via HTTP if the SURF caches should be invalidated):
    <config evaluator="string-compare" condition="Remote">
    	  <remote>
    	    
    		<!-- Connector instance -->
    		<connector>
    			<id>share-cluster</id>
    			<name>Share Cluster Connector</name>
    			<description>HTTP Connector used to notify other share cluster nodes if surf object cache should be invalidated</description>
    			<class>org.springframework.extensions.webscripts.connector.HttpConnector</class>
    		</connector>
    	  
    		<!-- Endpoint -->
    	    <endpoint>
    	      <id>share-cluster</id>
    	      <name>Share Cluster Remote API</name>
    	      <connector-id>share-cluster</connector-id>
    	      <endpoint-url>http://[other-node]:8080/share/service</endpoint-url>
    	      <identity>none</identity>
    	    </endpoint>
    	    
    	  </remote>
    	</config>
    
  2. append the following lines to site-webscripts\org\alfresco\components\dashboard\customise-dashboard.post.json.js:
    try{
    	var shareClusterConnector = remote.connect("share-cluster");
    	shareClusterConnector.post("/api/fme/cache", clientRequest, "application/json");
    }catch (exception){
    	//nothing to do
    }
    
  3. added a new Java-backed webscript (Share) mapped to URI api/fme/cache with following executeImpl():
    protected Map<String, Object> executeImpl(WebScriptRequest req, Status status)
        {
                RequestContext rc = ThreadLocalRequestContext.getRequestContext();
                CacheUtil.invalidateModelObjectServiceCache(rc);
                
                // we must reset the SpringMVC view resolvers - as they maintain a reference to View
                // object which could themselves reference pages or templates by ID
                Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                        applicationContext, ViewResolver.class, true, false);
                for (ViewResolver resolver : matchingBeans.values())
                {
                    if (resolver instanceof AbstractCachingViewResolver)
                    {
                        logger.info("cleared AbstractCachingViewResolver " +resolver);
                        ((AbstractCachingViewResolver)resolver).clearCache();
                    }
                }
            ...
        }
    

The following things should be kept in mind:

  • this scenario only works with 2 Share instances & has to be enhanced if you’ve more than 2 nodes
  • api/fme/cache is not secured via an authenticator …

I’ll fill (& link) a detailed jira issue shortly.

Alfresco Share 4.x feature wishlist

Linton Baddeley (UX Designer at Alfresco) asked (tweet)

What features would you like to see in the next version of #Alfresco Share

here is my wishlist:

  • Enhanced Wiki component: TOC generation, embed document previews, add documents as attachment
  • Enhanced datalist component (http://blog.alfrescian.com/?p=95): versioning, comments as thread, upload&attach, XLS/PDF-Export/Import
  • datalist items assigned to a user or group should be shown in my task
  • Doclib: Link to Action, Export folder as zip, export selected documents as zip
  • doclib: optional grid view (configurable per site if standard or gridview should be used in doclib)
  • bidirectional Share calendar & MS Exchange integration ;-)
  • also copy & link as drag&drop operations
  • Yammer integration: publish document to yammer (link/attachment)
  • configurable documentlist dashlet: documents per tag, category, +Aspect, +Type…or simply a cmis/fts query
  • search: support solr highlighting, synonyms & facets OTB (configurable via *config.xml)

Adding a synonym list to SOLR in Alfresco 4.0

If you missed it: Alfresco Community 4.0.a is out: 
http://wiki.alfresco.com/wiki/Alfresco_Community_4.0.a
(and my category manager is part of the new feature list ;-) )

One of my favorite new features is the new Apache Solr based Search Service. I’ve done an Alfresco-Solr integration project two years ago. I was very impressed by the power of Solr & mainly by the combination of Alfresco & Solr. Thus my first deeper look into Alfresco 4.0 was about to understand the new Solr based search service & how the “whole stuff” is working.

After some hours browsing the current svn HEAD & researching Alfresco’s tracking, model & query addons to solr I tried to tweak Alfresco’s schema.xml:

Task: Add basic support of synonyms

Steps-to-do:

  1. Open alf_data/solr/workspace-SpacesStore/conf/schema.xml & add a new filter org.apache.solr.analysis.SynonymFilterFactory:
    <schema name="alfresco" version="1.0">
       <types>
          <fieldType name="alfrescoDataType" class="org.alfresco.solr.AlfrescoDataType">
             <analyzer>
                <tokenizer class="org.apache.solr.analysis.WhitespaceTokenizerFactory" />
                <filter class="org.apache.solr.analysis.WordDelimiterFilterFactory" 
                    generateWordParts="1" 
                    generateNumberParts="1"
                    catenateWords="1"
                    catenateNumbers="1"
                    catenateAll="1" 
                    splitOnCaseChange="1"
                    splitOnNumerics="1"
                    preserveOriginal="1" 
                   stemEnglishPossessive="1" />
                <filter class="org.apache.solr.analysis.LowerCaseFilterFactory" />
                <filter class="org.apache.solr.analysis.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true" />
             </analyzer>
          </fieldType>
       </types>
       <fields>
          <field name="ID" type="alfrescoDataType" indexed="true" omitNorms="true" stored="true" multiValued="true"></field>
          <dynamicField name="*" type="alfrescoDataType" indexed="true" omitNorms="true" stored="true" multiValued="true"></dynamicField>
       </fields>
       <uniqueKey>ID</uniqueKey>
       <defaultSearchField>ID</defaultSearchField>
    </schema>
    
  2. Stop your
  3. Delete your Solr workspaceSpacesStore data dir: alf_data/solr/workspace
  4. Start your tomcat
  5. login to Alfresco Share a create a new txt file containing “television” and “GB”
  6. wait a few seconds (solr indexing is done async!) & execute search for “TV” –> you doc should be in the result set, because “TV” is a synonym for television (alf_data/solr/workspace-SpacesStore/conf/synonyms.txt)
  7. execute search for “gigabyte” –> you doc should be in the result set, because “gigabyte” is a synonym for “GB”

welcome to the new shiny Alfresco-SOLR world ;-)

Ok…it’s quick hack because the synonyms will be used for every property/field, but it is a good starting point :-)

Alfresco Share datalist extensions

A customer of my company (www.fme.de) asked me to develop some extensions for Alfresco Share datalist:

ID generation:

automatic generation of an ID for each item. These IDs are generated using a separate ID sequence on each datalist.

Read-only/view dialog

Double clicking an item display a view-mode form via FormService.

 

Auto versioning

Aspect cm:versionable with auto-version on property changes is applied to each item. A custom policy triggers versioning if an association was modified  - e.g. dl:assignee.

To display version history a new FormService control was implemented. Via show version link the selected version will be display in another view-mode dialog.

Comments (fm:discussable)

Alfresco only supports a simple comment field, but you often like to have  a discussion thread as you have in doclib. Hence I added support of fm:discussable aspect to datalist items.

Ellipsis for long text

Per default the whole text of a property is displayed in the datagrid. If you’ve longer text – e.g. in cm:description – your datagrid layout will be suboptimal.

So, I added an ellipsis feature for text longer than 40 chars. The whole text will be displayed as tooltip on mouse over.

 

Upload & Attach

To allow attaching a file to a datalist item that isn’t already stored in the repo an upload&attach action was added to cm:attachment-control.

After you’ve choosen the upload destination directory (reused global-folder.js here) you’ll get the normal upload dialog.

Unable to display content. Adobe Flash is required.

XLS-Export

Nick Burch had already developed a basic XLS-export WebScript. I resused that one & added support for fm:discussable comment threads.

XLS-Export button is displayed in the datalist toolbar.

Unable to display content. Adobe Flash is required.

 

Form-based filters

my last step was to provide form based filters for each datalist. Hence I configured a filter-form for each datalist-type & extended the filter logic of datagrid.js via JS/YUI augment/extend mechanism.

These form based filters support browser history & URL addressability and are collapsable via Alfresco.util.Twister.


Unable to display content. Adobe Flash is required.

 

I made another screencast that demonstrates most of the features in common:

Unable to display content. Adobe Flash is required.

 

I guess some of them are useful for every Alfresco Share installations. Thus me, fme and our client are willing to contribute these extensions.

a worldwide project swift launch codeCamp

I had a short chat with Jeff Potts & Bernard Werner (both Alfresco) regarding the Alfresco community a few days ago.

Besides some discussion especially regarding Alfresco’s community in D A CH,  I made the following proposal that I’d like to propagate here:

Currently Alfresco is developing the next “big” Alfresco version with code name swift – I bet it will be Alfresco 4.0. What I proposed to Jeff is to organize a worldwide – but local – Alfresco CodeCamp when 4.0 is launched. I mean a worldwide series of local Alfresco CodeCamps regarding new stuff in 4.0 (Solr, Social enhancements etc.) (nearly) simultaneous.

I guess that will be a big distributed event… & yes, of course John Newton should make an online Key Note ;-)

What do you think?