Alfresco Documentation
Published on Alfresco Documentation (https://docs.alfresco.com)

Home > Alfresco Community Edition 201911 GA > Developer guide > Share extensions

Share extensions

This information looks at developing extensions for Alfresco Share. In particular, the creation of Share Extensibility Modules.

  • Getting started [1] This information covers the use case of extending the Alfresco Share User Interface with extra functionality, such as displaying custom metadata and custom workflow forms.
  • Share Extension Points [2] Introduction to the supported extension points in the Alfresco Share web application.
  • Useful Tools [3] There are a few tools that is useful to know about when developing customizations for the Share web application.
  • Tutorials [4] Tutorials for the different Extension Points that can be used to customize the Share web application. The tutorials have been organized based on what part of the user interface they relate to, such as for example Menu, Pages, Dashboard, and so on.
  • Introducing Aikau [5] There are a number of updated UI framework features that were introduced in Alfresco One 4.2 and further expanded in Alfresco Community Edition 201911 GA. The updated UI framework goes by the name of Aikau.
Parent topic: Developer guide [6]

Getting started

This information covers the use case of extending the Alfresco Share User Interface with extra functionality, such as displaying custom metadata and custom workflow forms.

The Share User Interface provides a number of extension points where custom functionality can be plugged in and run embedded with the rest of the Share code. This is functionality that is not related to the platform and that does not fit the remote integration use case.

Parent topic: Share extensions [7]

Share Extension Points

Introduction to the supported extension points in the Alfresco Share web application.

An extension point is an interface that a developer can use to customize the Share web application in a supported way. There are a number of extension points that can be used to do things like adding custom pages, hiding content on existing pages, display custom metadata, modify the menu, and so on. To fully understand the extension points it is a good idea to first read through Share architecture [8].

The Share extension points can be grouped into three different categories:

  • Declarative - XML configuration that requires no coding
  • Programmatic - Code that adds new functionality
  • Override - Code that overrides default behavior of Share

The following table lists all the extension points that are available to you when customization the Share web application:

Extension Point Name Category Support Status
Share Configuration [9] Declarative Full Support [10]
Form Controls [11] Programmatic Full Support [10]
Form Field Validation Handlers [12] Programmatic Full Support [10]
Evaluators [13] Declarative and Programmatic Full Support [10]
Site Presets [14] Declarative Full Support [10]
Share Themes [15] Declarative Full Support [10]
Document Library [16] Declarative and Programmatic Full Support [10]
Surf Extension Modules [17] Declarative and Programmatic Full Support [10]
Surf Web Scripts [18] Declarative and Programmatic Full Support [10]
Surf Web Script JavaScript Root Objects [19] Programmatic Full Support [10]
Surf Pages [20] Declarative and Programmatic Limited Support [10] (Use Aikau Pages instead)
Surf Dashlets [21] Declarative and Programmatic Limited Support [10] (Use Aikau Dashlets instead)
Surf Widgets [22] Programmatic Full Support [10]
Aikau Menus [23] Programmatic Full Support [10]
Aikau Pages [24] Declarative and Programmatic Full Support [10]
Aikau Dashlets [25] Declarative and Programmatic Full Support [10]
Aikau Widgets [26] Programmatic Full Support [10]
Modifying OOTB Surf Pages [27] Override Full Support [10] via Surf Extension Modules [17]
Modifying OOTB Surf Dashlets [28] Override Full Support [10] via Surf Extension Modules [17]
Modifying OOTB Surf Widgets [29] Override Full Support [10] via Surf Extension Modules [17]
Modifying OOTB Aikau Pages [30] Override Full Support [10] via Surf Extension Modules [17]
Modifying OOTB Aikau Dashlets [31] Override Full Support [10] via Surf Extension Modules [17]
Modifying OOTB Aikau Widgets [32] Override Full Support [10]
Modifying OOTB Surf Web Scripts [33] Override Full Support [10]

Note that the table has links to each Extension Point section.

  • Share Configuration [34] A lot of the customizations that you might want to do to the Share user interface does not require coding. They can be handled by XML configuration and a simple restart of Alfresco Community Edition.
  • Form Controls [35] When defining a form the form controls for each field controls how the field is displayed and handled.
  • Form Field Validation Handlers [36] A validation handler is a small JavaScript function that gets called by the forms runtime when a field value needs to be validated.
  • Evaluators [37] Component visibility in the Share user interface can be controlled by Evaluators.
  • Site presets [38] A site preset contains the initial configuration for a Share site, such as the site Dashboard layout.
  • Share Themes [39] The Share web application comes with a number of themes that can be used to set the look and feel of the application. It is also possible to create your own custom UI themes.
  • Document Library [40] The Document Library page has several extension points that can be used to customize its behavior, such as actions.
  • Surf Extension Modules [41] This section covers the Surf Extension Modules.
  • Surf web scripts [42] When you look under the covers of the Share web application you will notice that most of the functionality is implemented as Surf Web Scripts. This is true for both Pages and Dashlets.
  • Surf Web Script JavaScript Root Objects [43] A number of JavaScript root objects are available when you are implementing a controller for a Surf Web Script, such as page and remote. Sometimes you might have custom Java code that you want to call from JavaScript controllers, this is possible by adding custom JavaScript root objects.
  • Surf Pages [44] The Alfresco Share web application is built up of a main menu from which you can navigate to a number of pages. These pages are implemented with the Surf development framework. However, note that some pages have been converted and implemented with the Aikau development framework, see architecture section.
  • Surf Dashlets [45] The Share web application has a special page called Dashboard, which contains windows (think Portlets) of content called dashlets. Currently most of these dashlets are Spring Surf dashlets, but they will eventually be converted to Aikau dashlets.
  • Surf Widgets [46] The Share web application is built up of a main menu, pages, and dashlets. The pages and dashlets are mainly processed on the server side as web scripts. When client side processing is needed in the form of browser JavaScript and CSS then this is contained in Widgets. The Surf Widgets uses the Yahoo UI library as JavaScript framework and widget library. These widgets will eventually be replaced by Aikau Widgets.
  • Aikau Menus [47] The main menu of Share is implemented with the new Aikau UI development framework. It is possible to customize this menu, so you can navigate to new custom pages for example.
  • Aikau Pages [48] The Share web application is built up of a main menu from which you can navigate to a number of pages. These pages are implemented mostly in the Surf development framework. However, a number of pages, such as search, have been converted and implemented with the new Aikau development framework, see architecture section.
  • Aikau dashlets [49] The Share web application has a special page called Dashboard, which contains windows of content called dashlets. Currently most of these dashlets are Spring Surf dashlets, but they will eventually be converted to Aikau dashlets.
  • Aikau Widgets [50] Aikau pages are built up of widgets. There are two types of widgets, presentation widgets and service widgets. These JavaScript widgets are Dojo classes. A widget can have its own CSS, HTML, and Properties.
  • Modifying Out-of-the-box Surf Web Scripts [51] Most of the Share UI functionality can be traced back to a web script in one place or another. Sometimes it is useful to be able to override the controller or template of one of these Out-of-the-box (OOTB) web scripts.
  • Modifying Out-of-the-box Surf Pages [52] Most of the pages in the Share web application are implemented with the Surf UI framework. In many cases it is necessary to modify these pages.
  • Modifying Out-of-the-box Surf dashlets [53] The Share web application has a special page called Dashboard, which contains windows of content called dashlets. Currently most of these dashlets are Spring Surf dashlets, and it is possible to modify the contents on them.
  • Modifying Out-of-the-box Surf Widgets [54] The Share web application pages and dashlets are built up of widgets. Sometimes it is necessary to modify these.
  • Modifying Out-of-the-box Aikau Pages [55] The Share web application has a number of Aikau pages. These can be modified.
  • Modifying Out-of-the-box Aikau dashlets [56] The Share web application has a special page called Dashboard, which contains windows of content called dashlets. Currently most of these dashlets are Spring Surf dashlets, but they will eventually be converted to Aikau dashlets.
  • Modifying Out-of-the-box Aikau Widgets [57] Every Aikau menu, page, and dashlet is built up of one or more widgets. Sometimes it is necessary to modify out of the box widgets.
Parent topic: Share extensions [7]

Share Configuration

A lot of the customizations that you might want to do to the Share user interface does not require coding. They can be handled by XML configuration and a simple restart of Alfresco Community Edition.
Extension Point Share Configuration
Support Status Full Support [10]
Architecture Information Share Architecture [8].
Description

There is a separate section in the documentation dedicated to Configuring Alfresco Share [58]. This configurations typically goes into the share-config-custom.xml file. The following is an example of stuff that you can configure in this file:

  • Visibility of Aspects and Types (from custom content models)
  • Metadata forms (from custom content model)
  • Workflow task forms
  • Document Library indicators, views, actions, and metadata templates
  • Visibility of workflow process definitions (that is, what workflows can be started)
  • Advanced Search
  • Repository location
  • Sorting fields and labels
  • Web Framework settings
  • Data Lists
  • Cross-site request forgery (CSRF) policy

Note that a lot of the other extension points that require coding also involve configuration, so it is a good idea to read up on the configuration bit before starting any development with the other extension points.

Deployment - App Server tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/META-INF/share-config-custom.xml - Share configuration related to a specific Share module extension, such as Document Library Action configuration, form configuration, aspect and type configuration.
More Information
  • Share configuration introduction [58]
  • Document Library configuration [60]
Tutorials
  • Making custom types visible [61]
  • Making custom aspects visible [62]
  • Controlling search results [63]
  • Controlling user name and password length [64]
  • Setting repository location [65] (If the Share webapp and the repository webapp runs in different application servers)
  • Remove persistent cookies [66]
  • Document Library actions, metadata templates, and indicators [67]
  • Document Library views [68]
  • Configuring Alfresco Share [69]Alfresco Share provides a rich web-based collaboration environment for managing documents, wiki content, blogs and more. Share leverages the repository to provide content services and uses the Surf platform to provide the underlying presentation framework.
Parent topic: Share Extension Points [2]

Configuring Alfresco Share

Alfresco Share provides a rich web-based collaboration environment for managing documents, wiki content, blogs and more. Share leverages the repository to provide content services and uses the Surf platform to provide the underlying presentation framework.

A number of options are available to developers and administrators for configuring Share to better fit into their environment. Many of these mechanisms are provided by the underlying Surf framework, therefore a knowledge of Surf is considered useful for anyone wishing to implement substantial customizations.

You can customize Share through configuration files. Extending Share through more advanced techniques in discussed in the Share extensions [70] section.

The main aspects of Share that can be configured are:

  • General properties (for example port number, user name and password)
  • UI Controls
  • Forms (custom types, built-in types, search, workflow)
  • Menu bar
  • Themes
  • Custom types and aspects
Note: Usually, once you have changed a configuration file, you will need to restart Alfresco Community Edition for the changes to take effect.
  • Share configuration files [71]Share can be configured through a number of configuration files.
  • Configuring Share user name and password minimum length [64] This information describes how to change the minimum length of the Share user name and password.
  • Configuring Share for mixed user name types [72] When there is a mix of user name types, for example, some using the @domain in their user name, this may have an impact on the use of Share.
  • Configuring the Share default port [65] Use this information to configure the default port configuration for Share.
  • Configuring Share to remove persistent cookies [66] Use this information to turn off cookies that store a user name after a session expires.
  • Configuring Share Actions with Smart Folders [73] Share actions are disabled, by default, when using Smart Folders in Alfresco Community Edition.
  • Enabling External Users panel [74] The External Users panel is disabled by default in Alfresco Share. Use this information to enable this panel to add external users.
  • Share Document Library [75] The Share document library is a feature that gives full access to the Alfresco Community Edition repository.
  • Share themes [76] When you run Share, the look and feel is set by a default theme. Use this information to select one of the alternative themes available in Share, and also how to create and use your own themes for corporate branding.
  • Share Forms [77]Alfresco Share presents data view and entry forms throughout its user interface, which are built on the Surf framework. This framework provides a convention for implementing forms.
  • Adding Custom MIME types [78] You can add custom MIME types to Share.
Parent topic: Share Configuration [34]
Related concepts
Developing Share Extensions [70]

Share configuration files

Share can be configured through a number of configuration files.

The main Share configuration file is share-config.xml. This can be found at tomcat/webapps/share/WEB-INF/classes/alfresco/share-config.xml. While it is possible to change configuration through direct changes to this file this is not recommended as any customizations will be lost if the Share WAR is re-exploded, or you install a new version of Alfresco Community Edition.

To get around this issue it is advisable to make configuration changes to a file outside of the Share WAR. This can be done through the file share-config-custom.xml, which can be found at tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml in the default Alfresco installation. Any changes made here will be applied once the changes have been saved, and Alfresco Community Edition restarted. Further, your configuration changes can be saved between reinstalls and if the Share WAR re-explodes at any point your configuration file will be unaffected.

Note: If you are overriding a configuration section, you must apply the replace="true" attribute to replace the existing Alfresco Community Edition configuration.

It should also be noted that it is possible to package a share-config-custom.xml file in a JAR or AMP. In this way you can have multiple share-config-custom.xml files packaged in JARs or AMPs if necessary. JARs will be loaded from the classpath, for example ./tomcat/shared/lib. AMPs will be applied to the Share WAR file.

CAUTION:
The order in which multiple share-config-custom.xml files are applied is not guaranteed in the case where multiple files override the same section of configuration.

Another key Share configuration file is slingshot-application-context.xml which can be found at tomcat/webapps/share/WEB-INF/classes/alfresco/slingshot-application-context.xml in the default Alfresco installation. This loads a number of other configuration files:

  
    <!-- Spring Web Scripts -->
    <value>classpath:org/springframework/extensions/webscripts/spring-webscripts-config.xml</value>
    <value>classpath:META-INF/spring-webscripts-config-custom.xml</value>
    <value>jar:*!/META-INF/spring-webscripts-config-custom.xml</value>
    
    <!-- Alfresco Surf -->
    <value>classpath:org/springframework/extensions/surf/spring-surf-config.xml</value>
    <value>classpath:org/springframework/extensions/surf/spring-surf-config-remote.xml</value>
    <value>classpath:META-INF/spring-surf-config-custom.xml</value>
    <value>jar:*!/META-INF/spring-surf-config-custom.xml</value>
    
    <!-- Surf Autowire Support -->
    <value>webapp:WEB-INF/surf.xml</value>

    <!-- Common form config -->
    <value>classpath:alfresco/form-config.xml</value>
    
    <!-- Share default config -->
    <value>classpath:alfresco/share-config.xml</value>
    
    <!-- Share help url config -->
    <value>classpath:alfresco/share-help-config.xml</value>
    
    <!-- Share form config -->
    <value>classpath:alfresco/share-form-config.xml</value>
    
    <!-- Share Document Library config -->
    <value>classpath:alfresco/share-documentlibrary-config.xml</value>

    <!-- Share Data List form config -->
    <value>classpath:alfresco/share-datalist-form-config.xml</value>

    <!-- Share workflow form config -->
    <value>classpath:alfresco/share-workflow-form-config.xml</value>
    
    <!-- Share CMIS config -->
    <value>classpath:alfresco/share-cmis-config.xml</value>

    <!-- Share Security config -->
    <value>classpath:alfresco/share-security-config.xml</value>

    <!-- Share custom config -->
    <value>classpath:alfresco/web-extension/share-config-custom.xml</value>
    <value>jar:*!/META-INF/share-config-custom.xml</value>
    <value>classpath:alfresco/web-extension/share-config-custom-dev.xml</value>
    <value>jar:*!/META-INF/share-config-custom-dev.xml</value>
  
  

Note that the custom configuration files are loaded last, so that they can override existing configuration.

CAUTION:
Note that the configuration files with the same base file name must have different effective paths in order to be loaded. For example, if you tried to load classes/alfresco/web-extension/share-config-custom.xml and WEB-INF/classes/alfresco/web-extension/share-config-custom.xml, only one of them would be loaded, as these both have the effective path alfresco/web-extension/share-config-custom.xml. Note that where files are loaded from multiple JAR files, such as through <value>jar:*!/META-INF/share-config-custom.xml</value>, they have different effective paths, and so multiple configuration files with the same base file name can be successfully loaded in this case.

Configuration files

The following table summarizes the main Share configuration files:

Configuration file Description Location
share-config.xml Default Share configuration file. classpath:alfresco
slingshot-application-context.xml Spring beans file which also loads various configuration files. tomcat/webapps/share/WEB-INF/classes/alfresco
share-form-config.xml Default configuration for the cm:content and cm:folder forms. classpath:alfresco
share-datalist-form-config.xml Default configuration for datalists. classpath:alfresco
share-documentlibrary-config.xml Default configuration for the document library, my files, shared files and repository pages. classpath:alfresco
share-workflow-config.xml Default configuration file for the Alfresco Process Services Workflow forms. classpath:alfresco
Parent topic: Configuring Alfresco Share [69]

Configuring Share user name and password minimum length

This information describes how to change the minimum length of the Share user name and password.
  1. Open the <web-extension>/share-config-custom.xml file.
  2. Search for the text "username". You see the following configuration:

          
       <config evaluator="string-compare" condition="Users">
          <users>
             <!-- minimum length for username and password -->
             <username-min-length>2</username-min-length>
             <password-min-length>3</password-min-length>
          </users>
       </config>            
                     
  3. Change the value of <username-min-length> and <password-min-length>.
Parent topic: Configuring Alfresco Share [69]

Configuring Share for mixed user name types

When there is a mix of user name types, for example, some using the @domain in their user name, this may have an impact on the use of Share.

For example, there may be users with both the @domain and without:

  • user2@domain.com
  • user1

This configuration enables Share to function correctly when using mixed users types.

  1. Open the <web-extension>/share-config-custom.xml file.
  2. Add the following bean:

    <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="tenantObjectCache"><value>false</value></property>
    </bean>           
  3. Save the file, and restart the Alfresco server.
Parent topic: Configuring Alfresco Share [69]

Configuring the Share default port

Use this information to configure the default port configuration for Share.
  1. Open the <web-extension>/share-config-custom.xml file.
  2. Uncomment the following section by removing the begin comment <-- and end comment --> lines surrounding this section.

    <config evaluator="string-compare" condition="Remote">
       <remote>
          <endpoint>
             <id>alfresco-noauth</id>
             <name>Alfresco - unauthenticated access</name>
             <description>Access to Alfresco Repository WebScripts that do not require authentication</description>
             <connector-id>alfresco</connector-id>
             <endpoint-url>http://localhost:8080/alfresco/s</endpoint-url>
             <identity>none</identity>
          </endpoint>
    
          <endpoint>
             <id>alfresco</id>
             <name>Alfresco - user access</name>
             <description>Access to Alfresco Repository WebScripts that require user authentication</description>
             <connector-id>alfresco</connector-id>
             <endpoint-url>http://localhost:8080/alfresco/s</endpoint-url>
             <identity>user</identity>
          </endpoint>
    
          <endpoint>
             <id>alfresco-feed</id>
             <name>Alfresco Feed</name>
             <description>Alfresco Feed - supports basic HTTP authentication via the EndPointProxyServlet</description>
             <connector-id>http</connector-id>
             <endpoint-url>http://localhost:8080/alfresco/s</endpoint-url>
             <basic-auth>true</basic-auth>
             <identity>user</identity>
          </endpoint>  
                
          <endpoint>
             <id>alfresco-api</id>
             <parent-id>alfresco</parent-id>
             <name>Alfresco Public API - user access</name>
             <description>Access to Alfresco Repository Public API that require user authentication.
               This makes use of the authentication that is provided by parent 'alfresco' endpoint.</description>
             <connector-id>alfresco</connector-id>
             <endpoint-url>http://localhost:8080/alfresco/api</endpoint-url>
             <identity>user</identity>
          </endpoint>
       </remote>
    </config>
    
  3. Uncomment the following section if you are using Kerberos, or external SSO, or an HTTP load balancer.

     <config evaluator="string-compare" condition="Remote"> 
          <remote> 
            <ssl-config>
                 <keystore-path>alfresco/web-extension/alfresco-system.p12</keystore-path>
                 <keystore-type>pkcs12</keystore-type>
                 <keystore-password> alfresco-system</keystore-password>
    
                 <truststore-path> alfresco/web-extension/ssl-truststore</truststore-path>
                 <truststore-type>JCEKS</truststore-type>
                 <truststore-password>password</truststore-password>
    
                 <verify-hostname>true</verify-hostname>
             </ssl-config>
              
             <connector> 
                <id>alfrescoCookie</id> 
                <name>Alfresco Connector</name> 
                <description>Connects to an Alfresco instance using cookie-based authentication</description> 
                <class>org.alfresco.web.site.servlet.SlingshotAlfrescoConnector</class> 
             </connector> 
              
             <endpoint> 
                <id>alfresco</id> 
                <name>Alfresco - user access</name> 
                <description>Access to Alfresco Repository WebScripts that require user authentication</description> 
                <connector-id>alfrescoCookie</connector-id> 
                <endpoint-url>http://localhost:8080/alfresco/wcs</endpoint-url> 
                <identity>user</identity> 
                <external-auth>true</external-auth> 
             </endpoint> 
                
             <endpoint>
                <id>alfresco-api</id>
                <parent-id>alfresco</parent-id>
                <name>Alfresco Public API - user access</name>
                <description>Access to Alfresco Repository Public API that require user authentication.
                  This makes use of the authentication that is provided by parent 'alfresco' endpoint.</description>
                <connector-id>alfresco</connector-id>
                <endpoint-url>http://localhost:8080/alfresco/api</endpoint-url>
                <identity>user</identity>
                <external-auth>true</external-auth>
             </endpoint> 
          </remote> 
       </config> 
    
              
  4. Replace all instances of 8080 with the required port number.
  5. Save the file.
Parent topic: Configuring Alfresco Share [69]

Configuring Share to remove persistent cookies

Use this information to turn off cookies that store a user name after a session expires.

By default, a cookie is stored that allows you to repopulate the user name after a user logs out, or the session expires. Follow these instructions if you do not want this user name stored.

  1. Open the following file:

    tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml

  2. Set the enableCookie property to false.

    <config evaluator="string-compare" condition="Cookie" replace="true">
      <cookie>
           <enableCookie>false</enableCookie>
      </cookie>
    </config>
    
    and disable the login cookie:
    <config evaluator="string-compare" condition="WebFramework">
      <web-framework>
          <defaults>
             <login-cookies-enabled>false</login-cookies-enabled>
          </defaults>
      </web-framework>
    </config>
  3. Save the edited file.
  4. Restart Alfresco.
Parent topic: Configuring Alfresco Share [69]

Configuring Share Actions with Smart Folders

Share actions are disabled, by default, when using Smart Folders in Alfresco Community Edition.
If you need to enable Share actions, these must be explicitly set in the following files:
  • <configRootShare>/classes/alfresco/share-documentlibrary-config.xml: these are Share standard defaults, do not modify them
  • <classpathRoot>/alfresco/web-extension/share-config-custom.xml: for standard Share actions
  • <classpathRoot>/alfresco/web-extension/smartfolders-amp-actions-config.xml: for custom module actions and Google Docs
For example:
<action index="100" id="document-download" appendEvaluators="true">
<evaluator>evaluator.doclib.action.DocumentEnableInSmartFolder</evaluator> 
</action> 
In each file, you can find the new evaluators to enable actions in the actionGroups section:
  • DocumentEnableInSmartFolder: enable action for documents in a Smart Folder
  • FolderEnableInSmartFolder: enable action for folders in a Smart Folder
  • SmartFolderEnable: enable action for Smart Folders
  • FolderAndSmartFolderEnable: enable action for folders and Smart Folders
Action limitations on Smart Folders include:
  • Alfresco permissions apply when viewing objects in a Smart Folder (permissions on the object are required)
  • Alfresco permissions apply when viewing a Smart Folder (permissions on the physical parent folder are required)
  • Suppressed actions in Share: Comment, Like, Favorite
  • Unsupported actions: Delete, Edit Properties, Unzip To, Sync, Locate To, Move, and Copy
  • Rules can't be used on a Smart Folder
  • Permissions can't be set on a Smart Folder
Parent topic: Configuring Alfresco Share [69]

Enabling External Users panel

The External Users panel is disabled by default in Alfresco Share. Use this information to enable this panel to add external users.
  1. Open the <web-extension>/share-config-custom.xml file.
  2. Add the following section to the share-config-custom.xml file.

    <config evaluator="string-compare" condition="Users" replace="true">
       <enable-external-users-panel>true</enable-external-users-panel>
    </config>
This implementation enables the External Users panel in the Share user interface.
Note: External users are a way for users without Administrator permissions to add a user to Alfresco Community Edition. When they accept the invite they will have the same access as a standard user, and will be counted against licensing.
Parent topic: Configuring Alfresco Share [69]

Share Document Library

The Share document library is a feature that gives full access to the Alfresco Community Edition repository.

The default content structure for Share is based on sites, and this does not give full visibility of the content in the repository. By enabling the document library configuration setting, you have access to multiple navigation options, for example, folders and categories, tags, and filters. This feature also allows you to recreate and edit text files, for example, within the Data Dictionary.

It is possible to copy or move files to the document library without any repository permissions.

The document library is accessed in Share through the Repository, My Files, and Shared Files links in the header, and through the Document Library link in a site. These all kind different views of the complete content repository.

  • Configuring the Repository link [79] It is possible to control the visibility of the Repository link in Share through configuration. Note the Repository link is always visible to Administrators.
  • Configuring aspects [80] Aspects can be configured in the file ./tomcat/webapps/share/WEB-INF/classes/alfresco/share-documentlibrary-config.xml.
  • Extending the Alfresco Share Document Library [81]Alfresco Community Edition offers a number of extension points for the document library. 
Parent topic: Configuring Alfresco Share [69]

Configuring the Repository link

It is possible to control the visibility of the Repository link in Share through configuration. Note the Repository link is always visible to Administrators.
  1. Load the file tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml into your favorite editor (assuming you are using the Tomcat application server).
  2. Locate the Repository Library config section:

    
       <!-- Repository Library config section -->
       <config evaluator="string-compare" condition="RepositoryLibrary" replace="true">
          <!--
             Root nodeRef or xpath expression for top-level folder.
             e.g. alfresco://user/home, /app:company_home/st:sites/cm:site1
             If using an xpath expression, ensure it is properly ISO9075 encoded here.
          -->
          <root-node>alfresco://company/home</root-node>
    
          <tree>
             <!--
                Whether the folder Tree component should enumerate child folders or not.
                This is a relatively expensive operation, so should be set to "false" for Repositories with broad folder structures.
             -->
             <evaluate-child-folders>false</evaluate-child-folders>
             
             <!--
                Optionally limit the number of folders shown in treeview throughout Share.
             -->
             <maximum-folder-count>500</maximum-folder-count>
          </tree>
    
          <!--
             Whether the link to the Repository Library appears in the header component or not.
          -->
          <visible>true</visible>
       </config>
    
  3. The configuration that can make the Repository link visible or invisible for non-administrators is the following:

                  
      <visible>false</visible>                
                  
                

    Set to true to have the Repository link available to all users.

  4. Restart the Alfresco Community Edition server.
Parent topic: Share Document Library [75]

Configuring aspects

Aspects can be configured in the file ./tomcat/webapps/share/WEB-INF/classes/alfresco/share-documentlibrary-config.xml.
Aspects can be added to any document in the repository. Examples of aspects include taggable, exif, and versionable. There are many others. Each aspect can be configured as visible, hidden, addable, and removable.
  1. Load the file ./tomcat/webapps/share/WEB-INF/classes/alfresco/share-documentlibrary-config.xml into your favorite editor.
  2. Search for the text "aspects". You will find the following configuration:

                
          <!--
             Used by the "Manage Aspects" action and Rules pages
    
             For custom aspects, remember to also add the relevant i18n string(s)
                aspect.cm_myaspect=My Aspect
          -->
          <aspects>
    
             <!--
                Aspects that a user can see in UI.
                Used by Rules, aspects are the listed aspects for rule's "has-aspect" action condition.
             -->
             <visible>
                <aspect name="cm:generalclassifiable" />
                <aspect name="cm:complianceable" />
                <aspect name="cm:dublincore" />
                <aspect name="cm:effectivity" />
                <aspect name="cm:summarizable" />
                <aspect name="cm:versionable" />
                <aspect name="cm:templatable" />
                <aspect name="cm:emailed" />
                <aspect name="emailserver:aliasable" />
                <aspect name="cm:taggable" />
                <aspect name="app:inlineeditable" />
                <aspect name="cm:geographic" />
                <aspect name="exif:exif" />
                <aspect name="audio:audio" />
                <aspect name="cm:indexControl" />
                <aspect name="dp:restrictable" />
             </visible>
    
             <!--
                Aspects that a user can add in UI.
                Used by Rules, aspects are the listed aspects for rule's "add-features" action.
                Same as "visible" if left empty.
             -->
             <addable>
             </addable>
    
             <!--
                Aspects that a user can remove in UI.
                Used by Rules, aspects are the listed aspects for rule's "remove-features" action.
                Same as "visible" if left empty
             -->
             <removeable>
             </removeable>
          </aspects>
                
                
              
  3. Modify the configuration of aspects as required.
You have seen how to configure aspects via the document library configuration file. You can also add similar configurations to share-config-custom.xml.
Parent topic: Share Document Library [75]

Extending the Alfresco Share Document Library

Alfresco Community Edition offers a number of extension points for the document library. 

This includes:

  • Repository tier
  • Web tier
  • Status indicators
  • Metadata templates
  • Actions
  • Client-side extension points

This documentation also includes a jsNode client-side help object reference and a list of out-of-the-box evaluators.

  • Alfresco Share Document Library repository tier [82] In order to preserve existing customizations and third party add-ons, a parallel set of data web scripts has been developed to coexist with the previous data web scripts. These web scripts are located in the remote-api project and have URLs starting with /slingshot/doclib2/.
  • Alfresco Share Document Library web tier [83] In versions of Alfresco Share previous to 4.0, the client-side JavaScript requested JSON data from the repository directly by using the proxy servlet. From 4.0 onwards, there is a new data web script (at /components/documentlibrary/data/) that requests data from the repository and processes the response based on a configurable set of evaluators before finally returning JSON data to the browser.
  • Override and extension examples [84] You configure new evaluators by using a web-extension/custom-slingshot-*-context.xml file, taking the form of bean definitions.  
  • Client-side template and action extensions [85] Two global events are available to make it easier to add new metadata template renderers and client-side action handlers.
  • Customizing document library views [86] Within the document library it is possible to select from a number of views. It is also possible to add custom views to the document library through configuration in the share-documentlibrary-config.xml file.
  • Reference [87] This information provides reference material about the Alfresco Share Document Library; for example, jsNode methods and properties, evaluators, and EXIF renderer source code.
Parent topic: Share Document Library [75]

Alfresco Share Document Library repository tier

In order to preserve existing customizations and third party add-ons, a parallel set of data web scripts has been developed to coexist with the previous data web scripts. These web scripts are located in the remote-api project and have URLs starting with /slingshot/doclib2/.

There are three extension points supported by the repository data web scripts.

1. Document Library custom response

A custom response appears within the metadata.custom section of the JSON response. An example of a cleanly installed service is the vtiServer configuration, defined within the slingshot-context.xml file.

The customResponses property defines a map of JSON key to custom response bean within the SlingshotDocLibCustomerResponse bean definition.

Default slingshotDocLibCustomResponse bean configuration:


<bean id="slingshotDocLibCustomResponse"
       parent="baseJavaScriptExtension"
       class="org.alfresco.repo.jscript.SlingshotDocLibCustomResponse">
  <property name="extensionName">
     <value>slingshotDocLib</value>
  </property>
  <property name="<b>customResponses</b>">
    <map>
       <entry key="vtiServer">
          <ref bean="doclibCustomVtiServer"/>
       </entry>
    </map>
  </property>
</bean>
  

The bean for returning the vtiServer configuration is defined as:


<bean id="doclibCustomVtiServer" class="org.alfresco.repo.jscript.app.VtiServerCustomResponse">
    <property name="scriptUtils">
        <ref bean="utilsScript" />
    </property>
    <property name="sysAdminParams">
        <ref bean="sysAdminParams" />
    </property>
    <property name="port">
        <value>${vti.server.external.port}</value>
    </property>
    <property name="host">
        <value>${vti.server.external.host}</value>
    </property>
</bean>

The VtiServerCustomResponse class (which implements CustomResponse) returns a Serializable object (for example a LinkedHashMap) that is serialized into the JSON response by the DocLib web scripts.

This extension point is designed to return useful information that is not specific to any node, for example, the presence of an optional module; whether a subsystem is active or not, etc.

2. Property decorators

The other place the data web scripts may be extended is via the property decorator extension mechanism.

To add a new property decorator, the baseDecorator bean needs to be extended with the property decorator you wish to use. For example:

        
      <bean id="customPropertyDecorator" parent="baseDecorator" class="org.alfresco.repo.jscript.app.UsernamePropertyDecorator">
        <property name="nodeService" ref="nodeService"/>
        <property name="personService" ref="PersonService" />
        <property name="propertyNames">
          <set>
            <value>ds:uploader</value>
          </set>
        </property>
      </bean>
      
      

3. Permissions list

The third place the data web script response can be extended is by using the list of permissions that are returned for each node. These are defined by using the userPermissions property on the applicationScriptUtils bean. For example:

  
        
<property name="<b>userPermissions</b>">
  <list>
      <value>CancelCheckOut</value>
      <value>ChangePermissions</value>
      <value>CreateChildren</value>
      <value>Delete</value> 
      <value>Write</value>
  </list>
</property>

The default set of permissions should not be reduced without fully understanding the impact on actions, indicators, and metadata evaluators already in use throughout Share.

Parent topic: Extending the Alfresco Share Document Library [81]

Alfresco Share Document Library web tier

In versions of Alfresco Share previous to 4.0, the client-side JavaScript requested JSON data from the repository directly by using the proxy servlet. From 4.0 onwards, there is a new data web script (at /components/documentlibrary/data/) that requests data from the repository and processes the response based on a configurable set of evaluators before finally returning JSON data to the browser.

All configuration for, and evaluation of, Document Library status indicators, metadata templates, and actions is on the web tier instead of split between the repository and the browser.

1. Web tier configuration overview

The individual action configuration files (for example documentlist.get.config.xml, document-details.get.config.xml) have been removed and all actions are now defined within common configuration sections.

The new or altered areas of configuration in share-documentlibrary-config.xml are:

DocumentLibrary
  • New <indicators> section for configuring status indicators
  • New <metadata templates> for configuring the metadata displayed within the Document Library's "browse" view
DocLibCustom
  • <dependencies> section for defining custom client-side functionality and stylesheets
DocLibActions
  • <actions> section defining all available actions across the various Document Library view pages
  • <actionGroups> that define which (and in what order) actions are to appear on the Document Library pages

The slingshot-documentlibrary-context.xml file contains all bean definitions for web tier evaluators.

2. Status indicators

  • Defined within the DocumentLibrary config section, status indicators are small icons typically used to indicate the presence of a marker aspect, or whether a document is in a particular state, for example checked out for editing.
  • Indicator images by default are referenced by id: /res/components/documentlibrary/indicators/{id}-16.png unless the name is overridden by the "icon" attribute.
  • Tooltip labels are also defaulted by id: status.{id} and can be overridden by the label attribute.

The status indicators are located in the <indicators> config container element with the following structure:

        
<indicator id (index) (icon) (label)>
    <evaluator />
    <labelParam index />
    <override />
</indicator>
      
      

where:

<indicator> Status indicator element with the following attributes:
  • id: Unique indicator id
  • index: Determines display order of this indicator
  • icon: Icon filename; if not specified, id is used
  • label: Tooltip i18 label; if not specified, id is used
<evaluator> Bean id of evaluator that determines the visibility of the image. The Evaluator extends org.alfresco.web.evaluator.BaseEvaluator
<labelParam> Allows placeholder values within i18n label to be replace at runtime with node properties. The value is the replacement string or dot notation path to a node property. The attribute is:
  • index: Index of placeholder value with i18n message
<override> Allows this indicator to override (hide) other indicators that would otherwise be visible. The value is the id of another indicator to override.

Example config


<indicator id="google-docs-locked" index="10">
    <evaluator>evaluator.doclib.indicator.googleDocsLocked</evaluator>
    <labelParam index="0">{jsNode.properties.owner.displayName}</labelParam>
    <labelParam index="1">{jsNode.properties.owner.userName}</labelParam>
    <override>locked</override>
</indicator>
      
      

A note about the labelParam value: refactoring on the client-side (JavaScript code) means that a common helper object is available for each node within the Document Library during the rendering cycle, namely jsNode. A full reference for this new resource is in jsNode reference. [88]

3. Metadata templates

The metadata template refers to the section of the document "browse" page under the file name. This area can be customized with node properties and/or by custom rendering functions.

In a clean install, there are two templates defined: the default (fallback) template and one used when rendering working copies. These are both defined within share-documentlibrary-config.xml and can be extended or overridden as required (by using share-config-custom.xml).

The metadata templates are located in the <metadata-templates> config container element with the following structure:


<template id>
    <evaluator />
    <line id (index) (simpleView) (evaluator) />
    <override />
</template>
      
      

where:

<template> Template element with the following attribute:
  • id: Unique template id
<evaluator> Bean id of evaluator that determines whether the template is to be used for this node or not. The Evaluator extends org.alfresco.web.evaluator.BaseEvaluator
<line> Allows placeholder values within i18n label to be replace at runtime with node properties. The value refers either to a node property (such as cm_description) or a customer JavaScript renderer. To add a label in front of the property, add the label's i18n messageId after the property value, separated by a space (such as {cm_description details.description}. The attributes are:
  • id: Id of the line within the template. Must be unique within this template.
  • index: Optional index for ordering the lines when rendering.
  • view: If set to "simple" or "detailed", then this line will only be rendered when either the simple or detailed view is toggled on, respectively. Leave empty, or omit the attribute for both views.
  • evaluator: Optional evaluator to determine whether this line will be rendered for a node when using the template.

Example config


<template id="isPhoto">
   <evaluator>evaluator.doclib.metadata.hasExif</evaluator>
   <line index="10" id="date" view="detailed">{date}{size}</line>
   <line index="20" id="exposure" evaluator="evaluator.doclib.metadata.hasExposure">
      {exposure exif.label.exposure}
   </line>
   <line index="30" id="description" view="detailed">{description}</line>
   <line index="40" id="social" view="detailed">{social}</line>
</template>

Custom JavaScript renderers

A renderer can either be a simple property value, or use a custom JavaScript renderer. To register a custom renderer, fire a Bubbling (global) event, passing-in the renderer id and the rendering function:

        
if (Alfresco.DocumentList)
{
    YAHOO.Bubbling.fire("registerRenderer",
    {
        propertyName: "renderer id",
        renderer: function(record, label)
        {
           return "...";
        }
    });
}

The rendering function should return property escaped HTML.

4. Actions

Actions are defined globally in the share-documentlibrary-config.xml file, in the DocLibActions config section. This means they can be overridden and extended by using a share-config-custom.xml file. These customizations can be by using the AMP, JAR or web-extension folder mechanism, or a mixture of all three.

Actions are also now grouped by view type instead of node “state”.

The actions are located in the <actions> config container element with the following structure:

        
<action id type (icon) label>
    <param name />
    <evaluator negate />
    <permissions>
      <permissions allow />
    </permissions>
    <override />    
</action>        

where:

<action> Action config container element with the following attributes:
  • id: Unique action id
  • type: Action type. Javascript, link, and pagelink are supported
  • icon: Optionally, override the icon name. If not set, the ID is used
  • label: The action's i18n message ID
<param> Action parameter elements with the following attribute:
  • name: Parameter name
<evaluator> Bean ID of evaluator that determines whether the action is valid for this node or not. Evaluator extends org.alfresco.web.evaluator.BaseEvaluator and contains the following attribute:
  • negate: If set to "true", the output of the evaluator is inverted
<permissions> Permission config container element
<permission> List of permissions required for the actions, as defined in the applicationScriptUtils bean config with the following attributes:
  • allow: If the permission specifies, the action is allowed
  • deny: If the permission specifies, the action is hidden

Only one of the "allow" or "deny" permissions can be specified

<override> If this action should override the visibility of other actions, they are specified using this element.

Example config


<!-- Inline edit -->
<action id="document-inline-edit" type="pagelink" label="actions.document.inline-edit">   
   <param name="page">inline-edit?nodeRef={node.nodeRef}</param>
   <permissions>
     <permission allow="true">Write</permission>   
   </permissions>  
   <evaluator>evaluator.doclib.action.inlineEdit</evaluator>
</action>
   
   

<!-- Checkin from Google Docs -->
<action id="document-checkin-from-googledocs" type="javascript" label="actions.document.checkin-google">
     <param name="function">onActionCheckinFromGoogleDocs</param>  
     <evaluator>evaluator.doclib.action.googleDocsCheckIn</evaluator>  
     <override>document-checkout-to-googledocs</override>
</action>

    

5. Action groups

Actions are grouped using the actionGroup elements. The type of node and also the view currently in use determines the actual group used. The group is calculated by the calculateActionGroupId() function in surf-doclist.lib.js and is designed to be overridden if many new and/or altered actions are required.

The action groups defined in a default installation are:

Action Group ID Default Usage
document-browse Documents on the browse page
document-details Document on the document details page
folder-browse Folders on the browse page
folder-details Folders on the folder details page
document-link-browse Links to documents on the browse page
document-link-details Links to folders on the document details page
folder-link-browse Links to folders on the browse page
folder-link-details Links to folders on the folder details page

The action groups are located in the <actionGroups> config container element with the following structure:


<actionGroup id>
    <action />  
</actionGroup>

      

where:

<actionGroup> Action group config container element with the following attribute:
  • id: Unique action group ID
<action> Action element with the following mandatory attribute:
  • id: Reference to action as defined in <actions> config section

Other actions properties can be overridden here, although it is recommended from a maintenance view to only override "simple" properties like the icon and label. These make it possible to reuse an action with document-biased icon and label to be used for folders.

Example config

        
<actionGroup id="folder-browse">
    <action index="100" id="folder-view-details" />  
    <action index="110" id="folder-edit-properties" icon="folder-edit-properties" />
    <label="actions.folder.edit-metadata" />
</actionGroup>        
      
      

6. Custom client extensions

The DocLibCustom config section is where dependencies on custom client-side assets can be defined. These are defined in exactly the same way as for custom Forms dependencies.

The client-side dependencies are located in the <dependencies> config container element with the following structure:


<css src />  
<js src />        

      

where:

<css> Stylesheet dependencies element with the following attribute:
  • src: Path to the css file, relative to the /res servlet
<js> JavaScript dependencies element with the following attribute:
  • src: Path to the js file, relative to the /res servlet

Other actions properties can be overridden here, although it is recommended from a maintenance point of view to only override "simple" properties like the icon and label. These make it possible to reuse an action with document-biased icon and label to be used for folders.

Example config


<dependencies>
    <cs src="/custom/my-customization.css" />  
    <js src="/custom/my-customization.js" /> 
</dependencies>

Parent topic: Extending the Alfresco Share Document Library [81]

Override and extension examples

You configure new evaluators by using a web-extension/custom-slingshot-*-context.xml file, taking the form of bean definitions.  

You can use any of the out-of-the-box evaluators as parents to template from. For example:

     
<bean id="evaluator.doclib.metadata.hasExposure"
      parent="evaluator.doclib.action.propertyNotNull">  
   <property name="property" value="exif:exposureTime"/>
</bean>

Client-side dependencies are specified in the share-config-custom.xml file using the DocLibCustom config section.


<config evaluator="string-compare" condition="DocLibCustom">   
   <dependencies>   
         <js src="/custom/exif.js" />   
   </dependencies> 
</config>
 
 

Extra status indicators are configured in the following way by using the share-config-custom.xml file.

      
<config evaluator="string-compare" condition="DocumentLibrary">
    <indicator id="my-custom"index="10">
        <evaluator>evaluator.doclib.indicator.myCustomEvaluator</evaluator>
        <labelParam index="0">{jsNode.properties.owner.displayName}</labelParam>
    </indicator>
</config>

Custom metadata templates are also specified in the share-config-custom.xml file, in the DocumentLibrary config section.

      
<config evaluator="string-compare" condition="DocumentLibrary">
<metadata-templates>
    <!-- Photos -->
    <template id="isPhoto">
       <evaluator>evaluator.doclib.metadata.hasExif</evaluator>
       <line index="10" id="date" view="detailed">{date}{size}</line>
       <line index="20" id="exposure" evaluator="evaluator.doclib.metadata.hasExposure">
         {exposure exif.label.exposure}
       </line>
       <line index="30" id="description" view="detailed">{description}</line>
       <line index="40" id="social" view="detailed">{social}</line>
   </template>
</metadata-templates></config>

New actions can be specified within the share-config-custom.xml file as follows.


<config evaluator="string-compare" condition="DocLibActions">
   <actions>
      <action id="document-preview-webasset" type="javascript"
              label="actions.wcmqs.preview-webasset">
           <param name="function">onActionPreviewWebAsset</param>
           <evaluator>wcmqs.evaluator.doclib.action.isPreviewable</evaluator>
      </action>
   </actions>
</config>

An action can be disabled across the whole application using the following configuration in a share-config-custom.xml file. For example the following config removes the "Upload New Version" action from users.

     
<config evaluator="string-compare" condition="DocLibActions">
   <actions>
      <action id="document-upload-new-version">
          <evaluator>evaluator.doclib.action.disableAction</evaluator>
      </action>
   </actions>
</config>

Add an evaluator, used on an out-of-the-box action:


<config evaluator="string-compare" condition="DocLibActions">
   <actions>
      <action id="document-publish">
          <evaluator negate="true">
             wcmqs.evaluator.doclib.action.isWebsiteContainerType
          </evaluator>
      </action>
   </actions>
</config>

Parent topic: Extending the Alfresco Share Document Library [81]

Client-side template and action extensions

Two global events are available to make it easier to add new metadata template renderers and client-side action handlers.

Metadata template renderer

Custom client renderers are registered with the Document Library using the new registerRenderer Bubbling event.

Ensure the client-side assets are loaded onto the page using the DocLibCustom / dependencies configuration section.

Using the example to add a new EXIF metadata renderer to produce the output as follows.

Giving the renderer an id of exposure also extends the metadata templates using a custom line config:

        
        <line index="20" id="exposure">{exposure exif.label.exposure}</line>
        
      

The JavaScript to register the custom renderer is then as follows. Note the event name, the event property names and where the custom renderer id is specified.

YAHOO.Bubbling.fire("registerRenderer",
{
  propertyName: "exposure",
  renderer: function exif_renderer(record, label)
  {      
  ...      
     return html;
  }
  });
  

See EXIF renderer source code [89] for the complete source for this example.

Custom action handler

In a very similar way to metadata renderers, new client-side actions are registered using the registerAction Bubbling event message.

YAHOO.Bubbling.fire("registerAction",
{   
  actionName: "onActionPreviewWebAsset",
  fn: function WCMQS_onActionPreviewWebAsset(record)
  {      
  ...      
  }
});
      

A full example of this can be found in the modules/wcmquickstart/wcmquickstartsharemodule project in the main SVN repository.

Parent topic: Extending the Alfresco Share Document Library [81]

Customizing document library views

Within the document library it is possible to select from a number of views. It is also possible to add custom views to the document library through configuration in the share-documentlibrary-config.xml file.

When browsing content in the document library it is possible to select from a variety of views including:

  • Simple
  • Detailed
  • Gallery
  • Filmstrip
  • Table
  • Audio
  • Media

These views are selected from the Options button.

The share-documentlibrary-config.xml file controls what views will be available as options when browsing the Document Library, My Files, Shared Files, and repository pages. It is also possible to use a module that provides evaluated configuration to have the options change based on criteria such as site name, site preset, user group, and so on.

It is possible to customize these views, and also add additional view types through configuration in the share-documentlibrary-config.xml file. These views are also present in the My Files, Shared Files and repository pages.

The views are rendered by view renderers, which have various attributes and also a block of configuration (in JSON) associated with them. For example:

  
<view-renderer id="email" iconClass="table" label="button.view.email" index="50" widget="Alfresco.DocumentListTableViewRenderer">
     <dependencies>
        <js src="components/documentlibrary/documentlist-view-detailed.js" />
        <js src="components/documentlibrary/documentlist-view-table.js" />
        <css src="components/documentlibrary/documentlist-view-table.css" />
     </dependencies>
     <json-config>
        {
           "actions": {
              "show": "true"
           },
           "indicators": {
              "show": "true"
           },
           "selector": {
              "show": "true"
           },
           "thumbnail": {
              "show": "false"
           },
           "propertyColumns": [
              {
                 "property": "cm:originator",
                 "label": "table.email.label.originator",
                 "link": "true"
              },
              {
                 "property": "cm:subjectline",
                 "label": "table.email.label.subjectline",
                 "link": "true"
              },
              {
                 "property": "cm:sentdate",
                 "label": "table.email.label.sentdate"
              },
              {
                 "property": "cm:addressee",
                 "label": "table.email.label.addressee"
              },
              {
                 "property": "cm:addressees",
                 "label": "table.email.label.addressees"
              },
              {
                 "property": "cm:attachments",
                 "label": "table.email.label.attachments"
              }
           ]
        }
     </json-config>
  </view-renderer>  
  

The following snippet shows a custom simplified view called "minimalist":

      
    <view-renderer id="minimalist" iconClass="table" label="button.view.minimalist" index="60" widget="Alfresco.DocumentListTableViewRenderer">
      <dependencies>
        <js src="components/documentlibrary/documentlist-view-simple.js" />
        <js src="components/documentlibrary/documentlist-view-table.js" />
        <css src="components/documentlibrary/documentlist-view-table.css" />
      </dependencies>
      <json-config>
        {
           "actions": {
              "show": "false"
           },
           "indicators": {
              "show": "false"
           },
           "selector": {
              "show": "true"
           },
           "thumbnail": {
              "show": "false"
           },
           "propertyColumns": [
              {
                 "property": "cm:name",
                 "label": "table.minimalist.label.name",
                 "link": "true"
              }
           ]
        }
      </json-config>
    </view-renderer>      
      
    

Note that the value of labels such as table.minimalist.label.name are set in properties files, so that multiple translations can be provided.

The minimalist custom view uses the Table View renderer.

There are four columns that are always present in the table, which can be hidden if required:

  • actions: the menu of actions that can be performed on the document
  • indicators: the set of icons that visually communicate information about the document
  • selector: the check box to use when selecting multiple documents
  • thumbnail: the thumbnail-sized preview of the document

All other columns must be defined in the propertyColumns array. The property attribute can be set to either a document property, such as cm:name or a metadata template renderer such as size, tags or date.

Parent topic: Extending the Alfresco Share Document Library [81]

Reference

This information provides reference material about the Alfresco Share Document Library; for example, jsNode methods and properties, evaluators, and EXIF renderer source code.
  • jsNode reference [90]jsNode is the preferred object to access node properties and aspects by using JavaScript on the browser.
  • Predefined evaluators [91] This information provides a list of the evaluators that are defined in the core Alfresco Share code.
  • EXIF renderer source code [92] The EXIF renderer source code is as follows.  
Parent topic: Extending the Alfresco Share Document Library [81]

jsNode reference

jsNode is the preferred object to access node properties and aspects by using JavaScript on the browser.

When dealing with DataTable records, record.jsNode should be available.

Note: It is the responsibility of any code that updates DataTable records to also ensure the jsNode property is updated (usually within the AJAX success callback).
To create a jsNode instance, use:
jsNode = new Alfresco.util.Node(p_node)
where p_node can either be a JavaScript object or JSON string. In either case, it should be in the format returned by the doclist-v2 data web scripts.

Methods

The jsNode methods are:

getNode Returns original node object. If a JSON string was passed in, this method returns a JavaScript object
toJSON Return the JSON string serialization of the node
setNodeRef Sets a new nodeRef - doesn't requery node properties however. Used solely when generating new page urls
hasAspect Returns true if this node has the given aspect
hasTag Returns true if this node has the given tag applied

The slingshot-documentlibrary-context.xml file contains all bean definitions for web tier evaluators.

Properties

The jsNode properties are:

Core node properties
nodeRef NodeRef
type The node’s type in short QName format
isContainer Returns true if the node is a container type
isLink Returns true if the node is a file or folderlink type
isLocked Returns true if the node has been locked by any user
linkedNode If this node is a link, returns a jsNode instance of the linked node
Content nodes
contentURL Of the format /api/node/content/{nodeRef}/{filename}
mimetype Content mimetype
size Content size in bytes
Properties
properties All properties are available either by using:
properties[“my:property”]

or

properties.my_property

Note that cm: properties are available without the prefix, i.e. "properties.description", "properties.title"

Aspects
aspects Array of aspects present on this node. See also hasAspect()
Permissions
permissions The permissions the current user has on this node. The list of permissions is defined in the applicationScriptUtils bean configuration.
Tags
tags Array of tags. See also hasTag()
Categories
categories Returns an array of the format [categoryName, category path]
Parent topic: Reference [87]

Predefined evaluators

This information provides a list of the evaluators that are defined in the core Alfresco Share code.

They are all defined in slingshot-documentlibrary-context.xml and can be reused in customizations as required.

Evaluators

These evaluators might need extra configuration before they can be used and form the basis of all metadata and action evaluators by using the bean configuration parent attribute.

Evaluator Properties
evaluator.doclib.action.hasAspect
  • aspects: List of aspects the node must have
evaluator.doclib.action.isMimetype
  • mimetypes: The node must match one of the mimetypes
evaluator.doclib.action.propertyNotNull
  • property: the node property must not be null for this evaluator to return true
evaluator.doclib.action.chainedMatchAll
  • evaluators: List of evaluators that are run in turn until one returns false or the end is reached
evaluator.doclib.action.chainedMatchOne
  • evaluators: List of evaluators that are run in turn until one returns true
evaluator.doclib.action.disableAction
  • Always returns false
evaluator.doclib.action.sitePreset
  • presets: current site must match one of the listed presets
evaluator.doclib.action.siteBased
  • Returns true if the current node is located within a Share site and the Site Document Library is being used
evaluator.doclib.action.containerType
  • types: Current documentLibrary container folder must match one of the listed types
evaluator.doclib.action.nodeType
  • allow Subtypes: Whether subtypes of the node are also allowed
  • types: Node must match one of the listed types
evaluator.doclib.action.value

This evaluator is described in further detail in the next topic.

  • accessor: jsNode property to be tested
  • comparator: Bean definition of class implementing the Comparator interface
evaluator.doclib.action.metadataValue

This evaluator is described in further detail in the next topic.

  • accessor: metadata property to be tested
  • comparator: Bean definition of class implementing the Comparator interface
evaluator.doclib.action.isBrowser
  • regex: Regular expression to match against browser userAgent string
evaluator.doclib.action.isPortlet Returns true if the application is deployed within a portlet environment
evaluator.doclib.action.notPortlet Returns the inverse of isPortlet

Comparators

The evaluator.doclib.action.value and evaluator.doclib.action.metadataValue evaluators use comparator helper beans in order to test a value against certain conditions. The following comparators are available in a standard install.

Evaluator Properties
org.alfresco.web.evaluator.StringEqualsComparator
  • aspects: Value to test string against
  • caseInsensitive: Defaults to true
org.alfresco.web.evaluator.NullValueComparator
  • value: Boolean to indicate if null should be the pass or fail case

Evaluator Instances

The following lists describe each of the evaluators defined for the v4.0 release.

Status indicator evaluators

evaluator.doclib.indicator.editing The current user is editing this node (working copy)
evaluator.doclib.indicator.lockOwner The current user is the lock owner (original of a working copy pair)
evaluator.doclib.indicator.locked The node is locked by another user
evaluator.doclib.indicator.googleDocsEditing The node is being edited using Google Docs
evaluator.doclib.indicator.googleDocsOwner The current user is editing the node is Google Docs
evaluator.doclib.indicator.googleDocsLocked Another user is editing the node using Google Docs
evaluator.doclib.indicator.activeWorkflows The node is involved in one or more active (advanced) workflows
evaluator.doclib.indicator.simpleWorkflow The node is part of a simple workflow process
evaluator.doclib.indicator.rules The node has rules applied
evaluator.doclib.indicator.exifMetadata The node has the EXIF metadata aspect applied
evaluator.doclib.indicator.geographicMetadata The node has the Geographic aspect applied

Metadata template evaluators

evaluator.doclib.metadata.hasCategories The node has the classifiable aspect applied
evaluator.doclib.metadata.isWorkingCopy The node is a working copy

Action evaluators

evaluator.doclib.action.simpleApprove Uses simpleWorkflowAspect and simpleApproveProperty to check for the simple workflow “Approve” action validity
evaluator.doclib.action.simpleReject Uses simpleWorkflowAspect and simpleRejectProperty to check for the simple workflow “Reject” action validity
evaluator.doclib.action.locateAction Checks the current filter is “path”
evaluator.doclib.action.inlineEdit Uses the inlineEditAspect and inlineEditMimetype evaluators to determine if a content node can be edited inline
evaluator.doclib.action.onlineEdit Uses onlineEditVtiServer, onlineEditBrowser and onlineEditMimetype evaluators to determine if the “Edit in Microsoft Office” action is valid
evaluator.doclib.action.hasLockableAspect Used in an inverted state for the “Edit Offline”, “Copy”, “Move” and “Publish” actions
evaluator.doclib.action.siteBased Enables the site-based permissions dialog. Used in inverted state for the repository-based permissions page action
evaluator.doclib.action.isWorkingCopy Tests whether a node is a working copy
evaluator.doclib.action.viewInExplorer Reads the repository-url config value to determine the validity of the “View in Explorer” action
evaluator.doclib.action.googleDocsEditable Enables “Check out to Google Docs” action
evaluator.doclib.action.googleDocsCheckIn Tests for the validity of the “Check in from Google Docs” action
evaluator.doclib.action.googleDocsView Tests whether a node has been checked out to Google Docs
evaluator.doclib.action.googleMaps Checks for the cm:geographic aspect
evaluator.doclib.action.transferred Tests for the trx:transferred action for the “View in Source Repository” action
Parent topic: Reference [87]

EXIF renderer source code

The EXIF renderer source code is as follows.  


/*************************************************************************************
                                              EXIF
                                           EXTENSION
*************************************************************************************/
(function()
{
  /**
   * Alfresco Slingshot aliases
   */
   var $html = Alfresco.util.encodeHTML,
   $isValueSet = Alfresco.util.isValueSet;

   if (Alfresco.DocumentList)
   {
        YAHOO.Bubbling.fire("registerRenderer",
        {
           propertyName: "exposure",
           renderer: function exif_renderer(record, label)
           {
                 var jsNode = record.jsNode,
                 properties = jsNode.properties,
                 html = "";
                 
              var expTime = properties["exif:exposureTime"] || 0,
                 exifObj =
                {
                     exposureFraction: expTime > 0 ? "1/" + Math.ceil(1/expTime) : expTime,
                     fNumber: properties["exif:fNumber"] || 0,
                     isoSpeedRatings: properties["exif:isoSpeedRatings"] || 0
                 };
                 
              html = '<span class="item">' + label + '<b>' +
YAHOO.lang.substitute(this.msg("exif.metadata.exposure"), exifObj) + '</b></span>';
              return html;
           }
        });
   }
})();

Parent topic: Reference [87]

Share themes

When you run Share, the look and feel is set by a default theme. Use this information to select one of the alternative themes available in Share, and also how to create and use your own themes for corporate branding.

Share themes consist of a directory containing a CSS and images files, and they can be located in the theme directory (<TOMCAT_HOME>/webapps/share/WEB-INF/classes/alfresco/site-data/themes). The default theme is called default.xml.

The following themes are available:

  • Blue theme (default)
  • Light theme
  • Yellow theme
  • Green theme
  • High contrast black
  • Google Docs theme

The default theme, which comprises the CSS and image assets used across all pages, displays in a new installation.

You can also create your own themes. Take a look at the Adding a custom Share Theme [93] tutorial.

  • Selecting Share themes [94] Only an Administrator user can select the Share theme. Any change to the theme will affect all users of the Alfresco Community Edition instance from the next time that they log in or from a browser refresh.
  • Editing a Share theme [95] A theme consists of some CSS files, an image directory, and a directory for assets for YUI. To create a new look, change the presentation.css file and, if required, replace or add images to the /images directory.
Parent topic: Configuring Alfresco Share [69]

Selecting Share themes

Only an Administrator user can select the Share theme. Any change to the theme will affect all users of the Alfresco Community Edition instance from the next time that they log in or from a browser refresh.
The available themes are installed in the <configRootShare>/classes/alfresco/site-data/themes directory.
  1. On the toolbar, select the Admin Tools menu option and click Application in the Tools list.

    The Options page appears.

  2. Select the required theme from the menu:
    • Green Theme
    • Yellow Theme
    • High Contrast Theme
    • Default Theme
  3. Click Apply.
The new theme displays in Share. The new theme persists across sessions.
Parent topic: Share themes [76]

Editing a Share theme

A theme consists of some CSS files, an image directory, and a directory for assets for YUI. To create a new look, change the presentation.css file and, if required, replace or add images to the /images directory.
  1. Open the presentation.css file.
  2. Locate the properties at the end of the presentation.css file.
  3. Edit the following four properties:
    1. color
    2. background
    3. background-color
    4. border

    Any change to these properties will change the theme.

    / Theme colors /
    .theme-color-1,
    a.theme-color-1,
    a.theme-color-1:visited,
    a.theme-color-1:hover
    {
       color: #6CA5CE;
    }
    
    .theme-color-2,
    a.theme-color-2,
    a.theme-color-2:visited,
    a.theme-color-2:hover
    {
       color: #038603;
    }
    
    .theme-color-3,
    a.theme-color-3,
    a.theme-color-3:visited,
    a.theme-color-3:hover
    {
       color: #C7DBEB;
    }
    
    .theme-color-4,
    a.theme-color-4,
    a.theme-color-4:visited,
    a.theme-color-4:hover
    {
       color: #0D3B57;
    }
    
    / Theme background colors /
     .theme-bg-color-1,
    div.theme-bg-color-1
    {
       background-color: #6CA5CE;
    }
    
     .theme-bg-color-2,
    div.theme-bg-color-2
    {
       background-color: #fffbdd;
    }
    
     .theme-bg-color-3,
    div.theme-bg-color-3
    {
       background-color: #DEE8ED;
    }
    
     .theme-bg-color-4,
    div.theme-bg-color-4
    {
       background-color: #EBEFF1;
    }
    
    .theme-bg-color-5,
    div.theme-bg-color-5
    {
       background-color: #2B6EB5;
    }
    
     .theme-bg-1
    {
       / background-image: url(images/navbar-bg.png); /
    }
    
     .theme-bg-2
    {
       / background-image: url(images/navbar-bg-2.png); /
    }
    
     / Theme border type/colors /
     .theme-border-1
    {
       border-color: #457f63;
       border-style: dotted;
    }
    
     .theme-border-2
    {
       border-color: #2B6EB5;
    }
  4. Locate the YUI Theme Changes section.

    This section allows changes to the YUI components.

  5. Edit the properties in this section to change the theme.
Parent topic: Share themes [76]

Share Forms

Alfresco Share presents data view and entry forms throughout its user interface, which are built on the Surf framework. This framework provides a convention for implementing forms.

Alfresco Community Edition uses only one configuration syntax and one set of UI controls for forms throughout.

  • Use of forms in Share [96] Forms are used in the View Metadata and Edit Metadata pages within Share.
  • Forms architecture [97] The forms engine consists of four major parts; the form service, form component, form configuration, and the JavaScript for UI component (which includes the forms runtime).
  • Forms event sequence [98] When a request is made to a page containing the form component, the following sequence of events occurs.
  • Configuring forms [99] Forms can be configured through the share-config-custom.xml file.
  • Customizing forms controls [100] One of the most common customizations is to add new controls. A control is the label for a field and the interface that the user interacts with for setting the value of the field.
  • Customizing the validation handler [101] A validation handler is a small JavaScript function that gets called by the forms runtime when a field value needs to be validated.
  • Displaying type metadata [102] You can configure the type metadata in the share-config-custom.xml file in <web-extension>. It is also possible to deploy custom configurations via JARs or AMPs.
  • Displaying aspect metadata [103] Add the properties and associations defined on aspects by adding them to the list of fields to show for a type. The aspects that appear can be defined on a type by type basis, and you can control the ordering of the fields.
  • Configuring a form control [104] Most of the built in controls have parameters that allow some basic customization.
  • Grouping fields [105] For longer forms, you can group fields together in logical grouped or nested sections.
  • Changing the default set label [106] Fields that do not specify a set belong to the implicit default set. They are rendered together, by default, but without any visual grouping.
  • Providing a custom form control [107] If none of the out-of-the-box controls are sufficient, you can add new controls and reference them. Controls are FreeMarker template snippets, therefore, they contain only the HTML markup required to represent the control. The templates need to be stored in the site-webscripts directory, which will usually be in the application server shared classpath.
  • Changing the field label position [108] By default, forms are rendered with the field labels positioned above the input control.
  • Providing a custom form template [109] The default template that renders the form UI generates one column of fields. There are scenarios where more control might be required over the layout. To enable these scenarios, it is possible to replace the default template with a custom template. A different template can be provided for each form mode.
Parent topic: Configuring Alfresco Share [69]

Use of forms in Share

Forms are used in the View Metadata and Edit Metadata pages within Share.

The following screen shot shows the form component on the Edit Metadata page.

The content of the form is completely driven from configuration custom types, custom aspects. Their properties and associations can be displayed.

Parent topic: Share Forms [77]

Forms architecture

The forms engine consists of four major parts; the form service, form component, form configuration, and the JavaScript for UI component (which includes the forms runtime).

The following diagram shows a high-level architecture diagram of the forms engine.

The forms runtime is responsible for the execution of a form. It manages all input, validation (client or call-back), events, submission, and it consists of a small, lightweight JavaScript library. An unobtrusive JavaScript pattern is used, where behavior is added to the HTML form elements when the page loads. The forms runtime provides the following capabilities:

  • Mandatory property handling
  • Validation (enforceable at submission, as the user types or when a field loses focus), which includes:
    • Regular expression matching
    • String length
    • Email address
    • Is number
    • Numeric range
    • Date range
    • List of values
  • Repeating fields (for handling multi-valued properties)
Parent topic: Share Forms [77]

Forms event sequence

When a request is made to a page containing the form component, the following sequence of events occurs.
  1. The form component looks up the form configuration for the item being requested.
  2. The form component retrieves the list of fields to display (if any) and requests a form definition for those fields and the item from the form service by using its REST API.
  3. The form service looks for a form processor that can process the kind of item.
  4. The form processor is asked to generate a form definition.
  5. The form processor executes any registered filters before and after the main processing.
  6. The REST API takes the result from the form processor/form service and constructs the form definition JSON response.
  7. The form component receives the result from the form service and combines it with the form configuration to produce the form UI model.
  8. The form component Freemarker template iterates around the fields and includes the relevant controls.
  9. The form component Freemarker template instantiates the FormUI JavaScript component.
  10. The FormUI JavaScript instantiates the forms runtime and registers all validation handlers.

For a description of the available form controls, refer to Forms reference [110].

At this point, the form is ready for the user to interact. When the user interacts with the form, the forms runtime constantly checks the validation rules enabling and disabling the Submit button appropriately. When the user submits the form, the following sequence of events occurs.

  1. The browser submits the form by calling the REST API.
  2. The form service looks for a form processor that can process the kind of item.
  3. The form processor is asked to persist the form data.
  4. The form processor executes any registered filters before and after the main processing.
  5. The REST API takes the result from the form processor/form service and constructs the JSON response.
  6. The browser receives the response and processes it appropriately.
Parent topic: Share Forms [77]

Configuring forms

Forms can be configured through the share-config-custom.xml file.

The default forms configuration is specified in the ./tomcat/webapps/share/WEB-INF/classes/alfresco/share-form-config.xml file. This file contains all the default controls and constraint handlers for the content model and the form configuration for the cm:content and cm:folder types. This file also contains an example of configuring the cm:content type.

Note: You should apply all your forms customizations to a custom configuration file. To configure forms for the Share application, use the custom configuration file named share-config-custom.xml.

There are a number of files involved in form configuration, but generally you should add your configurations to a custom configuration file. The default configuration files are listed here so that you can see the range of configurations available:

File Description
form-config.xml Default form configuration file
share-form-config.xml Default forms for content and folder creation, edit and search
share-datalist-form-config.xml Forms for the built-in datalists
share-workflow-form-config.xml Core workflow forms and built-in workflows
CAUTION:
Avoid editing the default configuration files directly.
  1. Open the ./tomcat/shared/classes/alfresco/<web-extension>/share-config-custom.xml file.
  2. Modify the forms configuration settings using the XML configuration syntax.
Parent topic: Share Forms [77]

Customizing forms controls

One of the most common customizations is to add new controls. A control is the label for a field and the interface that the user interacts with for setting the value of the field.
A control is a FreeMarker template snippet that includes the markup to define the control. The model for the template includes the field and form being generated, represented by a field and form object, respectively. The following snippet shows the structure of the field object, using the cm:name property as an example:

{
   kind : "field",
   id : "prop_cm_name",
   configName : "cm:name",
   name : "prop_cm_name",
   dataType : "d:text",
   type : "property",
   label : "Name",
   description : "Name",
   mandatory : true
   disabled : false,
   repeating : false,        
   dataKeyName : "prop_cm_name",
   value : "plain-content.txt",
   control:
   {
      params: {},
      template : "controls/textfield.ftl"
   }
}
Although the id property provides a unique identifier for the field, it is only scoped to the current form. If there are multiple forms on the page containing the same field, this identifier will not be unique. The model property fieldHtmlId should be used as the identifier for the control, as this is guaranteed to be unique for the page.

The state of the disabled property must always be adhered to when implementing controls as this is driven from the field definition returned from the FormService and from the read-only attribute in the form configuration. If the disabled property is set to true, the control should never allow the value to be edited.

The control is also responsible for rendering an appropriate UI representation for the mode the form is currently in. The form mode can be retrieved from the form.mode property. A pattern used by most the out-of-the-box controls is shown:


<#if form.mode == "view">
   // view representation goes here...
<#else>
   // edit and create representation goes here...
</#if>

The final rule for controls is that they must supply the field current value in a DOM element that has a value property and the id property set to the value of fieldHtmlId Freemarker variable.

For advanced controls, that is, association, date, period, and so on, this usually requires a hidden form field.

Parent topic: Share Forms [77]

Customizing the validation handler

A validation handler is a small JavaScript function that gets called by the forms runtime when a field value needs to be validated.

The JavaScript function signature for a validation handler looks like this:

/**
 * Validation handler for a field.
 * 
 * @param field {object} The element representing the field the validation is for
 * @param args {object} Object containing arguments for the handler
 * @param event {object} The event that caused this handler to be called, maybe null
 * @param form {object} The forms runtime class instance the field is being managed by
 * @param silent {boolean} Determines whether the user should be informed upon failure
 * @param message {string} Message to display when validation fails, maybe null
 * @static
 */
function handler-name(field, args, event, form, silent, message)   

The built in "mandatory" validation handler is defined as follows:

Alfresco.forms.validation.mandatory = function mandatory(field, args, event, form, silent, message)

The field parameter is usually the HTML DOM element representing the field's value, which is normally an HTML input DOM element, so that the value property can be accessed. The structure of the args parameter is dependent on the handler being implemented. By default, these will be the parameters of the constraint defined on the field.

The handler is responsible for taking the value from the field and uses the args parameter to calculate whether the current value is valid or not, returning true if it is valid and false if it is not.

Parent topic: Share Forms [77]

Displaying type metadata

You can configure the type metadata in the share-config-custom.xml file in <web-extension>. It is also possible to deploy custom configurations via JARs or AMPs.
The following snippet shows the forms definition in the share-config-custom.xml file.

<config evaluator="node-type" condition="cm:content">
   <forms>
      <form>
         <field-visibility>
            <show id="cm:name" />
            <show id="cm:title" force="true" />
            <show id="cm:description" force="true" />
            <show id="mimetype" />
            <show id="cm:author" force="true" />
            <show id="size" for-mode="view" />
            <show id="cm:creator" for-mode="view" />
            <show id="cm:created" for-mode="view" />
            <show id="cm:modifier" for-mode="view" />
            <show id="cm:modified" for-mode="view" />
         </field-visibility>
      </form>
   </forms>
</config>

The configuration defines that the cm:name property is visible in all modes, whereas the cm:creator, cm:created, cm:modifier, and cm:modified properties are visible in view mode only.

The mimetype and size properties are known as transient properties because they do not exist as properties in the model. These properties are formed from the cm:content property. The NodeFormProcessor knows about these properties and generates a field definition to represent them so that they will appear in the forms.

The force attribute ensures that the NodeFormProcessor searches the entire content model for the property or association definition before returning anything.

  1. Open the <web-extension>/share-config-custom.xml file.
  2. Enter the configuration for custom types.

    The following example configuration shows the my:text, my:dateTime and my:association properties being configured for the custom my:example type.

    
    <config evaluator="node-type" condition="my:example">
       <forms>
          <form>
             <field-visibility>
                <show id="my:text" />
                <show id="my:dateTime" />
                <show id="my:association" />
             </field-visibility>
          </form>
       </forms>
    </config>
    
    
  3. Add more fields to the default configuration.

    The following example shows how to show the node's DBID property for all cm:content nodes.

              
    <config evaluator="node-type" condition="cm:content">
       <forms>
         <form>
            <appearance>
               <field id="cm:description">
                  <control>
                     <control-param name="rows">20</value>
                     <control-param name="columns">20</value>
                  </control>
               </field>
            </appearance>
         </form>
      </forms>
    </config>
    
    
    Note: The full prefix version of the type is required in the condition attribute.

    The force attribute forces the NodeFormProcessor to search the entire content model for the property or association definition before returning anything.

  4. Save your file.
Parent topic: Share Forms [77]

Displaying aspect metadata

Add the properties and associations defined on aspects by adding them to the list of fields to show for a type. The aspects that appear can be defined on a type by type basis, and you can control the ordering of the fields.
  1. Open the <web-extension>\share-config-custom.xml file.

    Note: While you can configure the aspect metadata by directly editing the share-config-custom.xml file in <web-extension>. It is also possible to deploy custom configurations via JARs or AMPs.
  2. Enter the configuration for custom types.

    The following example configuration shows the cm:from and cm:to properties for the cm:effectivity aspect.

    
    <config evaluator="node-type" condition="cm:content">
       <forms>
          <form>
             <field-visibility>
                <show id="cm:name" />
                <show id="cm:title" force="true" />
                <show id="cm:description" force="true" />
                <show id="mimetype" />
                <show id="cm:author" force="true" />
                <show id="size" for-mode="view" />
                <show id="cm:creator" for-mode="view" />
                <show id="cm:created" for-mode="view" />
                <show id="cm:modifier" for-mode="view" />
                <show id="cm:modified" for-mode="view" />
    
                <!-- cm:effectivity aspect -->
                <show id="cm:from"/>
                <show id="cm:to"/>
             </field-visibility>
          </form>
       </forms>
    </config>
    
    
  3. Add custom aspects to the default configuration by overriding the configuration.

    The following example shows how to add the fields of an example aspect to all forms for the cm:content type.

              
    <config evaluator="node-type" condition="cm:content">
       <forms>
          <form>
             <field-visibility>
                <!-- fields from my example aspect -->
                <show id="my:aspectProperty" />
                <show id="my:aspectAssociation" />
             </field-visibility>
          </form>
       </forms>
    </config>
    
    

    This will apply the aspects to all cm:content nodes.

  4. Save the file.
  5. It is also possible to have the fields appear for any type of node to which the aspect is applied. For example, you may wish to display the my:aspectProperty and my:aspectAssociation fields for any type of node to which the my:customAspect is applied:

              
    <config evaluator="aspect" condition="my:customAspect">
       <forms>
          <form>
             <field-visibility>
                <!-- fields from my example aspect -->
                <show id="my:aspectProperty" />
                <show id="my:aspectAssociation" />
             </field-visibility>
          </form>
       </forms>
    </config>
    
    
Parent topic: Share Forms [77]

Configuring a form control

Most of the built in controls have parameters that allow some basic customization.
  1. Open the <web-extension>\share-config-custom.xml file.
  2. Change the number of rows and columns used for the textarea control that the cm:description field uses by default.

    
    <config evaluator="node-type" condition="cm:content">
       <forms>
          <form>
             <appearance>
                <field id="cm:description">
                   <control>
                      <control-param name="rows">20</control-param>
                      <control-param name="columns">80</control-param>
                   </control>
                </field>
             </appearance>
          </form>
       </forms>
    </config>
    
    
  3. If all textarea controls in the application need to have these settings, configure the control in the default-controls element. For example:

              
    <config evaluator="node-type" condition="cm:content">
       <forms>
          <default-controls>
             <type name="d:mltext">
                <control-param name="rows">20</control-param>
                <control-param name="columns">80</control-param>
             </type>
          </default-controls>
       </forms>
    </config>
    
  4. Save the file.
Parent topic: Share Forms [77]

Grouping fields

For longer forms, you can group fields together in logical grouped or nested sections.
  1. Open the <web-extension>\share-config-custom.xml file.
  2. Enter the configuration for custom types.

    The following example configuration shows how to group some fields from an imaginary custom my:example type.

    
    <config evaluator="model-type" condition="my:example">
       <forms>
          <form>
             <field-visibility>
                <show id="cm:name" />
                <show id="my:text" />
                <show id="my:mltext" />
                <show id="my:boolean" />
                <show id="my:int" />
                <show id="my:long" />
                <show id="my:double" />
                <show id="my:float" />
                <show id="my:status" />
                <show id="my:restricted-string" />
                <show id="my:date" />
                <show id="my:dateTime" />
             </field-visibility>
             <appearance>
                <set id="text" appearance="fieldset" label="Text Fields" />
                <set id="number" appearance="panel" label="Number Fields" />
                <set id="date" appearance="fieldset" label="Date Fields" />
                   
                <field id="cm:name" set="text" />
                <field id="my:text" set="text" />
                <field id="my:mltext" set="text" />
                <field id="my:boolean" set="text" />
                   
                <field id="my:int" set="number" />
                <field id="my:long" set="number" />
                <field id="my:double" set="number" />
                <field id="my:float" set="number" />
                   
                <field id="my:date" set="date" />
                <field id="my:dateTime" set="date" />
             </appearance>
          </form>
       </forms>
    </config>
    
    

    Nested sets are also supported. Use the parent attribute in the set element. The following example configuration shows the fields of the my:example type in a nested set.

              
    <config evaluator="model-type" condition="my:example">
       <forms>
          <form>
             <field-visibility>
                <show id="cm:name" />
                <show id="my:text" />
                <show id="my:mltext" />
                <show id="my:boolean" />
                <show id="my:int" />
                <show id="my:long" />
                <show id="my:double" />
                <show id="my:float" />
             </field-visibility>
             <appearance>
                <set id="builtin" appearance="fieldset" label="Built In" />
                <set id="custom" appearance="fieldset" label="Custom Data" />
                <set id="text" parent="custom" appearance="panel" label="Text" />
                <set id="number" parent="custom" appearance="panel" label="Numbers" />
                   
                <field id="cm:name" set="builtin" />
                   
                <field id="my:text" set="text" />
                <field id="my:mltext" set="text" />
                <field id="my:boolean" set="text" />
                   
                <field id="my:int" set="number" />
                <field id="my:long" set="number" />
                <field id="my:double" set="number" />
                <field id="my:float" set="number" />
             </appearance>
          </form>
       </forms>
    </config>
    
    
  3. Save the file.
Parent topic: Share Forms [77]

Changing the default set label

Fields that do not specify a set belong to the implicit default set. They are rendered together, by default, but without any visual grouping.
  1. Open the <web-extension>\share-config-custom.xml file.
  2. Enter the configurations for the set label.

    The appearance of the default set can be controlled in the same way as other sets, for example, using an identifier of an empty string.

    <set id="" appearance="panel" />
    This will render a panel around all the fields with a label of Default.

    To specify a different label, add the label attribute. For example, the following label will be General.

    <set id="" appearance="panel" label="General" />

    You can also use a message bundle key.

    <set id="" appearance="panel" label-id="form.set.general" />
  3. Save the file.
Parent topic: Share Forms [77]

Providing a custom form control

If none of the out-of-the-box controls are sufficient, you can add new controls and reference them. Controls are FreeMarker template snippets, therefore, they contain only the HTML markup required to represent the control. The templates need to be stored in the site-webscripts directory, which will usually be in the application server shared classpath.
  • The following example configuration shows a very simple custom text field control that always displays with a green background, white text, and 700 pixels wide. For a production system, use a CSS class; however, this example shows a hard coded style.

    
    <div class="form-field">
       <#if form.mode == "view">
          <div class="viewmode-field">
             <span class="viewmode-label">${field.label?html}:</span>
             <span class="viewmode-value">${field.value?html}</span>
          </div>
       <#else>
          <label for="${fieldHtmlId}">${field.label?html}:<#if field.mandatory><span class="mandatory-indicator">*</span></#if></label>
          <input id="${fieldHtmlId}" type="text" name="${field.name}" value="${field.value}" 
                       style="background-color: green; color: white; width: 700px;" <#if field.disabled>disabled="true"</#if> />
       </#if>
    </div>
    
    
  • The following example configuration shows this control being used for the cm:name property, with a file name of my-textfield.ftl.

    
    <config evaluator="node-type" condition="cm:content">
       <forms>
          <form>
             <appearance>
                <field id="cm:name">
                   <control template="/my-textfield.ftl" />
                </field>
             </appearance>
          </form>
       </forms>
    </config>
    
    
Parent topic: Share Forms [77]

Changing the field label position

By default, forms are rendered with the field labels positioned above the input control.

To change this layout, provide a custom CSS to override the default style rules. Control dependencies can be provided by using custom configuration.

  1. Add the custom CSS in the custom-label-layout.css file, located in the /custom/forms directory within the web application.
  2. Add the following configuration:

    
    <config>
       <forms>
          <dependencies>
             <css src="/custom/forms/custom-label-layout.css" />
          </dependencies>
       </forms>
    </config>
    
    
  3. To move the labels to the left of the input control, the following CSS should be present in the custom-label-layout.css file:

              
    .form-container label
    {
       display: inline;
       float: left;
       text-align: right;
       width: 6em;
       margin-right: 1em;
       margin-top: 0.4em;
    }
    
    
  4. Save the file.

    The result of this customization is shown as:

Parent topic: Share Forms [77]

Providing a custom form template

The default template that renders the form UI generates one column of fields. There are scenarios where more control might be required over the layout. To enable these scenarios, it is possible to replace the default template with a custom template. A different template can be provided for each form mode.

Store the custom templates in the site-webscripts directory, which is usually be in the application server shared classpath.

  1. The example shows the Edit form for the standard cm:content type being configured to render with two columns of fields.

    
    <config evaluator="node-type" condition="cm:content">
       <forms>
          <form>
                <edit-form template="/2-column-edit-form.ftl" />
          </form>
       </forms>
    </config>
    
    
    The example template 2-column-edit-form.ftl is available in the distribution in the samples folder.

    The following example shows the contents of the 2-column-edit-form.ftl file. It uses some of the Freemarker macros available in form.lib.ftl but supplies its own renderSetWithColumns macro to render the HTML required to create the grid using the YUI grid CSS capabilities.

               
    <#import "/org/alfresco/components/form/form.lib.ftl" as formLib />
    
    <#if error?exists>
       <div class="error">${error}</div>
    <#elseif form?exists>
    
       <#assign formId=args.htmlid + "-form">
       <#assign formUI><#if args.formUI??>${args.formUI}<#else>true</#if></#assign>
    
       <#if formUI == "true">
          <@formLib.renderFormsRuntime formId=formId />
       </#if>
       
       <div id="${formId}-container" class="form-container">
          
          <#if form.showCaption?exists && form.showCaption>
             <div id="${formId}-caption" class="caption"><span class="mandatory-indicator">*</span>${msg("form.required.fields")}</div>
          </#if>
             
          <#if form.mode != "view">
             <form id="${formId}" method="${form.method}" accept-charset="utf-8" enctype="${form.enctype}" action="${form.submissionUrl}">
          </#if>
          
          <div id="${formId}-fields" class="form-fields"> 
            <#list form.items as item>
                <#if item.kind == "set">
                   <@renderSetWithColumns set=item />
                <#else>
                   <@formLib.renderField field=item />
                </#if>
             </#list>
          </div>
             
          <#if form.mode != "view">
             <@formLib.renderFormButtons formId=formId />
             </form>
          </#if>
    
       </div>
    </#if>
    
    <#macro renderSetWithColumns set>
       <#if set.appearance?exists>
          <#if set.appearance == "fieldset">
             <fieldset><legend>${set.label}</legend>
          <#elseif set.appearance == "panel">
             <div class="form-panel">
                <div class="form-panel-heading">${set.label}</div>
                <div class="form-panel-body">
          </#if>
       </#if>
       
       <#list set.children as item>
          <#if item.kind == "set">
             <@renderSetWithColumns set=item />
          <#else>
             <#if (item_index % 2) == 0>
             <div class="yui-g"><div class="yui-u first">
             <#else>
             <div class="yui-u">
             </#if>
             <@formLib.renderField field=item />
             </div>
             <#if ((item_index % 2) != 0) || !item_has_next></div></#if>
          </#if>
       </#list>
       
       <#if set.appearance?exists>
          <#if set.appearance == "fieldset">
             </fieldset>
          <#elseif set.appearance == "panel">
                </div>
             </div>
          </#if>
       </#if>
    </#macro>
    
    
  2. When the configuration and template is in place, the Edit Metadata page for a cm:content type in Share has the following appearance.
Parent topic: Share Forms [77]

Adding Custom MIME types

You can add custom MIME types to Share.

When you edit the properties of a document, it is possible to select a MIME types from a drop-down list. You can add custom MIME types to this list.

Custom MIME types are added to a configuration file. An example file is provided - ./tomcat/shared/classes/alfresco/extension/mimetype/mimetypes-extension-map.xml.sample. You can rename this file to ./tomcat/shared/classes/alfresco/extension/mimetype/mimetypes-extension-map.xml. It will be processed when Alfresco Community Edition is restarted.

The content of the example file is as follows:

         
<alfresco-config area="mimetype-map">
   
   <config evaluator="string-compare" condition="Mimetype Map">
      <mimetypes>

         <mimetype mimetype="application/XXX" display="Example mimetype">
            <extension>ex</extension>
         </mimetype>

      </mimetypes>
   </config>
   
</alfresco-config>         
         
      

You can add custom MIME types as required to this file, or create your own configuration file located on the classpath.

Parent topic: Configuring Alfresco Share [69]

Form Controls

When defining a form the form controls for each field controls how the field is displayed and handled.
Extension Point Form Controls
Support Status Full Support [10]
Architecture Information Share Architecture [8].
Description Share comes with form controls for most of the field types that is used in a form, such as integers, dates, text, and so on. However, sometimes it is necessary to define and implement a custom form control. A form control is implemented as a FreeMarker template, here is the one for a standard Text Field (textfield.ftl):
<div class="form-field">
   <#if form.mode == "view">
      <div class="viewmode-field">
         <#if field.mandatory && !(field.value?is_number) && field.value == "">
            <span class="incomplete-warning"><img src="${url.context}/res/components/form/images/warning-16.png" title="${msg("form.field.incomplete")}" /><span>
         </#if>
         <span class="viewmode-label">${field.label?html}:</span>
         <#if field.control.params.activateLinks?? && field.control.params.activateLinks == "true">
            <#assign fieldValue=field.value?html?replace("((http|ftp|https):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?\\^=%&:\\/~\\+#]*[\\w\\-\\@?\\^=%&\\/~\\+#])?)", "<a href=\"$1\" target=\"_blank\">$1</a>", "r")>
         <#else>
            <#if field.value?is_number>
               <#assign fieldValue=field.value?c>
            <#else>
               <#assign fieldValue=field.value?html>
            </#if>
         </#if>
         <span class="viewmode-value"><#if fieldValue == "">${msg("form.control.novalue")}<#else>${fieldValue}</#if></span>
      </div>
   <#else>
      <label for="${fieldHtmlId}">${field.label?html}:<#if field.mandatory><span class="mandatory-indicator">${msg("form.required.fields.marker")}</span></#if></label>
      <input id="${fieldHtmlId}" name="${field.name}" tabindex="0"
             <#if field.control.params.password??>type="password"<#else>type="text"</#if>
             <#if field.control.params.styleClass??>class="${field.control.params.styleClass}"</#if>
             <#if field.control.params.style??>style="${field.control.params.style}"</#if>
             <#if field.value?is_number>value="${field.value?c}"<#else>value="${field.value?html}"</#if>
             <#if field.description??>title="${field.description}"</#if>
             <#if field.control.params.maxLength??>maxlength="${field.control.params.maxLength}"<#else>maxlength="1024"</#if> 
             <#if field.control.params.size??>size="${field.control.params.size}"</#if> 
             <#if field.disabled && !(field.control.params.forceEditable?? && field.control.params.forceEditable == "true")>disabled="true"</#if> />
      <@formLib.renderFieldHelp field=field />
   </#if>
</div>

These standard form control implementations can be found in the tomcat/webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/form/controls directory. The two FreeMarker root objects that contain most of the information that we need when implementing the control is the form object and the field object. These objects get their data from the form and field definitions. And you can also implement Form Filters [111] to add extra properties that can be used in the form control implementation.

The following form definition shows the use of the Text Field form control:

<config evaluator="node-type" condition="cm:content">
      <forms>
         <!-- Default form configuration for the cm:content type -->
         <form>
            <field-visibility>
               <show id="cm:name" />
               <show id="cm:title" force="true" />
               ...               
            </field-visibility>
            <appearance>
               <field id="cm:name">
                 <control>
                    <control-param name="maxLength">255</control-param>
                 </control>
               </field>
               <field id="cm:title">
                  <control template="/org/alfresco/components/form/controls/textfield.ftl" />
               </field>
               ...               
            </appearance>
         </form>

The field cm:title is using the textfield.ftl form control. You can also leave out the form control specification, like is shown here for the cm:name field, and let the forms engine select a matching form control based on field data type.

Implementing a custom form control is as easy as creating a new FreeMarker template file and putting it somewhere under the alfresco/web-extension/site-webscripts/ directory. Here is an example of a custom form control for a Due Date field that should be editable sometimes and read-only sometimes, it is stored in org/alfresco/training/components/form/controls/duedate.ftl:

<#-- This Date control implementation checks a property that is set up in a Form Filter to see
     if the due date should be displayed as read-only or not -->
<#if form.data['prop_dueDateReadOnly'] == true>
    <#-- Bring in standard info.ftl -->
    <#include "/org/alfresco/components/form/controls/info.ftl" />
<#else>
    <#-- Bring in standard date.ftl -->
    <#include "/org/alfresco/components/form/controls/date.ftl" />
</#if>

This custom form control uses a custom property called prop_dueDateReadOnly that would need to be set up in a Form Filters [111]. This form control would then be used as follows:

<config evaluator="node-type" condition="acme:document">
      <forms>
        <form>
          <field-visibility>
            ...
            <show id="acme:dueDate" />
          </field-visibility>
          <appearance>
            ...
            <field id="acme:dueDate" set="info" label-id="acme.due.date">
                <control template="/org/alfresco/training/components/form/controls/duedate.ftl" />
            </field>
          </appearance>
        </form>
      </forms>
    </config>
This defines a form for a custom type called acme:document, which contains a property called acme:dueDate that should use the new form control.
Deployment - App Server tomcat/shared/classes/alfresco/web-extension/site-webscripts/ (Untouched by re-depolyments and upgrades)

Best practice is to put the file in a directory that explains what the file is for, such as for example:

tomcat/shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/training/components/form/controls

Deployment All-in-One SDK project [59]. aio/share-jar/src/main/resources/alfresco/web-extension/site-webscripts/{custom path}
More Information
  • Customizing Form Controls [100]
  • Form Control Reference [110]
  • Forms [112]
Tutorials
  • Adding a custom Form Control [107]
  • Configuring a Form Control [104]
Parent topic: Share Extension Points [2]

Form Field Validation Handlers

A validation handler is a small JavaScript function that gets called by the forms runtime when a field value needs to be validated.
Extension Point Form Field Validation Handlers
Support Status Full Support [113]
Architecture Information Share Architecture [8]
Description

The JavaScript function signature for a validation handler looks like this:

/**
 * Validation handler for a field.
 * 
 * @param field {object} The element representing the field the validation is for
 * @param args {object} Object containing arguments for the handler
 * @param event {object} The event that caused this handler to be called, maybe null
 * @param form {object} The forms runtime class instance the field is being managed by
 * @param silent {boolean} Determines whether the user should be informed upon failure
 * @param message {string} Message to display when validation fails, maybe null
 * @static
 */
function handler-name(field, args, event, form, silent, message)   

The built in "mandatory" validation handler is defined as follows:

Alfresco.forms.validation.mandatory = function mandatory(field, args, event, form, silent, message)

The field parameter is usually the HTML DOM element representing the field's value, which is normally an HTML input DOM element, so that the value property can be accessed. The structure of the args parameter is dependent on the handler being implemented. By default, these will be the parameters of the constraint defined on the field.

The handler is responsible for taking the value from the field and uses the args parameter to calculate whether the current value is valid or not, returning true if it is valid and false if it is not.

Now, to implement a validation handler you would need to first create a JavaScript function that does the validation. To do email address validation via RegExp, it could look something like this:

if (typeof MyCustomNamespace == "undefined" || !MyCustomNamespace) {
    var MyCustomNamespace = {};
}

MyCustomNamespace.forms.validation.checkEmailValidity =
    function checkEmailValidity(field, args, event, form, silent, message) {
    var valid = true;

    valid = YAHOO.lang.trim(field.value).length !== 0;
    if (valid) {
        var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
        valid = re.test(field.value);
    }

    return valid;
}   

After this, make sure the JavaScript file is loaded when the form is loaded, which can be set as follows in share-config-custom.xml when defining the form:

<config>
    <forms>
        <dependencies>
            <js src="/js/email-form-field-validation-handler.js"/>
        </dependencies>
    </forms>
</config>

Then, use the field value validation handler as follows in the form definition:

<config><forms><form>
  ...
  <appearance>
    <field id="acme:emailAddress">
     <constraint-handlers>
       <constraint type="MANDATORY" validation-handler="MyCustomNamespace.forms.validation.checkEmailValidity" event="keyup"/>
     </constraint-handlers>
    </field>
Deployment - App Server Deploying field validation handlers directly into and Application server is not recommended as you would have to put files directly under tomcat/webapps/share/, and these files would then be gone as soon as the webapp is re-deployed or upgraded. It is better to use a Share AMP [114] project.
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/js/{custom path} - validation handler JavaScript files
  • aio/share-jar/src/main/resources/META-INF/share-config-custom.xml - form definitions with validation handlers
More Information
  • Forms Reference [110]: see the constraint-handlers description
  • Forms [112]
Tutorials  
Alfresco Developer Blogs  
Parent topic: Share Extension Points [2]

Evaluators

Component visibility in the Share user interface can be controlled by Evaluators.
Extension Point Evaluators
Support Status Full Support [10]
Architecture Information Share Architecture [8]
Description

An evaluator is used by other extension points, such as Document Library Actions and Surf Extension Modules, to control when they should display or hide something. Custom evaluators are either configured or coded in Java. The following is an example of an evaluator that is configured as a Spring Bean:

<bean id="evaluator.doclib.metadata.hasExposure"
      parent="evaluator.doclib.action.propertyNotNull">  
   <property name="property" value="exif:exposureTime"/>
</bean>   

In this case a new custom evaluator with ID evaluator.doclib.metadata.hasExposure is created. It is based on the out-of-the-box propertyNotNull evaluator, which takes a property parameter with the content model property that should be checked for null. This evaluator is now ready to use in for example a Document Library Action definition.

If the evaluator is a bit more complex, and there is no existing evaluator that it can be based on, then we can implement the evaluator in Java as in the following example:

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.web.evaluator.BaseEvaluator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

public class CheckIfDocIsEmailedEvaluator extends BaseEvaluator {
    private static Log logger = LogFactory.getLog(CheckIfDocIsEmailedEvaluator.class);
    private static final String ASPECT_EMAILED = "cm:emailed";

    @Override
    public boolean evaluate(JSONObject jsonObject) {
        try {
            JSONArray nodeAspects = getNodeAspects(jsonObject);
            if (nodeAspects == null) {
                logger.info("No aspects found");
                return false;
            } else {
                if (nodeAspects.contains(ASPECT_EMAILED)) {
                    logger.info("Has been emailed");
                    return true;
                } else {
                    logger.info("Has NOT been emailed");
                    return false;
                }
            }
        } catch (Exception err) {
            throw new AlfrescoRuntimeException("JSONException whilst running action evaluator: " + err.getMessage());
        }
    }
}

This evaluator needs to be declared as a Spring bean too as follows:

<bean id="org.alfresco.training.evaluator.doclib.action.isEmailed"
          class="org.alfresco.training.documentlibrary.action.evaluator.CheckIfDocIsEmailedEvaluator" />
    

An evaluator is used by referring to it via the Spring Bean ID, as in the following example when declaring a Document Library Action:

<action id="org.alfresco.training.doclib.action.sendAsEmail"
        icon="email"
        type="javascript"
        label="actions.training.alfresco.sendAsEmail">
    <param name="function">onActionFormDialog</param>
    <param name="itemKind">action</param>
    <param name="itemId">send-as-email</param>
    <param name="mode">create</param>
    <param name="destination">{node.nodeRef}</param>
    <param name="successMessage">message.send-as-email.success</param>
    <param name="failureMessage">message.send-as-email.failure</param>
    <evaluator negate="true">org.alfresco.training.evaluator.doclib.action.isEmailed</evaluator>
</action>    

Note here how you can negate the outcome of the evaluation. Which means that in this case we want to show the Send As Email document library action in the UI if an email has not been sent.

Deployment - App Server tomcat/shared/classes/alfresco/web-extension/custom-slingshot-application-context.xml - the Spring Bean definition goes into this file

Custom evaluator implementations in Java does not lend themselves to be manually deployed into the application server. Use a Share AMP [114] project instead.

Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/share-jar-slingshot-application-context.xml - the Spring Bean definition goes into this file
  • aio/share-jar/src/main/java/{custom package path} - the Java implementation of the evaluator goes into this directory
More Information
  • See the Rating Extension Point for example evaluator [115]
  • Predefined evaluators for Document Library [116]
  • Configuring Evaluators for the Document Library [67]
  • Extension Module Deployment and Evaluators [117]
Tutorials
  • Creating a Custom Evaluator [118]
  • Selecting an Evaluator for an Extension Module [119]
  • Sub-Component Evalution [120]
  • Improving Sub-Component Evalution [121]
Alfresco Developer Blogs
  • Sub component evaluations [122]
Parent topic: Share Extension Points [2]

Site presets

A site preset contains the initial configuration for a Share site, such as the site Dashboard layout.
Extension Point Site Presets
Support Status Full Support [10]
Architecture Information Share Architecture [8]
Description

When you access a site the Dashboard layout, with the dashlets in different columns, is determined by the Site Preset that has been set for the site type. It looks something like this for the Collaboration site type:

<?xml version='1.0' encoding='UTF-8'?>
              <presets>
              <!-- Well known preset used to generate the default Collaboration Site dashboard -->
              <preset id="site-dashboard">
              <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>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 Site Dashboard</title>
              <title-id>page.siteDashboard.title</title-id>
              <description>Collaboration site's dashboard page</description>
              <description-id>page.siteDashboard.description</description-id>
              <template-instance>dashboard-2-columns-wide-right</template-instance>
              <authentication>user</authentication>
              <properties>
              <sitePages>[{"pageId":"documentlibrary"}]</sitePages>
              </properties>
              </page>
              </pages>
              </preset>
              ...    

Here is some Surf component configuration again. If you are not up to speed on Surf and the Share architecture, then read the Architecture section [8]. The first two component definitions sets the title and navigation bar for the site. The rest of the component definitions sets the different dashlets that should be displayed on the Dashboard. The last pages section is used to include the Dashboard page with layout and the different site pages.

This site preset looks like this:

The default preset configuration for Share is specified in the tomcat/webapps/share/WEB-INF/classes/alfresco/site-data/presets/presets.xml file.

When a new custom page has been added to Share it is not automatically visible in the navigation bar after a site has bee created. To have a custom page be part of every new site that is created you can override the site preset for the site type.

Here is an example of how to add a custom page to a site:

<preset id="site-dashboard">
              <components>         
              ...
              </components>
              <pages>
              <page id="site/${siteid}/dashboard">
              <title>Collaboration Site Dashboard</title>
              <title-id>page.siteDashboard.title</title-id>
              <description>Collaboration site's dashboard page</description>
              <description-id>page.siteDashboard.description</description-id>
              <template-instance>dashboard-2-columns-wide-right</template-instance>
              <authentication>user</authentication>
              <properties>
              <sitePages>[{"pageId":"documentlibrary"},{"pageId":"helloworld"}]</sitePages>
              </properties>
              </page>
              </pages>
              </preset>

In this case we have added a custom page called helloworld to the site, and this page will be directly accessible from inside the site.

Note: When you have set up a site preset, or a user preset, and it has been used by a site or a user. Then those site presets are stored in the database for the site and user. So you cannot change by redefining presets.xml.

Deployment - App Server tomcat/shared/classes/alfresco/web-extension/site-data/presets/presets.xml (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59] aio/share-jar/src/main/resources/alfresco/web-extension/site-data/presets/presets.xml
More Information  
Tutorials
  • Adding a new Preset [123] - this tutorial is not really about site presets, but it starts off by setting up a new user preset.
Alfresco Developer Blogs
  • Create and Edit Site Dialog [124]
Parent topic: Share Extension Points [2]

Share Themes

The Share web application comes with a number of themes that can be used to set the look and feel of the application. It is also possible to create your own custom UI themes.
Extension Point Share Themes
Support Status Full Support [113]
Architecture Information Share Architecture [8]
Description

The look and feel of the Share user interface is determined by what theme that is currently active, it provides the default CSS and image assets used across all pages. There are number of themes available out-of-the-box:

  • Light Theme (Default)
  • Google Docs
  • Green
  • Blue
  • High Contrast Black
  • Yellow
The default theme can be changed via the Share Admin Tools:

You set the theme for the whole Share UI, but you can also configure a theme per site.

It is possible to create a new custom theme based on one of these existing themes. The way to do that is to start by copying one of the themes under alfresco/tomcat/webapps/share/themes directory to a new directory under share-amp/src/main/amp/web/themes. Then update it with a new Theme ID and define the new Theme for Surf by adding a file to the alfresco/web-extension/site-data/themes directory. This XML file looks something like this:

<?xml version='1.0' encoding='UTF-8'?>
<theme>
    <title>My Red Theme</title>
    <title-id>theme.redTheme</title-id>
</theme>   

When a custom theme has been created based on an existing theme it is time to configure the look and feel for it. This involves two steps as the Share UI contains both Aikau components and YUI components, and they are styled in different ways. See the tutorials section for more information on this.

Important: If you are upgrading to a newer Alfresco Community Edition version, and you are using a custom theme, then it is important to make sure that whatever out-of-the-box theme your custom theme is based on (such as Green Theme) has not changed between versions. For example, upgrading from version 5.0 to 5.1 will mean that all the out-of-the-box themes will have an extra images/logo-enterprise.png file. So if you upgrade to a newer version you will also have to upgrade your custom theme to match.
Deployment - App Server It's not recommended to manually install a custom Theme directly into the application server and the exploded Share WAR.

It would mean copying all the theme resource files into tomcat/webapps/share/themes, which means the files would be gone after a re-deployment or upgrade. Use a Share AMP [114] project instead.

Deployment All-in-One SDK project [59]
  • aio/share-jar/src/main/resources/META-INF/themes - the theme's resource files goes here, pretty much the stuff you copy from alfresco/tomcat/webapps/share/themes
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/themes - the theme XML file goes here
More Information
  • Introduction to Share themes [125]
Tutorials
  • Adding a custom Share Theme [93]
Developer Blogs
  • Share Header Colour Customization [126] - Customizing the Share header via LESS variable override
  • Why Alfresco 5.0.d will be a game changer for UI development [127] - Aikau is from now on in its own JAR
  • Install new Share theme via JAR file [128]
    Note: This tutorial does not include information about how to style Aikau components, such as the header.
Parent topic: Share Extension Points [2]

Document Library

The Document Library page has several extension points that can be used to customize its behavior, such as actions.
Extension Point Document Library
Support Status Full Support [10]
Architecture Information Share Architecture [8]
Description

The Document Library in Share is probably the most comprehensive Surf page (note that it has not yet been converted to Aikau) in the whole application. You will have a number of extension points available so you can customize according to customer requirements.

The following list describes the different Document Library sub-extension points:

  • Actions - The document library page has lots of actions that you can use to manipulate the content you are looking at, whether it is a file or folder, such as Download. It is also possible to add your own actions so you can process content in a domain specific way.
  • Indicators - A content item in the Document library can have zero or more so called indicators, they can be used to denote certain states of the content, such as a file has been emailed. You can add your own indicators, they would typically be used together with an action.
  • Metadata templates - Whenever you view a list of folders or files in the Browse view there is a small number of metadata properties displayed. You can define custom metadata templates to control what metadata is displayed for a specific content type.
  • Views - When you are browsing the Document Library it is possible to select how to view it. By default the Detailed View will be active, but you can also select from the following views: "Simple", "Gallery", "Filmstrip", "Table", "Audio", and "Media". If none of these views fit your needs it is possible to define custom views.

Most of these sub-extension points are actually part of the Share Configuration [9] extension point as they are applied via XML configuration in share-config-custom.xml. It is really just the Actions sub-extension point that will involve coding.

The following picture shows an example of how the Document Actions looks like in the UI:

If you are viewing a Folder instead of a file you will be looking at Folder Actions.

There are a number of ways in which these Document Library actions can be implemented. The following picture illustrates:

So an action can be implemented as a client side JavaScript function that calls a repository action or a web script via AJAX. And an action can also just link directly to an existing Share Page. And finally, it is possible to have an action link to an external page.

Next picture shows you the views:

The Detailed View is currently active and the drop down to the right shows you the other available views. An indicator looks like this for a file:

In this case a workflow was started, and there is a little icon indicating that this file is part of a workflow. The default metadata template for files looks like this:

Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml (Untouched by re-depolyments and upgrades)
  • The following locations are inside the exploded Share WAR, so not recommended, use a Share AMP [114] SDK project instead:
  • tomcat/webapps/share/components/documentlibrary/actions - DocLib Action JavaScript implementation and icon go here
  • tomcat/webapps/share/components/documentlibrary/indicators - status indicators icons go here
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/META-INF/share-config-custom.xml - configuration for actions, indicators, views etc
  • Or even better, put all the configuration in a Surf Extension Module:
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions/doclib-actions-extension-modules.xml - configuration for actions, indicators, views etc
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/components/documentlibrary - DocLib Action JavaScript implementation code
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/components/documentlibrary/actions - DocLib Action icons go here
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/components/documentlibrary/indicators - Status Indicators icons go here
More Information
  • See the Rating Extension Point for example DocLib action, form, and evaluator [115]
  • The Site Document Library [129]
  • Extending the Alfresco Share Document Library [130]
Sample Code
  • Follow links to Tutorials below, they each have links to source code
Tutorials
  • Adding a new Document Library action [131]
  • Adding a menu item to the "Create..." menu in DocLib [132]
  • Override and extension examples [67]
  • Customizing document library views [68]
  • Configure aspect visibility [80]
  • Jeff Potts Alfresco Developer Series: Adding Repo and DocLib actions [133] - a very thorough walk-through of how to develop Repository Actions and Document Library actions, a must read.
Developer Blogs
  • Share Document Library Extensions in v4.0 [134]
Parent topic: Share Extension Points [2]

Surf Extension Modules

This section covers the Surf Extension Modules.
Extension Point Surf Extension Modules
Architecture Information Share Architecture [8].
Description

Surf Extension Modules are the main tool to use when adding, updating, or hiding content in the Share User Interface (UI). They can be deployed and un-deployed during runtime. A module is defined in XML and stored in the site-data/extensions directory.

Working with these extension modules assume a certain knowledge of the Surf UI development framework. Make sure that you have read through the Share Architecture [8] section and the Surf deep dive [135] section.

This section covers the following:
  • Introduction to Surf Extension Modules
  • Module deployment
  • Module dependencies
  • Module configuration
Deployment - App Server tomcat/shared/classes/alfresco/web-extension/site-data/extensions (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59]. aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions
More Information For a comprehensive introduction to Extension Modules see this page [136], which also compares Share configuration with Extension Modules.
Tutorials
  • Adding content to a Surf page [137]
  • Removing/hiding content from a Surf page [138]
  • Conditionally controlling rendering of content on Surf page [120]
  • Customizing (Web Script Properties) the footer text for a Surf page [139]
  • Customizing (Web Script Controller) the WebView dashlet on the Dashboard page [123]
  • Customizing (Web Script Template) the footer text for a Surf page [140]
  • Extend an out-of-the-box Surf Widget (YUI) [141]
  • Add a new menu item to "Create..." menu in DocLib [132]
  • Customizing the Share Header Style (Aikau) [142]
  • Adding JS packages (Aikau) [143]
  • Introduction to Surf Extension Modules [144] This information introduces you to the Spring Surf extension modules, which are the preferred way of customizing many of the Alfresco Share user interface features.
  • Module deployment and evaluators [145] You can deploy Surf Extension Modules and change their behavior using evaluators.
  • Module dependencies [146] Dependencies such as additional CSS and JavaScript code can be included in a Surf Extension Module.
  • Module dynamic configuration [147] You can dynamically configure modules.
Parent topic: Share Extension Points [2]

Introduction to Surf Extension Modules

This information introduces you to the Spring Surf extension modules, which are the preferred way of customizing many of the Alfresco Share user interface features.

If you look in the tomcat/webapps/share/WEB-INF/classes/alfresco directory of your Alfresco Community Edition installation, then you’ll notice a number of files ending with -config.xml, such as for example share-config.xml. These files contain configuration that is loaded into a Spring bean when Alfresco Community Edition starts and is accessed by Share code to dictate many different aspects of its behavior.

To customize this configuration you would not normally change these files directly, but instead make your changes in the tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml file, which overrides the default configuration in the exploded Share webapp. Changes to web-extension/share-config-custom.xml can be done at runtime and take effect after a Share Web Script Refresh. However, there is no way from the UI to disable or enable configuration, you need file system access. There is also no easy and straight forward way to split up the configuration in different units, name them, and version them.

Further on, with the web-extension/share-config-custom.xml file it is not possible to work with Surf pages and components. You can for example not add a component to a page, update a component, or hide a component.

This is where Spring Surf Extension Modules comes into the picture, they enable dynamic control of the Share configuration at runtime from the user interface. Extension Modules also give you full control over a Surf Page in that you can add, update, and remove components that it is made up of. These modules can be deployed and un-deployed without restarting the server. Note however that installing a new module requires a server restart.

Extension modules makes it more straight forward to organize, name, version, and manage your configuration. Instead of having everything in one long share-config-custom.xml file, configuration can now be named and kept in different modules so it is easy for an Administrator to deploy and un-deploy different configuration settings at runtime.

Each module is processed for every request that comes into the Share web application to determine what configuration that should be applied. If a module should only be processed for certain requests, then evaluators [13] can be used to determine when a module should be processed. This is an improvement to how the share-config-custom.xml configuration works, which is applied to all requests and you cannot add any evaluators.

Extension modules are defined in XML files that are stored under the tomcat/shared/classes/alfresco/web-extension/site-data/extensions directory.

Now, look at how to use an extension module to implement a Share customization. The following sample customization hides the External User Invite functionality in a site. The following picture illustrates the component to hide in the Invite Users page:

To hide this component, find out the region-id, source-id, and scope so it can be used when defining the extension module. If you are not up to speed on Surf see Spring Surf deep dive [135]. You can find this information by using a tool called SurfBug (more info on how to enable this tool [148]). When this tool is enabled, after refreshing the page, red lines will show up and mark the different components on the page:

Clicking the component area brings up an information window with all the Surf data that we need:

When we know the Surf component information it is easy to define an extension module that target the "...Add External Users" component:
<extension>
    <modules>
        <module>
            <id>Hide Add External Users</id>
            <auto-deploy>true</auto-deploy>
            <components>
                <component>
                    <region-id>addemail</region-id>
                    <source-id>invite</source-id>
                    <scope>template</scope>
                    <sub-components>
                        <sub-component id="default">
                            <evaluations>
                                <evaluation id="disable-add-external-users">
                                    <render>false</render>
                                </evaluation>
                            </evaluations>
                        </sub-component>
                    </sub-components>
                </component>
            </components>
        </module>
    </modules>
</extension>    
For more information about this module configuration see controlling rendering of components [138].
Besides manipulating the components of a Web Page you can also do the following with Surf Extension Modules:
<extension>
    <modules>
        <module>
            <id>Override Document Library</id>
            <auto-deploy>true</auto-deploy>
            <configurations>
                <config evaluator="string-compare" condition="DocumentLibrary">
                    <create-content>
                         <content id="plain-text" mimetype="text/plain" label="Create an Acme Document" itemid="acme:doc"/>
                    </create-content>
                </config>
Here we are including a Document Library configuration in the same way we would do it in the tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml file (this specific configuration adds a new menu item called Create an Acme Document under the Create... button in the toolbar in the Document Library). The next example shows how you can override a Web Script implementation:
<extension>
    <modules>
        <module>
            <id>Override Document Library</id>
            <auto-deploy>true</auto-deploy>
            <customizations>
                <customization>
                    <targetPackageRoot>org.alfresco.components.documentlibrary</targetPackageRoot>
                    <sourcePackageRoot>org.alfresco.training.components.documentlibrary.customization</sourcePackageRoot>
So in this case we are saying that the Surf Web Script implementations located in the org.alfresco.components.documentlibrary package, which is a standard Web Script location, will be overridden by custom Web Script files located in the org.alfresco.training.components.documentlibrary.customization package. Finally it is also possible to bring in extra web resource via an extension module:
<extension>
    <modules>
        <module>
            <id>Override Document Library</id>
            <auto-deploy>true</auto-deploy>
            <customizations>
                <customization>
                    <targetPackageRoot>org.acme</targetPackageRoot>
                    <dependencies>
                        <css>/res/demo/dependencies/styles.css</css>
                        <js>/res/demo/dependencies/script.js</js>
                    </dependencies>

The following table compares share-config-custom.xml with Surf Extension modules:

Task share-config-custom.xml? Surf Extension Module?
Override default configuration in tomcat/webapps/share/WEB-INF/classes/*-config.xml files YES YES
Add, Update, and Hide components on a Surf Page NO YES
Override Spring Surf Web Scripts NO YES
Load Web Resources (CSS, JS) YES YES
Deploy and Un-Deploy configuration from UI NO YES
Group configuration into named packages NO YES
Keep different versions of a configuration package NO YES
Parent topic: Surf Extension Modules [41]

Module deployment and evaluators

You can deploy Surf Extension Modules and change their behavior using evaluators.

Surf Extension Modules can be deployed using the facility provided at http://localhost:8080/share/page/modules/deploy. Available modules are listed and can be deployed by selecting the module, clicking Add, and then clicking Apply Changes.

Once a deployed module is selected, an evaluator can be selected, and its properties set, using the same interface, as shown in the following screen capture:

The mechanism shown allows the default module operation to be overridden.

The functionality of a deployed module might not be applicable to every request, so it is possible to associate an evaluator with a module. The evaluator runs to evaluate whether or not the request is applicable to the module. Surf will automatically apply the default evaluator on each request. When you deploy the module you have the option of using the default evaluator, or selecting another, which might be a custom written evaluator.

The default module evaluator is configured in the Spring application context with the id default.extensibility.evaluator. This maps to the class org.springframework.extensions.surf.extensibility.impl.ApproveAllModulesEvaluator which will always evaluate to true.

Another evaluator available as standard has the bean id of config.approval.evaluator. If you select this as the evaluator when deploying a module you will see that it asks for a single property with the key apply which determines whether or not the target module gets applied. If you set the value of apply to true then the module will always be applied, if you set it to anything else then the Module will never be applied.

Attention: If you make changes to the evaluator and its properties on the Module Deployment page in Share, it is important to click Update to update the module configuration and then Apply Changes to persist these changes.

It is important to note that many evaluators are never configured from this user interface. Instead they are included in the component definition, with no human interaction needed. The evaluator will instead use data from the repository, or somewhere else, to determine if the component should be rendered or not.

Here is an example of a search instructions component that is added to the search page. It has an evaluator defined, so when the module is deployed via the UI there is no need to configure an evaluator manually:

<extension>
    <modules>
        <module>
            <id>Custom Search Instructions</id>
            <components>
                <component>
                    <region-id>search</region-id>
                    <source-id>search</source-id>
                    <scope>page</scope>
                    <sub-components>
                        <sub-component id="custom-search-instructions" index="25">
                            <url>/alfresco/training/search/instructions</url>
                            <evaluations>
                                <evaluation id="HideIfDisplaySearchInstructionsIsFalse">
                                    <evaluators>
                                        <evaluator type="org.alfresco.training.hideSearchInstructions"></evaluator>
                                    </evaluators>
                                    <render>false</render>
                                </evaluation>
                            </evaluations>
                        </sub-component>
                    </sub-components>
                </component>
            </components>
        </module>
    </modules>
</extension>
  • Auto-deploying modules [149] Surf Extension Modules can be deployed automatically.
Parent topic: Surf Extension Modules [41]

Auto-deploying modules

Surf Extension Modules can be deployed automatically.

While it is possible to deploy Surf Extension Modules manually using the user interface [135], it is also possible to have them deployed automatically after they have been installed and the server is started. Automatic deployment can happen in two ways, either all modules are deployed automatically, or they are deployed automatically if the module requests it. This can be summarized as:

  1. All modules are automatically deployed
  2. All modules are manually deployed
  3. All modules are manually deployed, except for those modules that request to be deployed automatically. This is the default configuration.

In the file tomcat/webapps/share/WEB-INF/classes/alfresco/share-config.xml of a standard Alfresco install, you will find the following XML:

   
   <config evaluator="string-compare" condition="WebFramework">
      <web-framework>
         <module-deployment>
            <!-- Allow extension modules with <auto-deploy> set to true to be automatically deployed -->
            <mode>manual</mode>
            <enable-auto-deploy-modules>true</enable-auto-deploy-modules>
         </module-deployment>
         <use-checksum-dependencies>true</use-checksum-dependencies>
         <generate-css-data-images>true</generate-css-data-images>
      </web-framework>
   </config>   
   
  

This sets the configuration so that modules are manually deployed unless they request to be automatically deployed.

A Surf Extension Module can configure itself to deploy automatically by using the following configuration:

   
<auto-deploy>true</auto-deploy>   
   
  

This would be located in the module's configuration, for example:

   
<extension>
	<modules>
		<module>
			<id>New Content Module</id>
                     <auto-deploy>true</auto-deploy>   
			<components>
				<component>
					<region-id>footer</region-id>
					<source-id>global</source-id>
					<scope>global</scope>
					<sub-components>
						<sub-component id="New_Content" index="25">
							<url>/tutorials/new-content</url>
						</sub-component>
					</sub-components>
				</component>
			</components>
		</module>
...   
   
  
Parent topic: Module deployment and evaluators [145]

Module dependencies

Dependencies such as additional CSS and JavaScript code can be included in a Surf Extension Module.

Surf Extension Modules can include dependencies such as CSS and JavaScript files. This content is then linked to from the <head> element of the targeted web script.

An example is given here:

<module>
   <id>Add dependencies</id>
   <customizations>
      <customization>
         <targetPackageRoot>org.acme</targetPackageRoot>
         <dependencies>
            <css>/res/demo/dependencies/styles.css</css>
            <js>/res/demo/dependencies/script.js</js>
         </dependencies>
      </customization>
   </customizations>
</module>          
         

A target package is specified and when a web script declared at that package is invoked, then the dependencies will be included as imports into the <head>> element of that page.

Parent topic: Surf Extension Modules [41]

Module dynamic configuration

You can dynamically configure modules.

Alfresco Share uses the Surf configuration service extensively to control its behavior. This is usually achieved through the files ending with the suffix "-config.xml" in the webapps/share/WEB-INF/classes/alfresco directory. Changes to these configuration files will not be picked up until next server restart. However, it is possible to change configuration of a Surf Extension Module by changing the module configuration file, and redeploying the module.

The following example demonstrates a module that replaces the Document Library's Flash enablement configuration for any site with the URL noflash:

<extension>
   <modules>
      <module>
         <id>Site_Conditional_Flash</id>
         <description>Applies config based on site id</description>
         <evaluator type="site.module.evaluator">
            <params>
               <sites>noflash</sites>
               <sitePresets>.*</sitePresets>
            </params>
         </evaluator>
         <configurations>
            <config evaluator="string-compare" condition="DocumentLibrary" replace="true">
               <file-upload>
                  <adobe-flash-enabled>false</adobe-flash-enabled>
                  <in-memory-limit>262144000</in-memory-limit>
                  <maximum-file-size-limit>0</maximum-file-size-limit>
               </file-upload>
            </config>
         </configurations>
      </module>
   </modules>
</extension>         
      

The original configuration can be found in webapps/share/WEB-INF/classes/alfresco/share-documentlibrary-config.xml:


      <!--
         File upload configuration
      -->
      <file-upload>
         <!--
            Adobe Flash???
            In certain environments, an HTTP request originating from Flash cannot be authenticated using an existing session.
            See: http://bugs.adobe.com/jira/browse/FP-4830
            For these cases, it is useful to disable the Flash-based uploader for Share Document Libraries.
         -->
         <adobe-flash-enabled>true</adobe-flash-enabled>
         
         <!--
            In order to support drag-and-drop file upload a browser must be able to support the HTML5 drag-and-drop events, however
            if the browser does not support the FormData type (that allows streamed multipart file uploads) then all files need to be
            loaded into the browser's memory before being uploaded to the server. In order to prevent potential memory related errors,
            a limit is set for the sum of all file sizes being uploaded in a single operation (specified in bytes).
            As of April 2011, the only known browser that requires this restriction is Firefox 3.6.
          -->
         <in-memory-limit>262144000</in-memory-limit>
         <!--
            The maximum number of bytes per file that Share will allow to be uploaded.
            A value of 0 means that any size is allowed.
         -->
         <maximum-file-size-limit>0</maximum-file-size-limit>
      </file-upload>

         
      
Important: Note that when replacing configuration (with the replace attribute), it is important to preserve any of the original configuration (from the -config.xml file) you want to retain. In this case the memory limit and upload file size have been retained.

If replace is not used, then the configurations are "added" sequentially. For example:

   
<adobe-flash-enabled>true</adobe-flash-enabled>
<in-memory-limit>262144000</in-memory-limit>
<maximum-file-size-limit>0</maximum-file-size-limit>
<adobe-flash-enabled>false</adobe-flash-enabled>
<in-memory-limit>262144000</in-memory-limit>
<maximum-file-size-limit>0</maximum-file-size-limit>

In this case the second <adobe-flash-enabled> element would be ignored as only the first occurrence would be used.

Parent topic: Surf Extension Modules [41]

Surf web scripts

When you look under the covers of the Share web application you will notice that most of the functionality is implemented as Surf Web Scripts. This is true for both Pages and Dashlets.
Extension Point Surf Web Scripts
Architecture Information
  • Share Architecture [8]
  • Presentation Web Scripts (Surf) vs Data Web Scripts (Repository) [150]
Description Web Scripts are just another name for a REST-based API [151]. Could also be referred to as Web Services. They are stateless and scale extremely well. Surf Web Scripts are defined in XML, JavaScript, and FreeMarker files and stored under alfresco/web-extension/site-webscripts. Surf Web Scripts are referred to as Presentation Web Scripts as they usually return HTML, or some other UI markup.

The simplest Web Script you can write consist of a descriptor and a template. The descriptor will tell you what URL that should be used to invoke the Web Script. And the template is used to assemble the output returned from the Web Script. This kind of Web Script is very static to its nature and will always return the exact same content. Most Web Scripts also include a controller that is used to dynamically assemble a map of data that is then processed by the template to produce the final output. The data that the controller produces could come from anywhere as the controller can be implemented in both JavaScript and Java. Any content from the repository that should be included is however fetched via a repository web script (also called a Data web script), as the Surf Web Script controller does not have direct access to the repository (that is, it does not have access to root objects such as companyhome).

The following picture illustrates how a Presentation web script and a Data web script work together to generate the user interface (this is how most of the Share user interface is generated):

As we can see in the above figure a Surf Web Script is not called directly from the browser (with http://localhost:8080/share/service/...) but instead indirectly via either a Surf Page [20] or a Surf Dashlet [21]. Surf web scripts are also used when constructing Aikau Pages [24] and Aikau Dashlets [25].

Note: For a Surf application such as Alfresco Share, or an application created from the Aikau archetype, the authentication is handled at the page level. And any Web Script components or Aikau pages that run within that context will be authenticated. A Share dashlet is already running within an authenticated page so will also be authenticated. On the other hand, if you call a Surf Web Script URL directly in a browser address bar there is no authentication (that is, no context). You would need to have a JSESSIONID that is already authenticated (that is, by the page). Surf ties the given JSESSIONID to the TICKET that is stored in the session for that user for that connector (alfresco, alfresco-api). When a client-side library on an authenticated page makes an XHR call to a /service URL it will be passing the JSESSIONID automatically.

The JavaScript controller can fetch content from different remote sources, such as the repository and Web Services on the Internet. To do this the controller uses a special root object called remote [152], which can be used to authenticate and connect to a remote service.

To connect to a Remote Service on the Internet the controller will look something like this:
// Most services will require an API Key
// You can get one for the openweathermap API here: http://openweathermap.org/appid
var apiKey = "Put your API Key here.....";
// Get weather for London in JSON structure
var londonWeatherURL = "http://api.openweathermap.org/data/2.5/weather?q=London,uk&APPID=" + apiKey;
var connector = remote.connect("http");
var JSONString = connector.get(londonWeatherURL);

// create json object from data
var londonWeatherJSON = jsonUtils.toObject(JSONString);
model.weather = londonWeatherJSON["weather"];  
Web Script controller file names follow a naming convention: <web script id>.<http method>.js, the above controller could be stored in a file called londonweather.get.js.

Note the use of the http connector [152] when communicating with external Web Services. Now, to create a Web Script you also need a descriptor, which is defined in XML and looks something like this:

<webscript>
   <shortname>London Weather</shortname>
   <description>Simple Surf Web Script fetching London weather information</description>
   <family>Share</family>
   <url>/tutorial/london-weather</url>
</webscript>   
Web Script descriptor file names follow a naming convention: <web script id>.<http method>.desc.xml, the above descriptor should be stored in a file called londonweather.get.desc.xml to link it to the controller.

The template for a Web Script is defined in FreeMarker and looks something like this:

<div>
<#if weather?exists>
    <h1>London Weather Today</h1>
<h2>${weather[0].description}</h2>
<#else>
    Could not access weather information.
</#if>
</div>
Web Script template file names follow a naming convention: <web script id>.<http method>.<format>.ftl, the above template should be stored in a file called londonweather.get.html.ftl to link it to the descriptor.

The London weather web script does not need any authentication with Alfresco Community Edition so it could actually be called directly from the browser with the http://localhost:8080/share/service/tutorial/london-weather URL, and we should see a response similar to:

London Weather Today

light rain

It is more likely though that we would use this Web Scrip as a basis for a Surf Dashlet [21].

If we instead want to fetch and present data from the repository, we will most likely call an out-of-the-box repository web script, although it is common to implement and use your own repository web scripts. To connect and call an Alfresco repository web script (that is, Data web script) you will do something like this:

var filterValue = args.filter;
if (filterValue == null) {
   filterValue = "";
}
var connector = remote.connect("alfresco");
var peopleJSONString = connector.get("/api/people?filter=" + filterValue);
var peopleJSON = jsonUtils.toObject(peopleJSONString);

model.people = peopleJSON["people"];
model.filterValue = filterValue;  

For this controller to successfully pass on authentication information when making the repository web script call it need to be called in context of a Surf Page [20] or a Surf Dashlet [21]. Note the use of the alfresco connector [152] when communicating with a repository, This connector assumes that you will call the older deprecated v0 REST API [153] (http://localhost:8080/alfresco/service). We use it here as the feature of searching for people is not yet available in the v1 REST API.

We should use the v1 REST API [154] as much as possible, which requires a different connector called alfresco-api. Here is an example controller that uses the v1 API to get all the sites in the Repository:

var siteJSON = {}
var connector = remote.connect("alfresco-api");
var result = connector.get("/-default-/public/alfresco/versions/1/sites");
if (result.status.code == status.STATUS_OK) {
    var siteJSON = jsonUtils.toObject(result);
}

model.sites = siteJSON["list"]["entries"];
Can then be displayed via the following template example:
<#if sites?? && (sites?size > 0)>
<h1>Search Result:</h1>
    Found ${sites?size} sites.
    <#list sites as s>
        <br/>
        <a href="${url.context}/page/site/${s.entry.id}">${s.entry.title}</a>
    </#list>
<#else>
    Did not find any sites.
</#if>
The v1 REST API is actually split up into two different APIs depending on what we want to do. If we want to manage files and folders then we will have to use the CMIS API. And when we want to access Alfresco specific content, such as sites, then we need to use the Alfresco Community Edition REST API. Both of these are accessible via the alfresco-api connector. As we saw above, the Alfresco v1 REST API is accessible via the /-default-/public/alfresco/versions/1 URL. The CMIS API is accessible via the /-default-/public/cmis/versions/1.1 URL. The following is an example of a Surf Web Script controller that uses the CMIS API to access the top folders and files in the repository:
var connector = remote.connect("alfresco-api");

// Get some stuff via CMIS REST API
var topFolderContentAsJSONString = connector.get("/-default-/public/cmis/versions/1.1/browser/root");

// Query via CMIS REST API
var queryStatement = encodeURIComponent("select * from cmis:document where cmis:name like 'Project%'");
var cmisQuery = "cmisselector=query&q=" + queryStatement + "&searchAllVersions=false&includeAllowableActions=false&includeRelationships=none&skipCount=0&maxItems=10";
var queryResult = connector.get("/-default-/public/cmis/versions/1.1/browser?" + cmisQuery);

var topFolderContentJSON = jsonUtils.toObject(topFolderContentAsJSONString);
var docs = jsonUtils.toObject(queryResult);

model.topContent = topFolderContentJSON["objects"];
model.docs = docs["results"];
This controller puts the top folder content and search result into the model and we can use it in a template as follows:
<#if topContent?? && (topContent?size > 0)>
<h1>Top Folders:</h1>
    Found ${topContent?size} content items in the top folder (/Company Home).
<#list topContent as tc>
        <br/>
${tc.object.properties["cmis:name"].value}
    </#list>
<#else>
    Did not find any content items in the top folder (root).
</#if>

<#if docs?? && (docs?size > 0)>
<h1>Search Result:</h1>
    Found ${docs?size} documents.
<#list docs as d>
        <br/>
${d.properties["cmis:name"].value}
    </#list>
<#else>
    Did not find any documents.
</#if>
Note that we need to use different way of accessing the CMIS properties than the standard . (dot) notation.
Warning: When parsing the JSON response do not use the eval('('+result+')'); function as it is not secure. Instead use jsonUtils.toObject as in the above examples.
Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-webscripts/ (Untouched by re-depolyments and upgrades)
  • tomcat/webapps/share/components/ (when web resources are included you need to put them directly into the exploded webapp, this is NOT recommended. Use a Share AMP [114] project instead)
Deployment All-in-One SDK project [59]
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-webscripts
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/components (when web resources such as CSS and JS are included)
More Information
  • Presentation Tier Web Scripts [155]
  • Where to put your Web Scripts [156] (When trying them out without a build project)
  • Controller Root Objects [157] (Root objects that can be used in the JavaScript controller)
  • Template Root Objects [158] (Root objects that can be used when the main FreeMarker template for a Page is rendered)
  • Component Root Objects [159] (Root objects that can be used when the FreeMarker template is rendered for a Page Component Web Script)
  • Caching approach [160] - HTTP Response caching and Web Scripts (from repository web script section but still applicable)

    Important. There are two types [150] of web scripts, Repository Web Scripts [161] and Surf Web Scripts [155]. When you work with Surf web scripts you have access to different content (root objects [162]) then when using repository web scripts.

Sample Code
  • Custom Surf Pages, Surf Dashlets, and Surf Web Scripts [163]
Tutorials
  • XML Configuration [164] - Additional XML configuration for Web Script (from repository web script section but still applicable)
  • Cache Control [165] - Additional Cache control configuration for Web Script (from repository web script section but still applicable)
  • POST data processing [166] - Additional Cache control configuration for Web Script (from repository web script section but still applicable)
  • Exploring the Root Objects [167]
Alfresco Developer Blogs  
  • Presentation-tier web scripts [168] Web scripts can be written that run in the presentation tier.
Parent topic: Share Extension Points [2]

Presentation-tier web scripts

Web scripts can be written that run in the presentation tier.

Web scripts can be developed that run in the presentation tier. You could have the repository running on one server (say at port 8080) and the presentation tier running on another server (say on port 8081). These two tiers can communicate - for example, Share accesses the repository via the Repository REST API. When running a web script in the presentation tier, the web script has access to numerous root objects that are only available in the presentation tier. Likewise, some root objects that are available to web scripts when running in the repository tier are not available to web scripts running in the presentation tier. For example, objects associated with core repository concepts, such as nodes, are not directly available to web scripts running in the presentation tier.

  • Web script locations [169] Web scripts need to be located on the application server classpath.
  • Root objects [170] Web scripts written to run in the presentation tier have access to presentation-tier root objects, not available in the repository context.
  • Surf tutorials [171] Tutorials demonstrating how to use Surf.
  • FreeMarker extensibility directives in Web Script templates [172] Extensibility directives provide a way of dynamically editing HTML through configuration.
Parent topic: Surf web scripts [42]
Repository-tier web scripts [173]

Web script locations

Web scripts need to be located on the application server classpath.

There are certain locations where it is the convention to locate your web scripts. The normal location when using the Tomcat application server is ./tomcat/shared/classes/alfresco. Within that directory there are a couple of directories you should know about:

  • extension - your repository-tier web scripts will most likely be located here, typically in templates/webscripts. Web scripts are usually organized into packages below this directory, for example org/alfresco/*. You might create a package com/mycompany/* in which you can locate your company's web scripts.
  • web-extension - custom Share configuration can go directly into this directory. There are two important sub-directories in the web-extension directory: site-data and site-webscripts. site-data would contain Surf configuration XML files, such as page definitions, template-instances and components (see the Surf Framework documentation). The site-webscripts directory would contain your presentation tier web scripts, consisting of description files, JavaScript controllers and FreeMarker template files.
Parent topic: Presentation-tier web scripts [168]

Root objects

Web scripts written to run in the presentation tier have access to presentation-tier root objects, not available in the repository context.

When running a web script in the presentation tier, the web script has access to numerous root objects that are only available in the presentation tier. Likewise, some root objects that are available to web scripts when running in the repository tier are not available to web scripts running in the presentation tier. For example, objects associated with core repository concepts, such as nodes, are not directly available to web scripts running in the presentation tier.

Parent topic: Presentation-tier web scripts [168]
Surf root objects [157]
JavaScript repository root objects [174]
FreeMarker template root objects [175]

Surf tutorials

Tutorials demonstrating how to use Surf.

The tutorials use the Alfresco SDK to create a basic Surf project archetype. This provides a foundation on which to build your own Surf-based projects.

  • Creating a Surf project [176] A Surf project can be created most conveniently using the Alfresco SDK 2.0.
  • Exploring root objects [167] When running in the presentation tier, there are a number of root objects available to your Surf project.
Parent topic: Presentation-tier web scripts [168]

Creating a Surf project

A Surf project can be created most conveniently using the Alfresco SDK 2.0.

The Alfresco SDK, based on Maven, provides a simple way to create a fresh Surf project that you can work with and extend.

You need to have Maven installed. The version required is 3.2.2+. As Maven is a Java program, you will also need to have Java installed.
Surf projects require a number of configuration files and boiler plate code. The project directory hierarchy for a typical Surf project can also be quite daunting. To alleviate this problem, it is recommended that you create a Surf project using the Alfresco SDK. This will create a project structure for you, and populate all the required configuration files for you. Each of the files in the archetype are clearly documented. The Surf project archetype provides a convenient starting point for your own Surf projects.
  1. In the terminal run the following command:

    mvn archetype:generate -DarchetypeCatalog=https://artifacts.alfresco.com/nexus/content/groups/public-snapshots/archetype-catalog.xml

  2. Choose the Surf project archetype (spring-surf-archetype).
  3. When prompted for groupId enter com.alfresco.tutorials, or other suitable package name you would prefer.
  4. When prompted for artifactId enter a suitable project name such as simple-surf-project.
  5. If prompted for a packageId accept the default.
  6. The project will be created.
  7. Change into the directory created for you based on the artifactId. In this case it will be simple-surf-project.
  8. You can now build the project with the following command:

                            
        mvn install                    
                            
                        
  9. You can now run the project with the following command:

                            
        mvn jetty:run                    
                            
                        
  10. Once the server has booted up you can point your web browser at http://localhost:8080/ to see the Surf web site.
  11. You can type a URL such as the following to further test the web site: http://localhost:8080/page/home/welcome/tony

You can now explore the project more conveniently by importing the project into an IDE such as Eclipse.

  1. In Eclipse, select File, Import, and then select Existing Maven Project.
  2. Browse to the simple-surf-project directory.
  3. Click Finish to import the project into Eclipse.

    Now that the project is imported into Eclipse, you can use the Package Explorer to explore the directory hierarchy and files. Note that all the basic configuration files you need are present and heavily commented. You can use this project as the starting point for your own projects.

Parent topic: Surf tutorials [171]

Exploring root objects

When running in the presentation tier, there are a number of root objects available to your Surf project.
This tutorial builds on the project created in the previous tutorial [177].
There are a number of root objects exposed to your JavaScript code and FreeMarker templates in your Surf project. This tutorial shows how you can explore what's available. All of the Surf root objects are documented in the Surf API Reference for web scripts [178].
  1. In the Eclipse Package Explorer, expand out your simple-surf-project to find the file src/main/webapp/WEB-INF/webscripts/home/body.get.html.ftl. This is the FreeMarker template whose output is injected into the body of the page found at: http://localhost:8080/simple-surf-project/page/home.
  2. Load the file into your editor.
  3. Change the code to be as follows:

                            
    <#-- This is an example of how to request a CSS dependency. The resource request will
         be made in the <head> element of the page (because this is where the <@outputCSS>
         directive has been placed. A checksum suffix generated from the file contents 
         will be appended to the request so that the browser can cache it indefinitely.
         The group attribute is used when dependencies are aggregated together into a single
         request. -->
    
    <#-- APB
    <@link href="${url.context}/res/css/body.css" group="default"/>
    -->
    
    <div class="body">
        <!-- The body class defined in the "css/body.css" file sets a background image to 
             provide an example of how images referenced from within CSS files are encoded
             when the "generate-css-data-images" configuration option is enabled (see the
             "WEB-INF/surf.xml" file). -->
             
             <h2>Root Object Test</h2>
             
    		 <hr/>
             <p>theme: ${theme}</p>
             <p>locale: ${locale}</p>
                      
    		 <hr/>
    		 <p>Page root object:</p>		 
             <p>page.url.url: ${page.url.url}</p>
             <p>page.id: ${page.id}</p>
             <p>page.title: ${page.title}</p>
             
             <hr/>
             <p>context: ${context}</p>
    
    		<hr/>
    		<p>User root object:</p>
    		<p>user.id: ${user.id}</p>
    		<p>user.name: ${user.name}</p>
    		<p>user.fullName: ${user.fullName}</p>
    		<p>user.firstName: ${user.firstName}</p>        
    		 
     		<hr/>
     		<p>List page properties:</p>
            <#assign keys = page.properties?keys/>
            <#list keys as k>
              ${k} => ${page.properties[k]}
            </#list>
             
     		<hr/>
     		<p>List user properties:</p>
            <#assign keys = user.properties?keys/>
            <#list keys as k>
              ${k} => ${user.properties[k]},
            </#list>
             
     		<hr/>
     		<p>List context properties:</p>
     		<table border="2">
            <#assign keys = context.properties?keys/>
            <#list keys as k>
            	<#if context.properties[k]??>
            		<tr>
              		<#if context.properties[k]?is_boolean>
              			<#if context.properties[k]>
              				<td>${k} => TRUE</td>
              			<#else>
              				<td>${k} => FALSE</td>
              			</#if>
              			<br/>
              		<#else>
              			<td>${k} => ${context.properties[k]}</td>
              		</#if>
              		</tr>
              	</#if>
            </#list>
            </table>
    
     		<hr/>
     		<p>List template properties:</p>
     		<table border="2">
            <#assign keys = template.properties?keys/>
            <#list keys as k>
            	<#if template.properties[k]??>
            		<tr>
              		<#if template.properties[k]?is_boolean>
              			<#if template.properties[k]>
              				<td>${k} => TRUE</td>
              			<#else>
              				<td>${k} => FALSE</td>
              			</#if>
              			<br/>
              		<#else>
              			<td>${k} => ${template.properties[k]}</td>
              		</#if>
              		</tr>
              	</#if>
            </#list>
            </table>
    </div>                        
                        

    First, you comment out the code that places a background image using the CSS. This is no longer required. Then code has been added that explores the root objects as documented in the Surf API Reference for web scripts [178].

  4. Make sure you have no instances of Alfresco Community Edition running.
  5. Rebuild your project on the command line using the command mvn clean install.
  6. Run the project by entering the command: mvn jetty:run.

    You can also set up Eclipse so that it is possible to run your Maven project from within Eclipse [179].

  7. In your web browser navigate to http://localhost:8080/simple-surf-project/page/home.

    Information will be displayed about the various root objects.

  8. You can expand your FreeMarker code to include more root objects. Then refresh the page to display the results.
You have seen how to explore some of the root objects.
Parent topic: Surf tutorials [171]

FreeMarker extensibility directives in Web Script templates

Extensibility directives provide a way of dynamically editing HTML through configuration.

Surf has been enhanced so that instead of writing templates directly to the output stream, it writes to an in-memory model and then allows extensions to manipulate that model before the model is flushed to the output stream. The mechanism for updating the model is through the use of new and updated FreeMarker template directives.

Wherever an extensibility directive is used in a base template, it can be manipulated by a corresponding extension file. The currently supported actions include:

  • remove - completely removes the directive contents from the model.

  • replace - replaces the directive contents in the model.

  • before - place contents immediately before the target directive contents.

  • after - place new directive contents immediately after the target directive contents.

Alfresco provides two directives that support extensibility: <@region> and <@markup>.

The <@region> directive was used extensively in previous versions of Alfresco Share to define the Regions into which Components are bound. This implementation has been updated to work with the extensibility model. The <@markup> directive is new and is used to demarcate sections of HTML in a template.

The add content [137] and removing content [138] tutorials relies on there being a Component available that will be bound to a Region in the template.

Using this alternative mechanism makes it possible to add new regions and also completely remove regions to prevent Components from being bound. This is significantly different because it is a much more volatile approach but could be useful in certain circumstances. If you want to remove some content and prevent another module from either restoring or adding to it, then using this technique you can remove it entirely from the model so that it cannot be changed.

Parent topic: Presentation-tier web scripts [168]

Surf Web Script JavaScript Root Objects

A number of JavaScript root objects are available when you are implementing a controller for a Surf Web Script, such as page and remote. Sometimes you might have custom Java code that you want to call from JavaScript controllers, this is possible by adding custom JavaScript root objects.
Extension Point Surf Web Script JavaScript Root Objects
Support Status Full Support [10]
Architecture Information
  • Share Architecture [8]
Description This is implemented as a Repository extension, see Repository JavaScript root objects [180]
Deployment - App Server JavaScript root object implementations does not lend themselves very well to be manually installed in an application server.

Build a Repository JAR instead.

Deployment All-in-One SDK project [59]. Use a Repository JAR.
More Information
  • Spring Surf Root Object Reference [157] (Have a look at what root objects are already there)
Parent topic: Share Extension Points [2]

Surf Pages

The Alfresco Share web application is built up of a main menu from which you can navigate to a number of pages. These pages are implemented with the Surf development framework. However, note that some pages have been converted and implemented with the Aikau development framework, see architecture section.
Extension Point Surf Pages (It is recommended to use Aikau Pages [24] instead)
Architecture Information Share Architecture [8].
Description

Surf pages are the "old school" pages that most of the Share UI is built up around. But more and more pages are converted to Aikau pages [24]. All files involved in defining a Surf page are stored under /site-data and /templates.

Putting together a Surf page involves a lot of objects, such as page, template-instance, component, and so on. This is called the siteData [181] model and the following picture illustrates how these objects play together:

The definition of a Surf page [182] is done in XML and looks like this in a Hello World example:

<?xml version="1.0" encoding="UTF-8"?>
<page>
    <title>Hello World</title>
    <title-id>page.helloworld.title</title-id>
    <description>Hello World Description</description>
    <description-id>page.helloworld.description</description-id>
    <template-instance>helloworldhome-three-column</template-instance>
    <authentication>none</authentication>
</page>   

Page definition file names follow a naming convention: <page-id>.xml, the above page definition could be stored in a file called helloworld.xml under site-data/pages.

The page definition refers to a template instance [183] that links to the physical template, it is defined in XML:

<?xml version="1.0" encoding="UTF-8"?>
<template-instance>
    <template-type>org/alfresco/training/helloworld-1-column</template-type>
</template-instance>   

Template instance definition file names follow a naming convention: <template-instance-id>.xml, the above template instance definition could be stored in a file called helloworld-1-column.xml under site-data/template-instances.

The physical template is defined in FreeMarker and looks something like this:

<#include "/org/alfresco/include/alfresco-template.ftl" />
<@templateHeader>
</@>

<@templateBody>
   <@markup id="alf-hd">
   <div id="alf-hd">
      <@region scope="global" id="share-header" chromeless="true"/>
   </div>
   </@>
   <@markup id="bd">
    <div id="bd">
        <@region id="body" scope="page" />
    </div>
   </@>
</@>

<@templateFooter>
   <@markup id="alf-ft">
   <div id="alf-ft">
      <@region id="footer" scope="global" />
   </div>
   </@>
</@>   

Template files are stored under /templates.

The page is built up of different regions, and each region is defined for a specific scope, such as global or page. If the scope is page then we always need to implement the rendition for the region. This is done via a component [184], which is defined in XML:

<?xml version="1.0" encoding="UTF-8"?>
<component>
    <url>/components/helloworld/body</url>
</component>
Page definition file names follow a naming convention: <scope[global|template|page]>.<region-id>.<[template-instance-id|page-id]>.xml, the above component definition could be stored in a file called page.body.helloworld.xml under site-data/components.

The component just points to a Surf Web Script [18] that should render the HTML for the page region.

A Surf page is processed and generated via the Spring MVC framework. The following picture gives an overview of how it works:

As we can see in the picture, all dynamic content that should go onto the page is fetched indirectly via Surf Web Scripts, which can get the content from either the Alfresco Repository or from some other remote Web Service.

Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-data/pages/ - Page definition
  • tomcat/shared/classes/alfresco/web-extension/site-data/template-instances/ - Template instance pointing to FreeMarker template
  • tomcat/shared/classes/alfresco/web-extension/site-data/components/ - Components fetching content and rendering presentation for a specific region in the physical template
  • tomcat/shared/classes/alfresco/web-extension/templates - FreeMarker template location
(These paths are untouched by re-depolyments and upgrades)

For component Web Script file locations see: Web Script [18] section.

Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/pages/
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/template-instances/
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/components/
  • aio/share-jar/src/main/resources/alfresco/web-extension/templates
For component web script file locations see: Web Script [18] section.
More Information
  • Introduction to Surf Pages [185]) - This page contains a walk-through on how to create a Surf page, and it also has links to a page that shows how to create the same page with Aikau.
  • Spring Surf Framework Guide [135] - Deep dive into the Surf framework
  • Site Date Model Reference [181] - Details around all the model objects such as page, component, and template-instance.
  • Surf root objects [157]
  • Component rendering root objects [159]
  • Template rendering root objects [158]
Sample Code
  • Custom Surf Pages, Surf Dashlets, and Surf Web Scripts [163]
Tutorials
  • Adding a new Surf page. [186]
  • Making a new Surf page the default page. [187]
  • Adding content to a Surf page [137]
  • Removing/hiding content from a Surf page [138]
  • Conditionally controlling rendering of content on Surf page [120]
  • Exploring root objects that can be used in a template. [167]
Developer Blogs
  • Adding a Surf page and making it the landing page [188]
  • Adding a Surf page and making it the login page [189]
  • Checksum dependencies for page resources [190]
  • CSS Data Image Support in pages [191]
  • Template markup [192] There are a number of additional FreeMarker template directives.
Parent topic: Share Extension Points [2]

Template markup

There are a number of additional FreeMarker template directives.

Introduction

In the widget instantiation customization tutorial [141] the documentlist.get.html.ftl and documentlist.get.js files were modified to instantiate a custom JavaScript widget that extends the default Alfresco.DocumentList. This is a fragment from the webview.get.html.ftl file:

<@markup id="widgets">
   <@inlineScript group="dashlets">var editDashletEvent = new YAHOO.util.CustomEvent("onDashletConfigure"); </@> 
   <@createWidgets group="dashlets"/> 
   <@inlineScript group="dashlets"> editDashletEvent.subscribe(webView.onConfigWebViewClick, webView, true); </@> 
</@>

Note that another new FreeMarker directive is being used: <@inlineScript> - this directive is used to demarcate sections of JavaScript to be included on the rendered HTML page but allows the specified script to be moved around the page or into aggregated generated resource files.

Setting Object References

This extra code is required in this web script to create a custom event that is triggered when a user clicks on a button in the title bar. The <@createWidgets> directive is able to pass a reference to the editDashletEvent object by way of a special data structure that can be used when creating the instantiation metadata in the JavaScript controller.

The problem is that when creating the model it is impossible to distinguish between a primitive String and a String that is a reference to a JavaScript variable defined in the FreeMarker template, because the controller has no awareness of that variable (the JavaScript controller is processed before the FreeMarker template).

To work around this problem the webview.get.js controller sets a reference by including the following object to the widget's options metadata object:

eventOnClick: { _alfValue : "editDashletEvent", _alfType: "REFERENCE"},

When the <@createWidgets> directive encounters a JSON object with the attributes _alfValue and _alfType, and only those attributes, it converts that object into a variable reference instead of a String.

Generated JavaScript

The source for the generated page will contain the following:

      <script>//<![CDATA[ var editDashletEvent = new YAHOO.util.CustomEvent("onDashletConfigure"); //]]></script> 
      <script type="text/javascript">//&lt;![CDATA[ var webView=new Alfresco.dashlet.WebView("page_x002e_component-1-2_x002e_site_x007e_site1_x007e_dashboard_x0023_default").setOptions({"webviewTitle":"","webviewURI":"/share/page/webview-default","isDefault":true,"webviewHeight":"","componentId":"page.component-1-2.site~site1~dashboard#default"}).setMessages({"label.noWebPage": "No web page to display.", "dashlet.help": "<p>This dashlet shows the website of your choice. Click the edit icon on the dashlet to change the web address.</p><p>Clicking the dashlet title opens the website in a separate window.</p>", "label.header": "Web View"}); new Alfresco.widget.DashletResizer("page_x002e_component-1-2_x002e_site_x007e_site1_x007e_dashboard_x0023_default", "page.component-1-2.site~site1~dashboard#default"); new Alfresco.widget.DashletTitleBarActions("page_x002e_component-1-2_x002e_site_x007e_site1_x007e_dashboard_x0023_default").setOptions({"actions":[{"eventOnClick":editDashletEvent,"cssClass":"edit","tooltip":"dashlet.edit.tooltip"},{"bubbleOnClick":{"message":"dashlet.help"},"cssClass":"help","tooltip":"dashlet.help.tooltip"}]}); //]]></script> <script type="text/javascript">//<![CDATA[ editDashletEvent.subscribe(webView.onConfigWebViewClick, webView, true); //]]></script> 
   

Contained in the script for the Alfresco.widget.DashletTitleBarActions widget is the following call to the .setOptions() function:

.setOptions({ "actions": [{ "eventOnClick" : editDashletEvent, "cssClass" : "edit", "tooltip" : "dashlet.edit.tooltip" }, { "bubbleOnClick": { "message" : "dashlet.help" }, "cssClass" : "help", "tooltip" : "dashlet.help.tooltip" } ] });

Note that the eventOnClick attribute is being set as an object reference and not a String.

Summary

You have learned how to address the issue of referencing JavaScript objects declared in the FreeMarker template in the widget instantiation metadata, and the reason for the "pre" and "post" <@markup> directives in the new boiler-plate template.

Parent topic: Surf Pages [44]

Surf Dashlets

The Share web application has a special page called Dashboard, which contains windows (think Portlets) of content called dashlets. Currently most of these dashlets are Spring Surf dashlets, but they will eventually be converted to Aikau dashlets.
Extension Point Surf Dashlets (It is recommended to use Aikau Dashlets [25] instead)
Architecture Information Share Architecture [8].
Description

The following picture shows a User Dashboard with a number of Dashlets, such as My Sites and My Tasks:

You can implement your own custom dashlets that can be added to either the User Dashboard or the Site Dashboard.

Creating a Surf dashlet is the same thing as creating a Surf web script. Before continuing read through the Surf Web Scripts section [18].

The controller of the dashlet presentation web script will usually call a Data web script on the repository side to get the content that should be displayed in the dashlet.

Let's look at an example of a custom dashlet, the following picture shows a Member Directory dashlet that can be used to search the User/People directory:

This dashlet is implemented using a presentation web script, which in turn uses a Data web script to get the people matching the Search Filter parameter. The following picture illustrates:

In this case we have a Spring Surf Web Script on the Share side that will, in its controller, call a repository web script (that is Data Web Script) to get a list of person records in JSON format. The controller looks something like this:

// Get args from the Share page URL
var filterValue = page.url.args["filter"];
var connector = remote.connect("alfresco");
var peopleJSONString = connector.get("/api/people?filter=" + filterValue);

// create json object from data
var peopleJSON = jsonUtils.toObject(peopleJSONString);
model.people = peopleJSON["people"];  

The controller makes use of a special root object called remote [152], which is used to connect to a remote service, such as the repository, and get data. The JSON data is returned from a repository web script) (that is, a Data web script), which in its controller uses the public API to fetch person information matching passed in Search Filter (that is, filter).

The repository web script uses a root object called people to search for person info. This root object is Alfresco Community Edition specific and is only available in repository web scripts.

Now, to create a dashlet web script you also need a descriptor, which is defined in XML and looks something like this:

<webscript>
    <shortname>Member Directory</shortname>
    <description>Provide Search of people and display in a list</description>
    <family>user-dashlet</family>
    <url>/components/dashlets/member-directory</url>
</webscript>   

The descriptor looks like any other web script descriptor except the family parameter, which can have the following values:

  • user-dashlet - A web script that implements a dashlet that can be added to a User Dashboard
  • site-dashlet - A web script that implements a dashlet that can be added to a Site Dashboard
  • dashlet - A web script that implements a dashlet that can be added to any Dashboard
The Dashlet UI needs to be implemented in the web script template as follows:
<#-- JavaScript Dependencies
<@markup id="js">
</@>
-->

<#-- Stylesheet Dependencies
<@markup id="css">
</@>
 -->

<#-- Surf Widget creation
<@markup id="widgets">
    <@createWidgets group="dashlets"/>
</@>
-->

<@markup id="html">
    <@uniqueIdDiv>
        <#assign id = args.htmlid?html>
        <#assign dashboardconfig=config.scoped['Dashboard']['dashboard']>

        <div class="dashlet">
            <div class="title">${msg("member.directory.dashletName")}</div>
            <div id="${id}-memberdir" class="body">

                <div class="toolbar">
                    <div class="actions">
                        <form name="input"
                              action="${url.context}/page/user/${context.user.id}/dashboard"
                              method="get">
                        ${msg("member.directory.searchFilter")}: <input type="text" name="filter" />
                            <input type="submit" value="Search" />
                        </form>
                    </div>
                </div>

                <p valign="top">
                    <span>${msg("member.directory.searchResult")}</span>
                    <#list people as p>
                        <br/>
                        <a href="${url.context}/page/user/${p.userName}/profile">
                        ${p.firstName} ${p.lastName} (${p.email})
                        </a>
                    </#list>
                </p>

            </div>
        </div>
    </@>
</@>   

Here we are not using any custom client side JavaScript or CSS. Instead we use a simple HTML only based UI with default out-of-the-box styling. In the markup you will see references to i18n labels such ${msg("member.directory.searchResult")}. These messages are defined in the web script properties file as follows:

member.directory.dashletName=Member Directory
member.directory.searchFilter=Search Filter
member.directory.searchResult=Search Result   
Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-webscripts/ (Untouched by re-depolyments and upgrades)
  • tomcat/webapps/share/components/dashlets/ (when web resources are included you need to put them directly into the exploded webapp, this is NOT recommended.)

Best practice is to put the files in a directory that explains what they are for, such as for example:

tomcat/shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/training/components/dashlets

Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-webscripts/{custom path}
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/components/dashlets (when web resources such as CSS and JS are included)
More Information
  • Share Extras Project [193] - Contains loads of Dashlets, good place to go and look at how different types of Dashlets can be implemented.
Sample Code
  • Custom Surf Pages, Surf Dashlets, and Surf Web Scripts [163]
Tutorials
  • Share Extras Project [193] - Look at the source code for the dashlets in this project
Parent topic: Share Extension Points [2]

Surf Widgets

The Share web application is built up of a main menu, pages, and dashlets. The pages and dashlets are mainly processed on the server side as web scripts. When client side processing is needed in the form of browser JavaScript and CSS then this is contained in Widgets. The Surf Widgets uses the Yahoo UI library as JavaScript framework and widget library. These widgets will eventually be replaced by Aikau Widgets.
Extension Point Surf Widgets
Architecture Information Share Architecture [8].
Description

We are going to look at implementing a custom Surf widget. To do this we will implement a very simple Hello World Dashlet that uses a Surf Widget.

Creating a Surf Dashlet is the same thing as creating a Surf Web Script. Before continuing read through the Surf Web Scripts section [18].

The Dashlet will contain a Button that when clicked shows a message. The click handler and pop-up message will be handled by the Surf widget.

The finished Dashlet will look something like this:

When the user clicks the "Click Me!" button it will display a message in grey that fades away. If you hover over the Dashlet it will show a "?" for help in the toolbar. It will also be possible to resize the Dashlet. The Web Script controller looks like this:

// Dashlet widgets
var widgets = [];

// Main component
widgets.push({
    id: "HelloWorld",
    name: "MyCompany.dashlet.HelloWorld",
    options: {
        componentId: instance.object.id
    }
});

// Resizer
widgets.push({
    id : "DashletResizer",
    name : "Alfresco.widget.DashletResizer",
    initArgs : ["\"" + args.htmlid + "\"","\"" + instance.object.id + "\""],
    useMessages: false
});

// Title bar actions
var actions = [];
actions.push({
    cssClass: "help",
    bubbleOnClick:
    {
        message: msg.get("dashlet.help")
    },
    tooltip: msg.get("dashlet.help.tooltip")
});
widgets.push({
    id : "DashletTitleBarActions",
    name : "Alfresco.widget.DashletTitleBarActions",
    useMessages : false,
    options : {
        actions: actions
    }
});

model.widgets = widgets;   

The controller will put together a list of Spring Surf widgets that we can use. The first one is called MyCompany.dashlet.HelloWorld and this is the custom widget that we need to implement.

Here is the implementation of the Hello World widget:

/**
 * MyCompany root namespace.
 *
 * @namespace MyCompany
 */
if (typeof MyCompany == "undefined" || !MyCompany) {
    var MyCompany = {};
}

/**
 * MyCompany dashlet namespace.
 *
 * @namespace MyCompany.dashlet
 */
if (typeof MyCompany.dashlet == "undefined" || !MyCompany.dashlet) {
    MyCompany.dashlet = {};
}

/**
 * Sample Hello World dashboard component.
 *
 * @namespace MyCompany.dashlet
 * @class MyCompany.dashlet.HelloWorld
 * @author
 */
(function () {
    /**
     * YUI Library aliases
     */
    var Dom = YAHOO.util.Dom,
        Event = YAHOO.util.Event;

    /**
     * Alfresco Slingshot aliases
     */
    var $html = Alfresco.util.encodeHTML,
        $combine = Alfresco.util.combinePaths;


    /**
     * Dashboard HelloWorld constructor.
     *
     * @param {String} htmlId The HTML id of the parent element
     * @return {MyCompany.dashlet.HelloWorld} The new component instance
     * @constructor
     */
    MyCompany.dashlet.HelloWorld = function HelloWorld_constructor(htmlId) {
        return MyCompany.dashlet.HelloWorld.superclass.constructor.call(this, "MyCompany.dashlet.HelloWorld", htmlId);
    };

    /**
     * Extend from Alfresco.component.Base and add class implementation
     */
    YAHOO.extend(MyCompany.dashlet.HelloWorld, Alfresco.component.Base,
        {
            /**
             * Object container for initialization options
             *
             * @property options
             * @type object
             */
            options: {},

            /**
             * Fired by YUI when parent element is available for scripting
             *
             * @method onReady
             */
            onReady: function HelloWorld_onReady() {
                this.widgets.testButton = Alfresco.util.createYUIButton(this, "testButton", this.onButtonClick);
            },

            /**
             * Button click event handler
             *
             * @method onButtonClick
             */
            onButtonClick: function HelloWorld_onButtonClick(e) {
                Alfresco.util.PopupManager.displayMessage(
                    {
                        text: "Button clicked in Hello World Dashlet!"
                    });
            }
        });
})();   

This widget will create a YUI button and attach it to the element in the UI with the testButton id. The UI markup looks like this in the Web Script template:

<#-- JavaScript Dependencies -->
<@markup id="js">
    <@script type="text/javascript" src="${url.context}/res/components/dashlets/helloworld.js" group="dashlets"/>
</@>

<#-- Stylesheet Dependencies
<@markup id="css">
    <@link rel="stylesheet" type="text/css" href="${url.context}/res/components/dashlets/helloworld.css" group="dashlets"/>
</@>
-->

<#-- Widget creation -->
<@markup id="widgets">
    <@createWidgets group="dashlets"/>
</@>

<@markup id="html">
    <@uniqueIdDiv>
        <#assign el = args.htmlid?html>
        <#assign dashboardconfig=config.scoped['Dashboard']['dashboard']>

        <div class="dashlet">
            <div class="title">
            ${msg("hello.world.dashletTitle")}
            </div>
            <div class="body">
                <button id="${el}-testButton">${msg('hello.world.buttonLabel')}</button>
            </div>
        </div>
    </@>
</@>
   

Now, to create a Dashlet Web Script you also need a descriptor, which is defined in XML and looks something like this:

<webscript>
    <shortname>Hello World</shortname>
    <description>Hello World Dashlet</description>
    <family>dashlet</family>
    <url>/components/dashlets/helloworld</url>
</webscript>   

Here we are not using any custom client side CSS. Instead we use out-of-the-box styling. In the markup you will see references to i18n labels such ${msg("hello.world.buttonLabel")}. These messages are defined in the Web Script properties file as follows:

hello.world.dashletTitle=Hello World
hello.world.buttonLabel=Click Me!   
Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-webscripts/ (Untouched by re-depolyments and upgrades)
  • tomcat/webapps/share/components/ (when web resources are included you need to put them directly into the exploded webapp, this is NOT recommended.)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-webscripts/{custom path}
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/components (when web resources such as CSS and JS are included)
More Information
  • Customizing widget instantiation [194]
Sample Code
  • Custom Surf Pages, Surf Dashlets, and Surf Web Scripts [163]
Tutorials
  • Customizing Surf Widget Instantiation [141]
Alfresco Developer Blogs
  • Display pop-up a message every time that a Document Library filter is changed [195]
  • The technique behind customizing Share widgets [196]
  • Referencing JavaScript objects declared in the FreeMarker template in the widget instantiation metadata [197]
  • More on FreeMarker directives affecting widget instantiation [198]
  • Customizing Surf Widget instantiation [199] Use this information to understand the mechanisms behind customizing widget instantiation.
Parent topic: Share Extension Points [2]

Customizing Surf Widget instantiation

Use this information to understand the mechanisms behind customizing widget instantiation.

Introduction

It is possible to customize any part of Share by making changes to the Surf libraries and Share web scripts.

Original implementation

The original implementations of the Share web scripts that instantiated client-side widgets have a common pattern:

  1. The widget is instantiated (sometimes it is assigned to a variable).
  2. The ${args.htmlid} property is almost always passed as a single instantiation argument.
  3. A .setOptions(..) function call is chained to the instantiation call. The argument to this call is a single JavaScript object containing all the options to apply to the widget.
  4. A .setMessages(..) function call is chained to the result of the .setOptions(..) call

The main variables in this process are:

  1. The fully-qualified name of the widget instantiated
  2. The name of the variable that the widget is ultimately assigned to
  3. The options applied to the widget

Not all web scripts are coded this way:

  1. Not all assign the widget to a variable
  2. Not all widgets are instantiated with a String as the sole argument
  3. Not all widgets have additional options applied to them
  4. Not all widgets have messages applied to them

A template JavaScript object that encapsulated all the metadata that represented the instantiation of a single widget and created a custom FreeMarker directive <@createWidgets/> that could process this object structure and output the JavaScript code that would perform the instantiation.

There are many web scripts, most commonly those that create dashlets, that instantiate more than one widget. Therefore any custom directive developed in the future would need to be able to process multiple metadata objects. Due to this the controller always adds the metadata objects to a list in the FreeMarker model.

So for example, if the following objects were constructed and set in the model:

widgets: [
   {
      name: "Alfresco.Widget1",
      assignTo: "w1",
      initArgs: [ "x", "y" ],
      useMessages: true,
      useOptions: true,
      options: {
         option1: "one",
         option2: two
      }
   },
   {
      name: “Alfresco.Widget2”
   },
   {
      name: “Alfresco.Widget3”,
      useOptions: false,
      useMessages: false
   }
]

This would result in the following JavaScript output:

<script type="text/javascript">
   var w1 = new Alfresco.Widget1("w", "y").setMessages(${messages}).setOptions({
   option1: "one",
   option2: "two"
   });
   new Alfresco.Widget2(${args.htmlId}).setMessages(${messages});
   new Alfresco.Widget3(${args.htmlId});
</script>

In the example it has been possible to control exactly how each widget is instantiated:

  • Alfresco.Widget1 has explicitly set all properties.
  • Alfresco.Widget2 has taken all defaults.
  • Alfresco.Widget3 has taken some defaults but has elected not to set options or messages.

Here is a list of the properties that can be set on widget instantiation:

name
The fully qualified name of the JavaScript widget to be instantiated.
assignTo (optional)
The name of the variable to assign to. Used if additional JavaScript is required to access the widget after instantiation. This can then be used in the post-instantiation JavaScript code.
initArgs(optional)
The majority of widgets take just the unique id assigned to the outer <div> element of the HTML fragment, but this can be changed by providing alternative arguments. This is limited to String values.
useMessages (optional: defaults to true)
Indicates that the i18n messages associated with the WebScript should be passed to the widget by the .setMessages() function call.
useOptions (optional: defaults to true)
Indicates that the options object should be passed to the widget by the .setOptions() function call.
options (optional: defaults to the empty object)
An object containing all the options to pass to the widget in the .setOptions() function call.

4.2 style markup

The following code is from documentlist.get.html.ftl and illustrates how the 4.2 markup is used. All of the web script rendered components will adopt this template to introduce greater consistency.

<#include "include/documentlist.lib.ftl" />
<#include "../form/form.dependencies.inc">

<@markup id="css">
   <#-- CSS Dependencies -->
   <@link rel="stylesheet" href="${url.context}/res/components/documentlibrary/documentlist.css" group="documentlibrary"/>
</@>

<@markup id="js">
   <#-- JavaScript Dependencies -->
   <@script type="text/javascript" src="${url.context}/res/components/documentlibrary/documentlist.js" group="documentlibrary"/>
</@>

<@markup id="widgets">
   <@createWidgets group="documentlibrary"/>
</@>

<@uniqueIdDiv>
   <@markup id="html">
      <@documentlistTemplate/>
   </@>
</@>

The template is divided into six separate <@markup> directives:

  1. css declares the CSS files required for the web script
  2. js declares the JavaScript files required for the web script
  3. widgets is used for instantiating all the client-side JavaScript widgets
  4. html defines the HTML fragment that acts as the placeholder for the widget to anchor to.

By introducing a greater number of <@markup> directives into the template we make it easier to make finer-grained changes to the template; for example, to remove, replace or add new dependencies or to modify the HTML fragment.

Directives

There are four directives being used in the templates that replace the <@script> macro that was used in previous versions of Share.

Surf is able to process dependencies added using the *.html.ftl files by virtue of the extensibility model. Whereas before it would process all of the *.head.ftl WebScript files to gather all the required CSS and JavaScript dependencies before generating the page output, the <@script> and <@link> directives add content into previously processed directives. This facility will ultimately allow us to disable this double-pass processing to improve page rendering performance (although at the moment it is still enabled for backwards compatibility).

The <@createWidgets> directive is used to generate all of the JavaScript required to instantiate the client-side widgets defined in the model setup by the web script's controller (documentlist.get.js) which contains the following code:

<import resource="classpath:/alfresco/site-webscripts/org/alfresco/components/documentlibrary/include/documentlist.lib.js">

doclibCommon();

function main()
{

   var documentList = {
      id : "DocumentList",
      name : "Alfresco.DocumentList",
      options : {
         siteId : (page.url.templateArgs.site != null) ? page.url.templateArgs.site : "",
         containerId : template.properties.container != null ? template.properties.container : "documentLibrary",
         rootNode : model.rootNode != null ? model.rootNode : "null",
         usePagination : args.pagination != null ? args.pagination : false,
         sortAscending : model.preferences.sortAscending != null ? model.preferences.sortAscending : true,
         sortField : model.preferences.sortField != null ? model.preferences.sortField : "cm:name",
         showFolders : model.preferences.showFolders != null ? model.preferences.showFolders : true,
         simpleView : model.preferences.simpleView != null ? model.preferences.simpleView : "null",
         viewRendererName : model.preferences.viewRendererName != null ? model.preferences.viewRendererName : "detailed",
         viewRendererNames : model.viewRendererNames != null ? model.viewRendererNames : ["simple", "detailed"],
         highlightFile : page.url.args["file"] != null ? page.url.args["file"] : "",
         replicationUrlMapping : model.replicationUrlMapping != null ? model.replicationUrlMapping : "{}",
         repositoryBrowsing : model.repositoryBrowsing != null,
         useTitle : model.useTitle != null ? model.useTitle : true,
         userIsSiteManager : model.userIsSiteManager != null ? model.userIsSiteManager : false
      }
   };

   if (model.repositoryUrl != null)
   {
      documentList.options.repositoryUrl = model.repositoryUrl;
   }
   
   model.widgets = [documentList];
}

main();

The call to doclibCommon() defined in the documentlist.lib.js library file implements the basic controller setup and the remainder of the code defines the metadata object to instantiate the Alfresco.DocumentList widget.

Summary

Widget instantiation metadata is set in the JavaScript controller and rendered using a custom directive into the FreeMarker template.

Parent topic: Surf Widgets [46]

Aikau Menus

The main menu of Share is implemented with the new Aikau UI development framework. It is possible to customize this menu, so you can navigate to new custom pages for example.
Extension Point Aikau Menus
Support Status Full Support [10]
Architecture Information Share Architecture [8].
Description

The main menu and title in Share is implemented in Aikau, it looks like this:

Adding and removing menu items from the menu is a common task. To do this we use a Surf Extension Module [17]. It will look something like this:

<extension>
  <modules>
    <module>
      <id>Add custom menu item to header</id>
      <version>1.0</version>
      <auto-deploy>true</auto-deploy>  
      <customizations>
        <customization>
          <targetPackageRoot>org.alfresco.share.header</targetPackageRoot>
          <sourcePackageRoot>com.example.header</sourcePackageRoot>
        </customization>
      </customizations>
    </module>
  </modules>
</extension>   

So we are targeting the /share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/share/header/share-header.get.js web wcript controller file, which implements the menu JSON model. Now, if we want to for example add a menu item we need to create a file with the same name and put it in alfresco/web-extension/site-webscripts/com/example/header. The file will look like this:

var headerMenu = widgetUtils.findObject(model.jsonModel, "id", "HEADER_APP_MENU_BAR");
if (headerMenu != null) {
    headerMenu.config.widgets.push({
        id: "HEADER_CUSTOM_PROFILE_LINK",
        name: "alfresco/menus/AlfMenuBarItem",
            config: {
                label: "My profile",
                targetUrl: "user/" + encodeURIComponent(user.name) + "/profile"
            }
    });
}   

This is all that is required to extend an existing JSON model. We're using widgetUtils to find the HEADER_APP_MENU_BAR widget. Once we have it, we simply push a widget into it.

Deployment - App Server (Untouched by re-depolyments and upgrades)
  • tomcat/shared/classes/alfresco/web-extension/site-webscripts/ - put the files that are overriding here
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions - put the Extension Module here.
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-webscripts - put the files that are overriding here
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - put the Extension Module here.
More Information
  • Creating an Aikau page with Menus [200]
  • Aikau Widget Reference [201] - this is the place to look for menu widgets that you can use.
Tutorials
  • Customizing the Admin Tools Menu [202]
  • Customizing the Sites Menu [203]
  • Removing Menu Items [204]
  • Aikau Tutorials on GitHub [205]
Developer Blogs
  • Creating a simple Aikau page with Cascading Menu [206]
  • Extending the Share Main Menu [207]
Parent topic: Share Extension Points [2]

Aikau Pages

The Share web application is built up of a main menu from which you can navigate to a number of pages. These pages are implemented mostly in the Surf development framework. However, a number of pages, such as search, have been converted and implemented with the new Aikau development framework, see architecture section.
Extension Point Aikau Pages
Support Status Full Support [10]
Architecture Information Share Architecture [8].
Description

Aikau pages are the new type of pages that the Share UI will use in the future. Currently only the following pages are implemented in Aikau:

  • Live Search
  • Filtered Search Page
  • Search Management Page
  • Site Management Page
  • Analytics and Reporting Widgets
  • Document List prototype
Implementing an Aikau page is greatly simplified compared to creating a Surf page [20]. The only thing you need to do is implement a Surf Web Script [18] and zero or more Aikau Widgets [26].

The web scripts controller is where all the fun happens. It is where you define your page layout in JSON, including all the Aikau Widgets that make up the page. It will look something like this for a Hello World page:

model.jsonModel = {
    widgets: [
        {
            id: "SET_PAGE_TITLE",
            name: "alfresco/header/SetTitle",
            config: {
                title: "Hello World"
            }
        }
    ]
};   

The template will be very simple and the only thing it does is this:

<@processJsonModel />   

It basically just processes the JSON model that we set-up in the controller. Here we have used an out of the box Aikau widget called alfresco/header/SetTitle, and the only thing left to do is to create the web scripts descriptor:

<webscript>
    <shortname>Hello World</shortname>
    <description>Hello World page definition</description>
    <family>Share</family>
    <url>/helloworld</url>
</webscript>   
Now this is all there is to it. The page can be accessed via the http://localhost:8080/share/page/hdp/ws/helloworld URL. You should see the following page in Share:

An Aikau page is processed and generated via the Spring Surf and Spring MVC framework. The following picture gives an overview of how it works:

As we can see in the picture, all dynamic content that should go onto the page is fetched indirectly via Aikau Widgets, which can get the content from either the repository or from some other remote Web Service. Worth noting here is that we are actually using a Surf Page when we are invoking an Aikau page, in the Hello World example we are used the hybrid dynamic page (hdp) to get the Share header and footer included.

Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-webscripts/ (Untouched by re-depolyments and upgrades)
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions (Untouched by re-depolyments and upgrades)
  • tomcat/webapps/share/js/ (when web resources are included, such as Aikau Widgets, you need to put them directly into the exploded webapp, this is NOT recommended.)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-webscripts - Aikau page web scripts
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Extension modules with Dojo package definitions
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/js/<dojo package> - web resources, such as Aikau Widgets
More Information
  • Introduction to Aikau Pages [208] - This page contains a walk-through on how to create an Aikau page, and it also has links to a page that shows how to create the same page with old-school Surf.
  • Introduction to Aikau [209]
  • Creating an Aikau page with Menus [200]
  • Aikau Widget Reference [201] - this is the place to look for widgets that you can use in your pages.
Tutorials
  • Adding new AMD packages for Aikau Widgets [143]
  • Aikau Tutorials on GitHub [205]
Developer Blogs
  • Creating a simple Aikau page with Cascading Menu [206]
  • Creating a simple Aikau page with a button on it. [210]
  • Aikau background and concepts [211]
  • Deep dive into Dojo, Dijit, and Aikau development [212]
  • Aikau Tutorial [213]
  • Creating Aikau Pages with Menus [214] You can add a page to Share using the new Aikau UI framework.
Parent topic: Share Extension Points [2]

Creating Aikau Pages with Menus

You can add a page to Share using the new Aikau UI framework.

Create a new page

To create a new page in Share using the new capabilities added by recent updates you can now do the following:

Create a new web script (made up of the following files):

  • new-page.get.desc.xml (web script descriptor)
  • new-page.get.js (web script controller)
  • new-page.get.html.ftl (web script template)

Place these files in any path under <tomcat-home>/webapps/share/WEB-INF/classes/alfresco/site-webscript

Add the following content to the web descriptor:

<webscript>
   <shortname>My New Page</shortname>
   <url>/my-new-page</url>
</webscript>

Add the following content to the JavaScript controller:

model.jsonModel = {
   widgets: [{
      name: "alfresco/logo/Logo"
   }]
};

Add the following content to the template file:

<@processJsonModel group="share"/>

In your web browser navigate to: http://<server>:<port>/share/page/dp/ws/my-new-page and you will see the Alfresco logo displayed on the page.

Widget creation configuration

The alfresco/logo/Logo widget declares a number of different CSS rules that allow us to easily change the logo that is rendered. Update the JavaScript controller to be the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/logo/Logo",
      config: {
         logoClasses: "surf-logo-large"
      }
   }]
};

If you make the changes to the source files in the deployed web application you can apply these changes simply by refreshing the web scripts by clicking the Refresh Web Scripts button on the web scripts home page http://<server>:<port>/share/service/index.

When you refresh the page you will now see the Surf logo displayed.

What we have done is to add some instantiation arguments to the alfresco/logo/Logo widget to override the default logoClasses attribute with a different CSS class that a selector was defined in the CSS resource associated with it. In the JSON model the name attribute refers to the name of the widget that you want to instantiate (technically it refers to the Module Identifier or MID) and config attribute is an object passed during instantiation of that widget.

A simple menu bar

To create a menu bar:

   model.jsonModel = {
   widgets: [{
      name: "alfresco/menus/AlfMenuBar",
      config: {
         widgets: [{
               name: "alfresco/menus/AlfMenuBarItem",
               config: {
               label: "One"
            }
            },{
               name: "alfresco/menus/AlfMenuBarItem",
               config: {
               label: "Two"
            }
         }]
      }
   }
]}; 

This creates a basic menu bar with two menu items.

The key thing to note here is the use of the widgets attribute in the config object of the alfresco/menus/AlfMenuBar. Where one widget can be the parent to several child widgets it is always possible for the model for those children to be defined in an array assigned to the widgets attribute. This repeating pattern is one of the many ways in which Surf is able to establish all the dependencies to load onto the page.

Adding a menu list

To make the menu bar a bit more detailed, update the model to be the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/menus/AlfMenuBar",
      config: {
         widgets: [{
            name: "alfresco/menus/AlfMenuBarPopup",
            config: {
               label: "One",
               widgets: [{
                  name: "alfresco/menus/AlfMenuItem",
                  config: {
                     label: "Popup item 1"
               }
         },{
               name: "alfresco/menus/AlfMenuItem",
               config: {
                  label: "Popup item 2"
               }
            }]
         }
      },{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "Two"
            }
         }]
      }
   }
]};

The result displays a menu list.

Adding cascading menus and icons

The first menu item has been converted to a popup menu containing two more menu items (note again the repeating config/widgets/config/widgets pattern). Now add some icons to the menu items, do some grouping and add a cascading menu. Try this model in your JavaScript controller:

model.jsonModel = {
   widgets: [{
      name: "alfresco/menus/AlfMenuBar",
      config: {
         widgets: [
         {
            name: "alfresco/menus/AlfMenuBarPopup",
            config: {
               label: "One",
               widgets: [
               {
                  name: "alfresco/menus/AlfMenuGroup",
                  config: {
                     label: "Group 1",
                     widgets: [{
                        name: "alfresco/menus/AlfMenuItem",
                        config: {
                           label: "Popup item 1",
                           iconClass: "alf-edit-icon"
                        }
                },{
                     name: "alfresco/menus/AlfCascadingMenu",
                     config: {
                        label: "Popup item 2",
                        iconClass: "alf-cog-icon",
                        widgets: [
                        {
                           name: "alfresco/menus/AlfMenuItem",
                           config: {
                              label: "Cascaded menu item 1",
                              iconClass: "alf-leave-icon"
                           }
                     },{
                        name: "alfresco/menus/AlfMenuItem",
                        config: {
                           label: "Cascaded menu item 2",
                           iconClass: "alf-help-icon"
                        }
                     }]
                  }
                 }]
               }
               },{
                  name: "alfresco/menus/AlfMenuGroup",
                  config: {
                     label: "Group 2",
                     iconClass: "alf-logout-icon",
                     widgets: [{
                        name: "alfresco/menus/AlfMenuItem",
                        config: {
                           label: "Popup item 3",
                           iconClass: "alf-profile-icon"
                        }
                     }]
                  }
               }]
            }
         },{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "Two"
            }
         }]
      }
   }
]};

When the web scripts are refreshed and the page is reloaded you will see a page displaying a menu with lists, cascaded menus and icons.

The hybrid page

There is also the availability of the "hybrid" view of the page. By changing the URL to: http://<server>:<port>/share/page/hdp/ws/my-new-page (note the additional "h") you will get the page content rendered between the standard Alfresco Community Edition header and footer.

Summary and next steps

This information has shown that it is possible to develop widgets using JSON models.

One of the main advantages of the new header bar in Share is that you can easily change it without copying and pasting all of the XML that defined it.

Parent topic: Aikau Pages [48]

Aikau dashlets

The Share web application has a special page called Dashboard, which contains windows of content called dashlets. Currently most of these dashlets are Spring Surf dashlets, but they will eventually be converted to Aikau dashlets.
Extension Point Aikau dashlets
Support Status Full Support [10]
Architecture Information Share Architecture [8].
Description

The preferred new way of adding custom Share dashlets is via the new Aikau UI development framework. An Aikau dashlet is defined via a Surf web script in the same way you define an Aikau page. The controller with the JSON model contains one widget referencing a dashlet widget, which in turn can use any other number of Aikau widgets to build up the dashlet content.

Let's say we wanted to implement a simple Hello World dashlet in Aikau that looks like this:

This dashlet contains the following Aikau Widgets [26]: the dashlet itself, two toolbar widgets, a vertical layout widget, a logo widget, and a Hello widget. To implement it start with the Surf Web Script [18].

The web script controller is where we specify what Aikau Widget that is implementing the dashlet, it is called acmedashlet/HelloDashlet in this case. The controller will look something like this for the above Hello World dashlet:

model.jsonModel = {
    rootNodeId: args.htmlid,
    pubSubScope: instance.object.id,
    /* Include Aikau services here, for example :
    services: [
        { name: "alfresco/services/ReportService" },
        { name: "alfresco/services/NavigationService" }
    ],*/
    widgets: [
        {
            id: "HELLO_DASHLET",
            name: "acmedashlets/HelloDashlet"
        }
    ]
};

Note that we can include service widgets here too if they are needed, in our case we are just showing a Hello World message so we don't need any services. The dashlet class HelloDashlet.js lives in a JavaScript package called acmedashlets. So for the AMD loader to actually find this class we need to configure a Surf extension module with this information as follows:

<extension>
    <modules>
        <module>
            <id>add-aikau-dashlet-share - ACME Dashlet and Widgets</id>
            <version>1.0</version>
            <auto-deploy>true</auto-deploy>
            <configurations>
                <config evaluator="string-compare" condition="WebFramework" replace="false">
                    <web-framework>
                        <dojo-pages>
                            <packages>
                                <package name="acmedashlets" location="js/tutorials/dashlets"/>
                                <package name="acmewidgets" location="js/tutorials/dashlets/widgets"/>
                            </packages>
                        </dojo-pages>
                    </web-framework>
                </config>
            </configurations>
        </module>
    </modules>
</extension>

Here we have also configured another package called acmewidgets for any other Aikau widgets that we will use as content in our dashlet. These packages points to physical locations in our produced artifact. So we need to put these widgets at specific paths in our AMP project. For example, the dashlet JavaScript file called HelloDashlet.js need to be located in the share-amp/src/main/amp/web/js/tutorials/dashlets directory.

The Surf web script template will be very simple and the only thing it does is this process the JSON model defined in the controller:

<@markup id="widgets">
    <@processJsonModel group="share-dashlets" rootModule="alfresco/core/Page"/>
</@>

<@markup id="html">
    <div id="${args.htmlid?html}"></div>
</@>

To complete the Surf web script a descriptor is also needed:

<webscript>
    <shortname>Hello Dashlet</shortname>
    <description>A dashlet that displays an Alfresco logo and a Hello message</description>
    <family>dashlet</family>
    <url>/tutorials/dashlets/hello</url>
</webscript>
The web script family property has been set to dashlet, which means that this dashlet can be added to both user dashboards and site dashboards. Now we need to implement the actual Dashlet Widget code, it consists of a JavaScript file and i18n resource file. The dashlet JavaScript file, called HelloDashlet.js in this case, should extends the out-of-the-box alfresco/dashlets/Dashlet widget. Here is an example of how it might look like:
define(["dojo/_base/declare",
        "alfresco/core/Core",
        "alfresco/core/I18nUtils",
        "alfresco/dashlets/Dashlet"],
    function(declare, AlfCore, I18nUtils, Dashlet) {

        return declare([Dashlet], {
            additionalCssClasses: "mediumpad",
            bodyHeight: 200,
            componentId: "component.hello-dashlet",
            i18nScope: "tutorials.dashlets.HelloDashlet",
            i18nRequirements: [{i18nFile: "./i18n/HelloDashlet.properties"}],

            widgetsForTitleBarActions: [
                {
                    id: "HELLO_DASHLET_ACTIONS",
                    name: "alfresco/html/Label",
                    config: {
                        label: "Title-bar actions"
                    }
                }
            ],

            widgetsForToolbar: [
                {
                    id: "HELLO_DASHLET_TOOLBAR",
                    name: "alfresco/html/Label",
                    config: {
                        label: "Toolbar"
                    }
                }
            ],

            widgetsForToolbar2: [
                {
                    id: "HELLO_DASHLET_TOOLBAR2",
                    name: "alfresco/html/Label",
                    config: {
                        label: "Toolbar2"
                    }
                }
            ],

            widgetsForBody: [
                {
                    id: "HELLO_DASHLET_VERTICAL_LAYOUT",
                    name: "alfresco/layout/VerticalWidgets",
                    config: {
                        widgetWidth: 50,
                        widgets: [
                            {
                                id: "HELLO_DASHLET_ALFRESCO_LOGO_WIDGET",
                                name: "alfresco/logo/Logo",
                                config: {
                                    logoClasses: "alfresco-logo-only"
                                }
                            },
                            {
                                id: "HELLO_DASHLET_HELLO_WIDGET",
                                name: "acmewidgets/HelloDashletWidget"
                            }
                        ]
                    }
                }
            ]

        });
    });

There are a number of properties here that are defined in the base dashlet class alfresco/dashlets/Dashlet, they can be used for the following:

  • additionalCssClasses - Add padding to the body: smallpad (4px padding), mediumpad (10px padding - recommended) and largepad (16px padding).
  • bodyHeight - Explicit height in pixels of the dashlet body.
  • componentId - Identifier that will be used to store properties for this dashlet, such as the dashlet height when using the resizer.
  • i18nScope - The i18n scope to use for this dashlet.
  • i18nRequirements - An array of the i18n resource files to use with this dashlet.
  • widgetsForTitleBarActions - The widgets to be acting as title bar actions.
  • widgetsForToolbar - The widgets to be placed in the top toolbar.
  • widgetsForToolbar2 - The widgets to be placed in the second toolbar.
  • widgetsForBody - The widgets to be placed in the body of the dashlet.
As we can see, implementing a dashlet is just a question of mixing and matching different Aikau widgets. In this case we have used almost exclusively out-of-the-box widgets (i.e. alfresco/html/Label, alfresco/layout/VerticalWidgets, and alfresco/logo/Logo), but there is one custom widget called acmewidgets/HelloDashletWidget.

To implement this widget we need to create a JavaScript file called HelloDashletWidget.js and put it in the /js/tutorials/dashlets/widgets directory. The source code for the widget looks like this:

define(["dojo/_base/declare",
    "dijit/_WidgetBase",
    "alfresco/core/Core",
    "dijit/_TemplatedMixin",
    "dojo/text!./templates/HelloDashletWidget.html"
],
function(declare, _Widget, Core, _Templated, template) {
    return declare([_Widget, Core, _Templated], {
        templateString: template,
        i18nRequirements: [ {i18nFile: "./i18n/HelloDashletWidget.properties"} ],
        cssRequirements: [{cssFile:"./css/HelloDashletWidget.css"}],

        buildRendering: function org_tutorials_dashlets_HelloDashletWidget__buildRendering() {
            this.greeting = this.message('hello-label');

            this.inherited(arguments);

        }
    });
});      
This widget is based on an HTML template defined in a file called HelloDashletWidget.html, this file should be placed in the /templates sub-directory:
<div class="hello-dashlet-widget">${greeting}</div>
The dashlet widget uses a property called hello-label that needs to be available in a resource file called HellodashletWidget.properties, which should be located in the /i18n sub-directory:
hello-label=Hello Dashlet!
Finally the widget template uses a CSS style called hello-dashlet-widget that needs to be available in a resource file called HelloDashletWidget.css, located in the /css sub-directory:
.hello-dashlet-widget {
    border: 4px #000000 solid;
    padding: 1em;
    width: 100px;
}      
This widget will be loaded by the Dojo AMD loader as we defined the package for it in the beginning of this description.
Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-webscripts/ (Untouched by re-depolyments and upgrades)
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions (Untouched by re-depolyments and upgrades)
  • tomcat/webapps/share/js/ (when web resources are included, such as Aikau Widgets, you need to put them directly into the exploded webapp, this is NOT recommended.)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-webscripts - Aikau dashlet web scripts
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Extension modules with Dojo package definitions
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/js/<dojo package> - web resources, such as the dashlet Widget and other Aikau Widgets making up the content.
More Information
  • Introduction to Aikau [209]
  • Aikau Widget Reference [201] - this is the place to look for widgets that you can use in your dashlets.
Sample Code
  • Hello World dashlet implementation [215]
Tutorials
  • Adding new AMD packages for Aikau Widgets [143]
  • Aikau Tutorials on GitHub [205]
Alfresco Developer Blogs
  • Aikau background and concepts [211]
  • Deep dive into Dojo, Dijit, and Aikau development. [212]
Parent topic: Share Extension Points [2]

Aikau Widgets

Aikau pages are built up of widgets. There are two types of widgets, presentation widgets and service widgets. These JavaScript widgets are Dojo classes. A widget can have its own CSS, HTML, and Properties.
Extension Point Aikau Widgets
Support Status Full Support [10]
Architecture Information Share Architecture [8].
Description

Aikau menus, pages, and dashlets are all using Aikau widgets to build their user interface. An Aikau Widget contains both JavaScript, HTML, CSS, and resource properties to form a self contained component. Aikau widgets are implemented as Dojo classes and uses classes from the Dijit widget library. Alfresco Share comes with a lot of Aikau widgets out of the box such as for example:

  • Charts
  • Dialogs
  • Document Library Menu, List, Views, Filter, Toolbar
  • Document Pickers, Preview, Upload
  • Editors
  • Event Publish/Subscribe
  • Forms with controls - button, text fields, labels, drop downs, date, pickers, radio, etc
  • Header
  • Layouts - classic window, vertical, horizontal, etc
  • Lists
  • Menu
  • Navigation - links, tree
  • Reports
  • Search
  • Services such as Action Service, Content Service, Navigation Service, Tag Service, etc

For a full list of widgets, and documentation, see Aikau Widget Library [201]. If there is no widget here that fits your needs then you implement your own custom Aikau widget. Let's take an example of a simple page with a custom HelloWorld widget, the page JSON looks like this:

model.jsonModel = {
    widgets: [
        {
            id: "SET_PAGE_TITLE",
            name: "alfresco/header/SetTitle",
            config: {
                title: "Hello World"
            }
        },
        {
            id: "DEMO_SIMPLE_MSG",
            name: "example/widgets/HelloWorldTextWidget"
        }
    ]
};      

Here we got a custom widget called HelloWorldTextWidget in the Dojo AMD package exmaple/widgets. To implement this widget we need to create a JavaScript file called HelloWorldTextWidget.js and put it in the /js/example/widgets directory. The implementation of the widget looks like this:

define(["dojo/_base/declare",
        "dijit/_WidgetBase",
        "alfresco/core/Core",
        "dijit/_TemplatedMixin",
        "dojo/text!./HelloWorldTextWidget.html"
    ],
    function(declare, _Widget, Core, _Templated, template) {
        return declare([_Widget, Core, _Templated], {
            templateString: template,
            i18nRequirements: [ {i18nFile: "./HelloWorldTextWidget.properties"} ],
            cssRequirements: [{cssFile:"./HelloWorldTextWidget.css"}],
            
            buildRendering: function example_widgets_HelloWorldTextWidget__buildRendering() {
                this.helloWorldMsg = this.message('hello.world');
                this.inherited(arguments);

            }
        });
});      
This widget is based on an HTML template defined in a file called HelloWorldTextWidget.html, this file should be placed in the same place as the Widget class:
<div class="helloWorldMsgStyle">${helloWorldMsg}</div>
The widget also uses a property called hello.world that needs to be available in a resource file called HelloWorldTextWidget.properties, it also needs to be located in the same place as the Widget class:
hello.world=This is just a test page. Hello World! (Aikau)
Finally the widget template uses a CSS style called helloWorldMsgStyle that needs to be available in a resource file called HelloWorldTextWidget.css, located in the same place as the Widget class:
.helloWorldMsgStyle {
    border: 1px #000000 solid;
    padding: 1em;
    width: 100px;
    background-color:lightgrey;
}      
Before this widget can be loaded by the Dojo AMD loader the package that it is located in must be registered. This is done via a Surf Extension Module as follows:
<extension>
  <modules>
    <module>
      <id>Example Aikau Widgets</id>
      <version>1.0</version>
      <auto-deploy>true</auto-deploy>
      <configurations>
        <config evaluator="string-compare" condition="WebFramework" replace="false">
          <web-framework>
            <dojo-pages>
              <packages>
                <package name="example" location="js/example"/>
              </packages>
            </dojo-pages>
          </web-framework>
        </config>
      </configurations>
    </module>
  </modules>
</extension>
      
Deployment - App Server tomcat/shared/classes/alfresco/web-extension/site-webscripts/ (Untouched by re-depolyments and upgrades)

Best practice is to put the file in a directory that explains what the file is for, such as for example:

tomcat/shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/training/components/form/controls

Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Extension modules with Dojo package definitions (i.e. the JavaScript packages where the Widgets live)
  • aio/share-jar/src/main/resources/META-INF/resources/share-jar/js/<dojo package> - web resources, such as Aikau Widgets
More Information
  • Introduction to Aikau [209]
  • Aikau Widget Reference [201] - Look here before you start creating widgets, one might exist that does what you want.
Tutorials
  • Adding new AMD packages for Aikau Widgets [143]
  • Aikau Tutorials on GitHub [205]
Developer Blogs
  • Aikau background and concepts [211]
  • Creating custom Aikau Widgets [216]
  • Deep dive into Dojo, Dijit, and Aikau development. [212] - lots of information around creating Aikau widgets
  • Aikau Tutorial [213]
Parent topic: Share Extension Points [2]

Modifying Out-of-the-box Surf Web Scripts

Most of the Share UI functionality can be traced back to a web script in one place or another. Sometimes it is useful to be able to override the controller or template of one of these Out-of-the-box (OOTB) web scripts.
Extension Point Modifying Out-of-the-box Surf Web Scripts
Support Status Full Support [10] via Surf Extension Modules [17]
Architecture Information Share Architecture [8].
Description

The preferred way of modifying out-of-the-box Surf Web Scripts is by using Surf Extension Modules [17] to target the Web Script that should be replaced:

<extension>
    <modules>
        <module>
            <id>Customize a Web Script</id>
            <version>1.0</version>
            <auto-deploy>true</auto-deploy>
            <customizations>
                <customization>
                    <targetPackageRoot>The path to the out-of-the-box Web Script you are overriding, such as org.alfresco.components.dashlets</targetPackageRoot>
                    <sourcePackageRoot>The path to your Web Script customizations, such as org.alfresco.tutorials.customization.webscript.controller</sourcePackageRoot>
                </customization>
            </customizations>
        </module>
    </modules>
</extension>

The Web Script files in your package should have the same names as the original ones that you are overriding. The Extension Modules section has all the details.

Out-of-the-box Surf Web Scripts used to be overridden by putting the modified files under alfresco/web-extension/site-webscripts/org/alfresco/... directory using the exact same path. This approach is no longer needed.

Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions/ (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Store extension modules here
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-webscripts - Your Web Script overrides are stored here under a custom package path
More Information
  • Surf Web Scripts [18]
  • Surf Extension Modules [17]
Tutorials
  • See Surf Extension Modules [17]
Parent topic: Share Extension Points [2]

Modifying Out-of-the-box Surf Pages

Most of the pages in the Share web application are implemented with the Surf UI framework. In many cases it is necessary to modify these pages.
Extension Point Modifying Out-of-the-box Surf Pages
Support Status Full Support [10] via Surf Extension Modules [17]
Architecture Information Share Architecture [8].
Description

The preferred way of modifying Surf pages is by using Surf Extension Modules [17] to target the component that should be replaced or hidden. It is also possible to add components to a page this way. The Extension Modules section has all the details.

Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions/ (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Store extension modules here
More Information
  • Surf Pages [20]
Tutorials
  • See Surf Extension Modules [17]
Developer Blogs
Parent topic: Share Extension Points [2]

Modifying Out-of-the-box Surf dashlets

The Share web application has a special page called Dashboard, which contains windows of content called dashlets. Currently most of these dashlets are Spring Surf dashlets, and it is possible to modify the contents on them.
Extension Point Modifying Out-of-the-box Surf Dashlets
Support Status Full Support [10] via Surf Extension Modules [17]
Architecture Information Share Architecture [8].
Description

The preferred way of modifying Surf dashlets is by using Surf Extension Modules [17] to target the component that should be replaced or hidden. It is also possible to add components to a dashlet this way. The Extension Modules section has all the details.

Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions/ (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Store extension modules here
More Information
  • Surf Dashlets [21]
Tutorials
  • See Surf Extension Modules [17]
Developer Blogs
Parent topic: Share Extension Points [2]

Modifying Out-of-the-box Surf Widgets

The Share web application pages and dashlets are built up of widgets. Sometimes it is necessary to modify these.
Extension Point Modifying Out-of-the-box Surf Widgets
Support Status Full Support [10] via Surf Extension Modules [17]
Architecture Information Share Architecture [8].
Description

The preferred way of modifying Surf widgets is by using Surf Extension Modules [17] to target the component that has the widget that should be replaced or hidden. It is also possible to add widgets to a page this way. The Extension Modules section has all the details.

Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions/ (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Store extension modules here
More Information
  • Surf Widgets [22]
Tutorials
  • See Surf Extension Modules [17]
Developer Blogs
Parent topic: Share Extension Points [2]

Modifying Out-of-the-box Aikau Pages

The Share web application has a number of Aikau pages. These can be modified.
Extension Point Modifying Out-of-the-box Aikau Pages
Support Status Full Support [113] via Surf Extension Modules [17]
Architecture Information Share Architecture [8]
Description The preferred way of modifying Aikau pages is by using Surf Extension Modules [17] to target the Aikau widget that should be replaced or hidden. It is also possible to add widgets to a page this way. The Extension Modules section has all the details.

Now, if we want to modify an existing page we need to grab hold of it in the Web Script controller, this will look like this:

var widget = widgetUtils.findObject(model.jsonModel.widgets, "id", "FCTSRCH_SEARCH_RESULTS_LIST");
if (widget && widget.config && widget.config.widgets) {
   widget.config.widgets.push( {
...   

This is all that is required to extend an existing JSON model. We're using widgetUtils to find the FCTSRCH_SEARCH_RESULTS_LIST widget. Once we have it, we simply push widgets into it.

Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions/ (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59]
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Extension module goes here
More Information
  • Aikau Pages [24]
  • Introduction to Aikau [209]
  • Aikau Widget Reference [217] - this is the place to look for widgets that you can use in your dashlets.
Tutorials
  • Aikau Tutorials on GitHub [205]
  • Adding new AMD packages for Aikau Widgets [143]
Developer Blogs
  • Customizing Search Page [218]
  • Aikau background and concepts [211]
  • Deep dive into Dojo, Dijit, and Aikau development. [212]
Parent topic: Share Extension Points [2]

Modifying Out-of-the-box Aikau dashlets

The Share web application has a special page called Dashboard, which contains windows of content called dashlets. Currently most of these dashlets are Spring Surf dashlets, but they will eventually be converted to Aikau dashlets.
Extension Point Modifying Out-of-the-box Aikau Dashlets
Support Status Full Support [10] via Surf Extension Modules [17]
Architecture Information Share Architecture [8].
Description The preferred way of modifying Aikau dashlets is by using Surf Extension Modules [17] to target the Aikau widget that should be replaced or hidden. It is also possible to add widgets to a dashlet this way. The Extension Modules section has all the details.
Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions/ (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Extension modules go here
More Information
  • Aikau Dashlets [25]
  • Introduction to Aikau [209]
  • Aikau Widget Reference [201] - this is the place to look for widgets that you can use in your dashlets.
Tutorials
  • Aikau Tutorials on GitHub [205]
  • Adding new AMD packages for Aikau Widgets [143]
Developer Blogs
  • Aikau background and concepts [211]
  • Deep dive into Dojo, Dijit, and Aikau development. [212]
Parent topic: Share Extension Points [2]

Modifying Out-of-the-box Aikau Widgets

Every Aikau menu, page, and dashlet is built up of one or more widgets. Sometimes it is necessary to modify out of the box widgets.
Extension Point Modifying Out-of-the-box Aikau Widgets
Support Status Full Support [10] via Surf Extension Modules [17]
Architecture Information Share Architecture [8].
Description The preferred way of modifying Aikau widgets is by using Surf Extension Modules [17] to target the widget that should be replaced or hidden. It is also possible to add widgets this way. The Extension Modules section has all the details.
Deployment - App Server
  • tomcat/shared/classes/alfresco/web-extension/site-data/extensions/ (Untouched by re-depolyments and upgrades)
Deployment All-in-One SDK project [59].
  • aio/share-jar/src/main/resources/alfresco/web-extension/site-data/extensions - Extension modules go here
More Information
  • Aikau Widgets [26]
  • Aikau Widget Reference [201] - this is the place to look for widgets that you can use in your dashlets.
  • Introduction to Aikau [209]
Tutorials
  • Aikau Tutorials on GitHub [205]
  • Adding new AMD packages for Aikau Widgets [143]
Developer Blogs
  • Aikau background and concepts [211]
  • Deep dive into Dojo, Dijit, and Aikau development. [212]
Parent topic: Share Extension Points [2]

Useful Tools

There are a few tools that is useful to know about when developing customizations for the Share web application.

As the Share application is based on the Spring Surf UI development framework it is useful to get up to speed on the SurfBug, which is used to debug and view the different regions and components on a page during runtime. It also very useful to be able to use a JavaScript debugging tool like FireBug.

  • Introducing SurfBug [219] SurfBug is a debugging tool built into Spring Surf that displays a variety of information about the various components on a Surf page.
  • Using Firebug for Browser Debugging [220] Firebug is a debugging tool/plug-in for the Firefox web browser.
Parent topic: Share extensions [7]

Introducing SurfBug

SurfBug is a debugging tool built into Spring Surf that displays a variety of information about the various components on a Surf page. If you are not up to speed on Surf, see Spring Surf deep dive [135].

Introduction

SurfBug is used to identify the different components that make up a Surf page. For each component it provides a visual indication of location on the page, and information about the component, such as file names, properties, IDs, and sub-component details.

In Alfresco Share you can enable or disable SurfBug through the SurfBugStatus web script.
  • To enable SurfBug and make the tool visible, click Enable SurfBug on the http://domain:port/share/page/surfBugStatus page.
  • You can also check or change the SurfBug status in this page.
  • If you are running the Alfresco Community Edition server locally, and are using the default port for Tomcat servers, then the URL would be http://localhost:8080/share/page/surfBugStatus.

Enabling SurfBug and refreshing a page will overlay red boxes on the screen indicating the location of the Components or Sub-Components on the page. When the mouse cursor hovers over a component, the red highlight will change to green to show the currently selected component. The information shown for a component is based upon the Surf application configuration – if the Component interfaces are being fulfilled by the org.springframework.extension.surf.type.AdvancedComponentImpl class (which is the default) then Sub-Component information will be shown. When you click on a highlighted area, a pop up will be displayed that provides information about the Sub-Component.

SurfBug is not guaranteed to show every Sub-Component on the page if the DOM elements for that page have been manipulated in certain ways. For example, you will not see highlights for the Sub-Components that make the pop up panels for site creation, file upload, and so on. Since the highlights are absolutely positioned on the page (to avoid affecting the DOM structure) they are not guaranteed to be in pixel perfect position. However, the approximate position of a highlight and the information contained in its pop up will provide enough information for most needs.

You should not attempt to drive an application's user interface with SurfBug enabled. To avoid doing this, first navigate to the page of interest, then toggle SurfBug on from another tab in your browser. At this point you can refresh the application page and SurfBug highlights will then be displayed. If you need to navigate to another page, disable SurfBug, reload the page to switch off highlights, navigate to the new page of interest, and then re-enable SurfBug.

Note that SurfBug is enabled for the entire application, not just for the user who enables it. SurfBug is intended to be used in development, not production. It requires administrative privileges to invoke, so regular users will not be able to switch it on, but if it gets enabled then every user will see the highlights until it is disabled.

Information Provided

The following table provides a breakdown of the information that SurfBug provides:

Page ID The ID of the Page being displayed
Template ID The ID of the Template being displayed
Template Type Typically this is the path of the FreeMarker template used to render the Surf Template referenced by the Page.
Component ID The ID of the Component that the Sub-Component belongs to.
Component Definition Location The runtime path of the file containing the configuration for the Component
Component Details  
GUID Generated unique id of the component
Region-id The id of the region the template into which the component has been bound
Source-id The id of the object at which the component is defined (this will typically be a Page id, a Template id or will be “global”)
Scope The scope at which the Component has been defined (this will typically be "global", "page" or "template").
URL URL of the component
Custom Properties Any custom properties that have been configured for the component. These are not used by Surf to perform any rendering, but may be used by the Component itself if it is parameterized in any way (this may be the case for Components backed by JSPs, WebScripts or FreeMarker).
Height Height of the component in pixels
Sub-Component Details  
ID The id of the Sub-Component – this is always prefixed by the parent Component id and a "#" indicates the start of Sub-Component's identification
Contributing Paths The runtime paths of all the files that have provided input into this Sub-Component (a Sub-Components property, index and evaluation configuration can all be updated by zero or more extension modules). If no extensions have been applied then this will only contain a single path.
Index The specifically set index of the Sub-Component within the Component. This is the final index after all extensions have been applied. If nothing is shown it means that the default is being used.
Processor The processor that has been used to render the Sub-Component. If this Sub-Component has been generated from legacy configuration then this could be either WebScript, WebTemplate or JSP (or some custom processor) – but AdvancedComponents only currently support web script processors and if the Sub-Component is not legacy generated then this will be blank.
Evaluated URI The URI used to render the Sub-Component. This is the URI that is generated as a result of processing all Evaluations across all extensions – so is not necessarily the value configured in the source configuration file.
Evaluated By This is the id of the first successful Evaluation and therefore the one that returned the “Evaluated URI” field. If this is blank it means that no Evaluations were performed on the Sub-Component.
WebScript Location If the Sub-Component was rendered by a WebScript then this will show the runtime path of the WebScript descriptor file. The other WebScript files (template, controller, etc) will be co-located.
WebScript Details This provides a link to the WebScript information which will be opened in a new tab/window.
Evaluated Properties The properties for the Sub-Component as returned by a successful Evaluation. Properties can be overridden by Evaluations to change how a Sub-Component is rendered.
Extensibility Directives A list of the extensibility directives that have been applied to the Sub-Component.
Parent topic: Useful Tools [3]

Using Firebug for Browser Debugging

Firebug is a debugging tool/plug-in for the Firefox web browser.

Installation

Firefox does not come with Firebug installed by default, you need to install it manually. To do that navigate to this page [221] with Firefox, and click on the +Add to Firefox button.

Introduction

Once the Firebug plug-in has been installed in Firefox we can activate and use it. To activate the plug-in click on the Firebug icon in the Add-ons toolbar in the upper right corner of the browser window. See the following picture:

When Firebug is enabled the browser window will be split into two sections, one for the web page we are working with, and one for the Firebug application. See the following picture:

Firebug will display the Script tab by default so you can directly list all the JavaScript files used by currently loaded web page. The following figure shows some of the scripts loaded by the Dashboard page (the list appears if you click on the dojo.js file name):

Here you will notice that all the Alfresco Community Edition-related JavaScript files have big numbers in their file names. For example, the my-sites.js file is not just called that but instead something like my-sites_0500545812b99c1ed156728185c19b24.js. This is because the Spring Surf framework includes a checksum in the file name so it is more stable during upgrades of the software. For more information about this see the following article [222].

Finding out what Web Script(s) that is behind a page

One common task during Share extension development is to find out what web script that is used for a specific functionality in the user interface, such as file upload.

Instead of guessing for a while it is better to load the page that contains the functionality we are working with, and then inspect the JavaScript files that are loaded. Often you can deduct what web script that is used by looking at the names of the JavaScript files, as they are often similar to the web script file names.

Let's take the file upload as an example, how could we find the web script related to it? Start by navigating to a folder. From the User Dashboard click the Repository menu item in the top level menu. Then click on the Guest Home folder. You should now see the following window:

With Firebug enabled click on the Upload toolbar item. Firebug should now provide the list of script used by the upload page. See the following picture:

In this case I have scrolled down a bit in the script list until I came across JavaScript files called something related to file upload, such as dnd-upload_xxx.js, file-upload_xxx.js, flash-upload_xxx.js, and html-upload_xxx.js.

So if we are using Flash upload then we could start searching the file system for matching web script files. We would search the exploded webapps as follows:
martin@gravitonian:/opt/alfresco501/tomcat$ find . -name "flash-upload*.xml"
   ./webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/upload/flash-upload.get.desc.xml

The thinking here is that most of the functionality is using some client side JavaScript that we can use as entry point when searching. And then we can back track to the related Web Script files. Even better, we can search directly for the JavaScript file name and see if any Web Script file refers to it:

martin@gravitonian:/opt/alfresco501/tomcat$ find . -name "*.ftl"|xargs grep "flash-upload.js"
./webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/upload/flash-upload.get.html.ftl:   <@script type="text/javascript" src="${url.context}/res/components/upload/flash-upload.js" group="upload"/>          

So in this case it is quite clear that the Flash Upload functionality is managed by the flash-upload.get Web Script.

Debugging JavaScript files

Another common task during Share extension development is to be able to debug out-of-the-box and custom JavaScript files.

This is quite easy to do with Firebug:
  1. Load the web page that refers to the JavaScript file.
  2. In the script list select the JavaScript file so the source for it is displayed.
  3. Set breakpoints in the JavaScript code (by clicking to the left of the line number)
  4. Refresh the web page so the debugger stops at first breakpoint
The following is an example of how to debug the file-upload.js file that is used by Share to check if Flash is installed or not, and then uses either the FlashUpload or HtmlUpload component:

In this case the file-upload.js file with the checksum name was selected from the script list. Then a breakpoint was set in the show: function FU_show(config) method. To start debugging we just needed to click on the Upload button in the toolbar. To step over a line use F10, to step into use F11, and to continue to next breakpoint use F8.

An important thing we can see here is that the actual Uploader component used is the DNDUpload (Drag-and-Drop) component, and not the FlashUpload that we might think. So it is important to debug into the source and see what is really going on.

Parent topic: Useful Tools [3]

Tutorials

Tutorials for the different Extension Points that can be used to customize the Share web application. The tutorials have been organized based on what part of the user interface they relate to, such as for example Menu, Pages, Dashboard, and so on.

If you are looking for tutorials for a specific Extension Point, such as Surf Extension Modules, then start from Extension Points [223] and navigate to the tutorials using the Extension Point page.

  • Creating a Share Extension Project with Maven [224] You can create an extension project that you will use throughout the tutorials.
  • Header with Menu and Title [225] Tutorials associated with the Share Header, which contains the menu and title.
  • Pages [226] The following are tutorials for Surf Pages and Aikau Pages.
  • Document Library [227] These are tutorials for the Document Library.
  • Conditional Rendering (Evaluators) [228] Tutorials related to conditional rendering of content and operations. We call this evaluations and implementation of evaluators.
  • Styling [229] Tutorials that deal with styling of the Share user interface. For example, how to create a custom theme.
  • Adding AMD packages via Extension Modules (Aikau) [143] It is possible to add new AMD packages via Extension Modules, rather than having to edit surf.xml.
  • Client Debug Mode [230] Client Debug Mode allows you to debug JavaScript and CSS served to the client.
Parent topic: Share extensions [7]

Creating a Share Extension Project with Maven

You can create an extension project that you will use throughout the tutorials.

There are two different types of projects that you can use to develop extensions for the Share web application. Which one to use depends on the use case. If you are developing a component that should be distributed separately (that is, not part of the Share WAR), then use the Share JAR project type. On the other hand, if you are working for a client and are developing a whole solution, which requires you to work with repository, Alfresco Share, and Search (Solr), then use the so called All-in-One (AIO) project type.

The following list has links to both of the project types (SDK archetypes introduction [231]):

  • Share JAR: Project Structure [232], Generate Project [59]
  • All-in-One: Project Structure [233], Generate Project [59]

When going through the tutorials it might be easiest to use an All-in-One project as it has everything included. If you for example use the JAR project you would also need a repository running somewhere, and it does not support searching.

Parent topic: Tutorials [4]

Header with Menu and Title

Tutorials associated with the Share Header, which contains the menu and title.

The Share Header is fully implemented in Aikau.

  • Customizing the Share Header Style (Aikau) [142]
  • Customizing the Share header menu (Aikau) [234] The Share header menu can be readily customized. For example, you can add or remove menu items.
Parent topic: Tutorials [4]

Customizing the Share Header Style (Aikau)

Name Customize the Share Header Style
Extension Point Surf Extension Modules [41]
Description This tutorial demonstrates how the style used in the Share Header can be changed in an easy way. We will see how the background color, foreground color, and so on. can be customized for the main menu.

This tutorial will also introduce you to the Aikau debug mode so you can find out what widgets are available, and the CSS files that they use. Which means you can find out what LESS variables are available to set custom values for.

The main takeaway from this tutorial is that you can easily customize an existing, or custom, theme by redefining LESS variables. And you can easily find out what Aikau widgets that are used for different components in the UI.
Implementation Steps A simple and effective way to customize the Share header, and other parts of the Share UI that uses Aikau widgets, is to follow these steps:
  1. Enable Aikau Debug mode so you can inspect Aikau pages and widgets
  2. Identify what Aikau widgets that are used to produce the content that should be customized (that is, the Share Header)
  3. Find out what CSS files that are used by the relevant widgets
  4. Inspect the CSS files and find out what LESS variables you can work with
  5. Override an out-of-the-box Theme, or create a custom Theme, by redefining one or more LESS variables
Related Information This tutorial assumes that you are familiar with the Share Header, which contains the main menu and the title. The Header is implemented with the new Aikau development framework and it is possible to customize the CSS files used by the Header widgets via LESS variables.
Source Code Go to code [235]
This tutorial assumes you have created a new SDK All-In-One [59] project.
Tutorial implementation steps:
  1. Identify what Aikau widgets that are used to produce the content that should be customized (that is, the Share Header with the Main Menu).

    For this we use the Developer View in Share. To enable it select the Debug Menu | Toggle Developer View menu item from the top main menu. The Share UI should change so you see red boxes around Aikau widgets:

    Note that it is only the Share Header that has been implemented with Aikau so far, and a few other pages. So the red boxes are only marking content in the Header (Menu and Title), the rest of the Dashboard page, dashlets, footer, and so on. is not implemented in Aikau, and hence not marked. To customize the background color for the main menu we need to click the information icon (i) in the upper right corner of the big black box surrounding the main menu:

    Clicking this (i) icon brings up an information box as follows:

    This tells us that the Aikau widget we are looking for is alfresco/header/Header. To find out what stylesheet (CSS) it uses we can lookup the widget source code online (Aikau is open source). Navigate to the documentation site for Aikau (http://dev.alfresco.com/resource/docs/aikau-jsdoc/), and then search for the widget there. In the source code you should see what CSS file it uses by looking at the cssRequirements: [{cssFile:"./css/Header.css"}] variable.

    We can find out more about this CSS file by looking at the full source code on GitHub [236]. Here we can see a number of CSS classes that control the style of the header, which contains the menu and title:

    .@{alfresco} .alf-home-icon {
       background: url("./images/home.png");
       background-repeat: no-repeat;
       height: 16px;
       width: 16px;
       display: block;
       float: left;
    }
    
    .@{alfresco} .navigation-menu {
       margin-top: 23px;
    }
    
    .@{alfresco} .title-menu {
       margin-top: 23px;
    }
    
    .@{alfresco} .alfresco-header-Header {
       background-color: @header-primary-background-color;
       color: @header-primary-font-color;
       font-family: Open Sans,arial,helvetica,clean,sans-serif;
       padding: 0;
       width: auto;
    }
    
    /* Sets the highlight on the menu bar items in the header bar ONLY */
    .@{alfresco} .alfresco-header-Header .alfresco-menus-AlfMenuBar .dijitMenuPassive .dijitMenuItemHover {
       background-color: @header-hover-background-color;
       color: @header-hover-font-color;
    }
    . . .
    What we should be looking for here are so called LESS [237] variables. These are our way into customizing the Header component style.
    Warning: It might be tempting to override these CSS classes with a custom CSS file, such as this:
    .alfresco-share .alfresco-header-Header {
        background-color: #799212; /* Overriding the black background color */
    }
    This should be avoided as this will make upgrades very difficult and it is not supported. LESS variables and themes are the supported way to change the style of Share.
    Looking at the Header CSS file we can see that there are quite a few LESS variables that we can work with:
    @header-primary-background-color
    @header-primary-font-color
    @header-hover-background-color
    @header-hover-font-color
    @header-focus-background-color
    @header-focus-font-color
    @header-menubar-font-color
    @header-dropdown-menu-font-color
    

    What LESS variables that are available differs slightly between versions of Share, and what Aikau version that is brought in. The best thing you can do is, find out what exact version of Aikau that is being used, for example aikau-1.0.25.2.jar (check tomcat/webapps/share/WEB-INF/lib). Then make sure it is a version that is newer than Aikau version 1.0.18, which was the first version to introduce LESS. You can find the Header.css file in the /META-INF/js/aikau/<aikau version>/alfresco/header/css/ directory in the JAR file, which means that you can check exactly what LESS variables are available for you to use.

  2. Decide if you want to customize an existing theme or use a custom theme.

    Customizing the style of the header can be done either by overriding an existing theme, such as the Green Theme, or by creating a new custom theme. In this exercise we will customize the out-of-the-box Green Theme. If we are just customization LESS variables, and we don't need to change anything else in the Green Theme, then it is enough to just create a Green Theme XML file and override the LESS variables. There is no need to copy over the complete theme directory like when creating a custom theme [238].

  3. Create a new themes directory under the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data directory.

    This is the standard directory in the Spring Surf model for theme files.

  4. Create a new Green Theme XML file called greenTheme.xml in the themes directory.

    This file will contain the overridden LESS variables. It is important that the file name is the same as it is for the Green Theme in the Share webapp, see tomcat/webapps/share/WEB-INF/classes/alfresco/site-data/themes. Otherwise we will not be overriding the Green Theme but instead start creating a custom theme.

  5. Override the required LESS variables.

    Overriding the default values for the LESS variables, which are defined in the defaults.less [239] file in Aikau, is currently done by adding a particular element to the Theme XML file. A Theme is defined by an XML file that lives in the “themes” sub-folder of the client’s Surf configuration folder. In our example the greenTheme.xml file looks like this:

    <?xml version='1.0' encoding='UTF-8'?>
    <theme>
       <title>Green Theme Override</title>
       <title-id>theme.greenTheme</title-id>
       <css-tokens>
          <!-- Aikau related LESS variables (requires Aikau 1.0.18 or newer) -->
          <less-variables>
             @header-background-color: #799212;
             @header-font-color: #ccc;
             @header-hover-background-color: orange;
             @header-hover-font-color: green;
             @header-focus-background-color: yellow;
             @header-focus-font-color: red;
             @header-menubar-font-color: pink;
             @header-dropdown-menu-font-color: purple;
          </less-variables>
       </css-tokens>
    </theme> 
  6. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  7. Now, log in to Share (http://localhost:8080/share) and change them to Green Theme (via the Share Admin Tools). You should then see the main menu with the green background color, pink font color, and other changes:

Parent topic: Header with Menu and Title [225]
Parent topic: Styling [229]

Customizing the Share header menu (Aikau)

The Share header menu can be readily customized. For example, you can add or remove menu items.
In this tutorial you use the All-In-One SDK Project [59]. The tutorial assumes you are using an IDE to edit the Maven project files. You should also be familiar with the Introducing SurfBug [219] topic.
You are going to add several customizations to the Share header menu. The tutorial is split into several parts.
  • Customizing the admin tools menu (Aikau) [202] This tutorial shows you how to customize the Share header admin tools menu item.
  • Customizing the Sites menu (Aikau) [203] This tutorial shows you how to customize the Share header menu sites item.
  • Removing a menu item (Aikau) [204] This tutorial shows you how to remove menu items.
Parent topic: Header with Menu and Title [225]

Customizing the admin tools menu (Aikau)

This tutorial shows you how to customize the Share header admin tools menu item.
In this tutorial you add several customizations to the Share Header Menu. Your customization will be implemented as a Surf extension module [41].
  1. Create a new SDK All-In-One [59] project.
  2. In the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-webscripts folder create a new folder called tutorials

    Note: tutorials is the path to the web script in this case, you can use a different path if required.
  3. In your IDE, navigate to the new tutorials folder and create a new webscript file called share-header.get.js] with the following contents:

    
    // Find the admin menu - it'll only be present if the current user is Admin...
    var adminMenu = widgetUtils.findObject(model.jsonModel, "id", "HEADER_ADMIN_CONSOLE");
    if (adminMenu != null)
    {
       // Change the widget to a menu bar popup
       adminMenu.name = "alfresco/header/AlfMenuBarPopup";
       
       // Remove the targetUrl attribute - this isn't strictly necessary but is "cleaner"
       delete adminMenu.config.targetUrl;
       
       // Add a new "widgets" array to the configuration...
       adminMenu.config.widgets = [
          {   
             name: "alfresco/menus/AlfMenuGroup",
             config: {
                label: "Tools", // I'm not bothering with localisation - but you could get localised values using msg.get("..")
                widgets: [
                   {
                      name: "alfresco/header/AlfMenuItem",
                      config:
                      {
                         label: "Application",
                         targetUrl: "console/admin-console/application"
                      }
                   },
                   {
                      name: "alfresco/header/AlfMenuItem",
                      config:
                      {
                         label: "Category Manager",
                         targetUrl: "console/admin-console/category-manager"
                      }
                   },
                   {
                      name: "alfresco/header/AlfMenuItem",
                      config:
                      {
                         label: "Node Browser",
                         targetUrl: "console/admin-console/node-browser"
                      }
                   },
                   {
                      name: "alfresco/header/AlfMenuItem",
                      config:
                      {
                         label: "Tag Manager",
                         targetUrl: "console/admin-console/tag-management"
                      }
                   }
                ]
             }
          },
          {   
             name: "alfresco/menus/AlfMenuGroup",
             config: {
                label: "File Management",
                widgets: [
                   {
                      name: "alfresco/header/AlfMenuItem",
                      config:
                      {
                         label: "Trashcan",
                         targetUrl: "console/admin-console/trashcan"
                      }
                   }
                ]
             }
          },
          {   
             name: "alfresco/menus/AlfMenuGroup",
             config: {
                label: "Content Publishing", 
                widgets: [
                   {
                      name: "alfresco/header/AlfMenuItem",
                      config:
                      {
                         label: "Channel Manager",
                         targetUrl: "console/admin-console/channel-admin"
                      }
                   }
                ]
             }
          },
          {   
             name: "alfresco/menus/AlfMenuGroup",
             config: {
                label: "Repository", 
                widgets: [
                   {
                      name: "alfresco/header/AlfMenuItem",
                      config:
                      {
                         label: "Replication Jobs",
                         targetUrl: "console/admin-console/replication-jobs"
                      }
                   }
                ]
             }
          },
          {   
             name: "alfresco/menus/AlfMenuGroup",
             config: {
                label: "Users and Groups", 
                widgets: [
                   {
                      name: "alfresco/header/AlfMenuItem",
                      config:
                      {
                         label: "Groups",
                         targetUrl: "console/admin-console/groups"
                      }
                   },
                   {
                      name: "alfresco/header/AlfMenuItem",
                      config:
                      {
                         label: "Users",
                         targetUrl: "console/admin-console/users"
                      }
                   }
                ]
             }
          }
       ];
    }
                            
                        
  4. In your IDE navigate to the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions folder, and create a new extension file called custom-header-extension.xml that contains the following extension definition:

    
    <extension>
      <modules>
        <module>
          <id>Custom Share Header Menu</id>
          <version>1.0</version>
          <customizations>
            <customization>
              <targetPackageRoot>org.alfresco.share.header</targetPackageRoot>
              <sourcePackageRoot>tutorials</sourcePackageRoot>
            </customization>
          </customizations>
        </module>
      </modules>
    </extension>
    
    
  5. To build and run the new Surf extension module see working with an AIO project [240].
  6. Note: The extension module needs to be deployed before it will be visible. In your browser, go to http://localhost:8080/share/service/modules/deploy.
    You see a list of available Modules and a list of deployed Modules.
  7. Select Custom Share Header Menu and click Add to move it into the Deployed Modules list.
  8. Click Apply Changes.

    Note that the Last update time stamp changes. You only need to do this action once as Module Deployment data is saved into the repository.

  9. Now log back in to Share and click on the Admin Tools menu item - you can see it is now a drop-down menu list.
Parent topic: Customizing the Share header menu (Aikau) [234]

Customizing the Sites menu (Aikau)

This tutorial shows you how to customize the Share header menu sites item.
You should complete the previous tutorial [241] before attempting this one.
In this tutorial you will customize the Share main menu so that the Sites menu item includes additional functionality. The Sites menu item is modified so that if a site under Recent Sites is selected, then the individual pages of that site are displayed as additional menu items, allowing you to navigate directly to a specific site page. Individual pages displayed include Site Dashboard, Document Library and Site Members.
  1. Navigate to the aio/aio-share-jar/src/main/resources/META-INF/resources/aio-share-jar/js/tutorials folder.
  2. Create a new file SitesMenu.js in the tutorials folder with the following contents:

                            
     define(["dojo/_base/declare",
            "alfresco/header/AlfSitesMenu",
            "alfresco/core/CoreXhr",
            "dojo/_base/lang",
            "dojo/_base/array",
            "dojo/aspect",
            "dijit/registry",
            "alfresco/menus/AlfMenuGroup",
            "alfresco/header/AlfMenuItem",
            "alfresco/header/AlfCascadingMenu",
            "dojo/dom-style",
            "dijit/popup"], 
            function(declare, AlfSitesMenu, AlfXhr, lang, array, aspect, registry, AlfMenuGroup, AlfMenuItem, AlfCascadingMenu, domStyle, popup) {
       
       return declare([AlfSitesMenu, AlfXhr], {
          
          /**
           * Adds an individual menu item.
           * 
           * @instance
           * @param {object} group The group to add the menu item to
           * @param {object} widget The menu item to add
           * @param {integer} index The index to add the menu item at.
           */
          _addMenuItem: function tutorials_SitesMenu___addMenuItem(group, widget, index) {
             if (group == this.recentGroup)
             {
                // Create a basic group for holding the favourites...
                 var sitePageList = new AlfMenuGroup({
                   widgets: [{
                      name: "alfresco/header/AlfMenuItem",
                      config: {
                         label: "Loading..."
                      }
                   }]
                });
                
                // Create the cascading menu item to popout the favourites list...
                var siteCascade = new AlfCascadingMenu(widget.config);
                
                // Set up the sites cascading popup to asynchronously load the pages upon request...
                siteCascade.popup.onOpen = dojo.hitch(this, "loadSitePages", widget.config.siteShortName, sitePageList);
                
                // Add the list into the cascading menu...
                siteCascade.popup.addChild(sitePageList);
    
                // Add the default menu items...
                group.addChild(siteCascade);
             }
             else
             {
                // If we're not adding a Recent Sites menu item then just default to the normal action
                this.inherited(arguments);
             }
          },
          
          /**
           * This variable will be used to keep track of which sites pages have been loaded. It is initialised
           * to null and populated as page data is loaded.
           * 
           * @instance
           * @type {object}
           * @default null
           */
          _sitePagesLoaded: null,
          
          /**
           * This function is hitched to the each sites cascading menu so that when it is clicked a XHR request is made
           * to retrieve the pages for the site.
           * 
           * @instance
           * @param {string} siteShortName The short name of the site to load the pages for
           * @param {object} sitePageList A reference to the alfresco/menus/AlfMenuGroup widget that the site pages should be added to
           */
          loadSitePages: function tutorials_SitesMenu__loadSitePages(siteShortName, sitePageList) {
             if (this._sitePagesLoaded != null && this._sitePagesLoaded[siteShortName] == true)
             {
                this.alfLog("log", "Site pages already loaded for: " + siteShortName);
             }
             else
             {
                this.alfLog("log", "Loading pages for site: " + siteShortName);
                this.serviceXhr({url : Alfresco.constants.URL_SERVICECONTEXT + "tutorials/site/" + siteShortName,
                                 method: "GET",
                                 siteShortName: siteShortName, // Including the site short name will make it available in the "sitePagesLoaded" callback "originalRequestConfig"
                                 sitePageList: sitePageList,   // ...as will the sitePageList menu group
                                 successCallback: this.sitePagesLoaded,
                                 callbackScope: this});
             }
          },
          
          /**
           * This function is "hitched" from the serviceXhr call in the "loadSitePages" function and handles the response
           * from the asynchronous request to get site pages. It clears the original "Loading..." menu item and adds in
           * each of the site page links.
           * 
           * @instance
           * @param {object} response The response from the request
           * @param {object} originalRequestConfig The configuration passed on the original request
           */
          sitePagesLoaded: function tutorials_SitesMenu__loadSitePages(response, originalRequestConfig) {
             this.alfLog("log", "Site pages data loaded successfully", response);
             
             // Initialise the object that keeps track of which pages have been loaded if it has not
             // previously been initialised...
             if (this._sitePagesLoaded == null)
             {
                this._sitePagesLoaded = {};
             }
             
             // Record that the site pages have been loaded to prevent them from being loaded again...
             this._sitePagesLoaded[originalRequestConfig.siteShortName] = true;
             
             // Check for keyboard access by seeing if the first child is focused...
             var focusFirstChild = (originalRequestConfig.sitePageList && originalRequestConfig.sitePageList.getChildren().length > 0 && originalRequestConfig.sitePageList.getChildren()[0].focused);
             
             // Remove the loading item...
             array.forEach(originalRequestConfig.sitePageList.getChildren(), function(widget, index) {
                originalRequestConfig.sitePageList.removeChild(widget);
             });
             
             // Add the site pages...
             if (response.sitePages && response.sitePages.length > 0)
             {
                array.forEach(response.sitePages, function(sitePage, index) {
                   this.alfLog("log", "Adding site page menu item", sitePage);
                   var item = new AlfMenuItem(sitePage);
                   originalRequestConfig.sitePageList.addChild(item);
                }, this);
             }
             else
             {
                // TODO: Should add some error handling here - but has been left out as Example for Blog post only covers "golden path"
             }
             
             if (focusFirstChild)
             {
                // Focus the first site page...
                originalRequestConfig.sitePageList.focusFirstChild();
             }
          }
       });
    });                       
                            
                        
  3. Edit the file aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-webscripts/tutorials/share-header.get.js, created in the previous tutorial, and add the following code at the end of the file:

                            
       //Find the "Sites" menu...
       var sitesMenu = 
         widgetUtils.findObject(model.jsonModel, "id", "HEADER_SITES_MENU");
       if (sitesMenu != null)
       {
         // Change the widget to our custom menu...
         sitesMenu.name = "tutorials/SitesMenu";
       }                        
                            
                        
  4. Create a new XML file in the folder aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-webscripts/tutorials called site-pages.get.desc.xml, with the following content:

                            
    <webscript>
       <shortname>Get the pages for the requested site</shortname>
       <description>This WebScript is added to allow the tutorials/SitesMenu widget to asynchronously request the page for each site</description>
       <url>/tutorials/site/{shortname}</url>
       <format default="json"></format>
    </webscript>                        
                            
                        
  5. In the same folder create a new JavaScript file called site-pages.get.js, with the following content:

                            
    <import resource="classpath:/alfresco/site-webscripts/org/alfresco/share/imports/share-header.lib.js">
    
    // We need to set up a fake page structure to allow the page-centric imported function to work...                                                                  
    page = { url: { templateArgs: { site: url.templateArgs.shortname}}};
    
    // Create a new object in the model for the pages and add in the default dashboard page...                                                                         
    model.pages = [
       {
          label: msg.get("page.siteDashboard.title"),
          targetUrl: "site/" + url.templateArgs.shortname + "/dashboard"
       }
    ]
    
    // Call the "getSitesPages" function imported from the "share-header.lib.js" file...                                                                               
    // Iterate over the configured pages and add the details to the model...                                                                                           
    var pages = getSitePages();
    if (pages != null)
    {
       for (var i=0; i<pages.length; i++)
       {
          model.pages.push({
             label: (pages[i].sitePageTitle) ? pages[i].sitePageTitle : pages[i].title,
             targetUrl: "site/" + url.templateArgs.shortname + "/" + pages[i].pageUrl
          });
       }
    }
    
    // Finally push in the "site-members" page (the other default page)...                                                                                             
    model.pages.push({
       label: msg.get("page.siteMembers.title"),
       targetUrl: "site/" + url.templateArgs.shortname + "/site-members"
    });
                            
                            
                        
  6. Create a new file in the same folder called site-pages.get.json.ftl, with the following content:

                            
    <#escape x as jsonUtils.encodeJSONString(x)>
    {
       "sitePages":
       [
       <#list pages as page>
          {
             "label": "${page.label}",
             "targetUrl": "${page.targetUrl}"
          }<#if page_has_next>,</#if>
       </#list>
       ]
    }
    </#escape>
                            
                            
                        

    You have now created the three essential pieces of the web script - the JavaScript code, the FreeMarker template and the web script definition file.

  7. To build and run the updated Surf extension module see working with an AIO project [240].
  8. Now log back in to Share and select the Sites menu item. Click a site under Recent Sites. You will see that there are now additional menu items including Site Dashboard, Document Library, and Site Members. Click on one of these menu items to test it. For example, if you click on the Site Members items you will be taken to the Site Members page for that site.
Parent topic: Customizing the Share header menu (Aikau) [234]

Removing a menu item (Aikau)

This tutorial shows you how to remove menu items.
You should complete the previous tutorials [241] before attempting this one.

In this tutorial you customize the Share header menu. In the previous two tutorials you saw how to add menu items and customize them. In this one you learn how to remove menu items. This is demonstrated using two different techniques: using a built-in helper function,widgetUtils.deleteObjectFromArray, and through configuration exposed by the alfresco/header/AlfSitesMenu widget.

In the first part of this tutorial you use the helper function widgetUtils.deleteObjectFromArray to remove menu items. This function takes three arguments:

  1. The object to remove the widget definition from (you can typically just use model.jsonModel)
  2. The attribute to search for (you should use id)
  3. The value to match against the target attribute (the id attribute of the widget definition to remove)

For example, to remove the My Files link from the menu bar you would include the following code in the file share-header.get.js (see the two previous tutorials):

                    
widgetUtils.deleteObjectFromArray(model.jsonModel, "id", "HEADER_MY_FILES");                    
                    
                

Try this out by removing the My Files menu item.

  1. In your IDE edit the file share-header.get.js that you created in the previous tutorials.
  2. At the end of the file add the following code:

            
    widgetUtils.deleteObjectFromArray(model.jsonModel, "id", "HEADER_MY_FILES");        
            
        

    Save the file.

  3. To build and run the updated Surf extension module see working with an AIO project [240].
  4. Log in to Share, you will see that the My Files menu item is no longer present on the main menu.

    In this part of the tutorial you have seen how to use the helper function to remove a menu item. Note your code could have been more complex. For example, to hide MyFiles for users who are not admins you could use the following code:

                        
    if (!user.isAdmin)
    {
      widgetUtils.deleteObjectFromArray(model.jsonModel, "id", "HEADER_MY_FILES");
    }                    
                        
                    

In this part of the tutorial you see how to remove items from the Sites drop-down menu. Although you saw how to customize the Sites menu in the previous tutorial [242], you can also use configuration attributes that allow the easy removal of Site menu items.

These boolean attributes are as follows:

Attribute Description
showCreateSite Controls whether the Create Site menu item is displayed.
showSiteFinder Controls whether the Site Finder menu item is displayed.
showUsefulGroup Controls whether the Useful menu group is displayed. Will override showCreateSite, showSiteFinder and showFavourites.
showRecentSites Controls whether the Recent Sites menu group is displayed.
showFavourites Controls whether the Facvourites menu item and favourite controls (such as add/remove) are displayed.

For example, to hide the Site Finder, you would include the following code in share-header.get.js:

                    
// Find the "Sites" menu...
var sitesMenu = 
  widgetUtils.findObject(model.jsonModel, "id", "HEADER_SITES_MENU");
if (sitesMenu != null)
{
  // Hide the site finder...
  sitesMenu.config.showSiteFinder = false;
}                    
                    
                
  1. In your IDE edit the file share-header.get.js that you created in the previous tutorials.
  2. At the end of the file add the following code:

            
    // Find the "Sites" menu...
    var sitesMenu = 
      widgetUtils.findObject(model.jsonModel, "id", "HEADER_SITES_MENU");
    if (sitesMenu != null)
    {
      // Hide the site finder...
      sitesMenu.config.showSiteFinder = false;
    }        
        

    Save the file.

  3. To build and run the updated Surf extension module see working with an AIO project [240].
  4. Log in to Share. Select the Sites menu item from the main menu bar, you will see that the Site Finder is no longer present on the drop-down menu.
In this tutorial, you used two techniques for removing menu items from the main header menu bar.
Parent topic: Customizing the Share header menu (Aikau) [234]

Pages

The following are tutorials for Surf Pages and Aikau Pages.

  • Adding content to a Surf page [137]
  • Removing content from a Surf page [138]
  • Customizing (web script properties) the footer text for a Surf page [139]
  • Customizing (web script controller) the WebView dashlet on the Dashboard page [123]
  • Customizing (web script template) the footer text for a Surf page [140]
  • Adding a new Surf page to Share [186] This tutorial demonstrates how to add a new page to Alfresco Share.
  • Override Share sign in page [243] This tutorial demonstrates how to override the default Alfresco Share sign in page.
  • Making the new page the default [187] This tutorial demonstrates how to make the new page you created in the previous tutorial the default landing page.
  • Localizing Pages [244] Use this information to localize pages.
Parent topic: Tutorials [4]

Adding content to a Surf page

Name Add Surf Page Content
Extension Point Surf Extension Modules [41]
Description This tutorial demonstrates how to add some extra content to the Footer on each page. The steps you need to take to do this can be applied also when adding content to other parts of a page.
Implementation Steps A simple and effective way to add content to a Surf page is to follow these steps:
  1. Create a Web Script that returns the content to be displayed.
  2. Find a page component that is located where you want to add new content, such as the footer in our case.
  3. Add a new sub-component to the page component, referencing the new Web Script.
  4. Use a Surf Extension module to deploy the new sub-component.
Related Information This tutorial assumes that you are familiar with the Spring Surf development framework. If you are new to it then read up on it here [245] before starting this tutorial. If you have not already done so you should also review the Introducing SurfBug [219] topic as this tool is used in this tutorial.
Source Code Go to code [246]
This tutorial assumes you have created a new SDK All-In-One [59] project..
Tutorial implementation steps:
  1. In the Share JAR project create a new directory as follows for the Web Script: aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-webscripts/org/alfresco/tutorials.
  2. Add a Web Script descriptor file called new-content.get.desc.xml to the /tutorials directory:

    <webscript>
        <shortname>New Page Content</shortname>
        <description>Add new content to a Surf Page</description>
        <url>/tutorials/new-content</url>
        <family>Share Tutorials</family>
    </webscript>
  3. Add a web script template file called new-content.get.html.ftl to the /tutorials directory:

    <div>
        Hello World!
    </div>
  4. Identify which component to add the new sub-component to.

    For this we use the SurfBug [219] tool. Once the tool is activated (from http://localhost:8080/share/page/surfBugStatus) we can identify the component on the page as follows:

    Here we have scrolled to the bottom of the Dashboard page as we want to add our new content to the footer. Then we have clicked on the last component enclosed in red lines. This brings up the above black information box where we can see the region-id, source-id, and scope values that we are looking for.
  5. Add a new Surf Extension Modules file called add-page-content-extension-modules.xml to the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory (note. it is important to give this file a unique name when several Share JARs are installed, otherwise the last one wins):

    <extension>
        <modules>
            <module>
                <id>Add new content to footer</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <components>
                    <component>
                        <region-id>footer</region-id>
                        <source-id>global</source-id>
                        <scope>global</scope>
                        <sub-components>
                            <sub-component id="New_Content" index="25">
                                <url>/tutorials/new-content</url>
                            </sub-component>
                        </sub-components>
                    </component>
                </components>
            </module>
        </modules>
    </extension>

    What we are doing here is adding a new sub-component to the existing component identified by the region-id footer and the source-id global.

    The sub-component url points to the new Web Script that we just created.

    The sub-component's content can be displayed either before or after the default sub-component, which is the one with the id set to default. The position of the content is based on the index attribute value, which is set to 25 in our case. The default sub-component will have an index of 50 so our new sub-component will be displayed before it.

    The id of the new sub-component need to be unique within the main component (i.e. the footer) and the id default is reserved.

    This module will be deployed automatically when the application server is started as we have the auto-deploy property set to true.

  6. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  7. Now, log in to Alfresco Share (http://localhost:8080/share) and you will see the content (Hello World!) from the new web script displayed just above the footer:

    Note: A Surf Extension module like this can be deployed and undeployed during runtime. And this means that an Administrator can control when different customizations should be visible or hidden. This is managed via the Module deployment page that can be found at: http://localhost:8080/share/service/modules/deploy.
Parent topic: Pages [226]

Removing content from a Surf page

Name Remove Surf Page Content
Extension Point Surf Extension Modules [41]
Description This tutorial demonstrates how to prevent content from being rendered in a Surf page. This tutorial also shows how to use evaluations to decide whether content should be rendered or not.
Implementation Steps A simple and effective way to remove content from a Surf page is to follow these steps:
  1. Locate the component that corresponds to the content you want to remove from the page.
  2. Make a note of the region-id, source-id, and scope information for the component.
  3. Override the component definition and add an evaluator that resolves to not rendering the component.
  4. Use a Surf Extension module to deploy the overridden component definition.
Related Information This tutorial assumes that you are familiar with the Spring Surf development framework. If you are new to it then read up on it here [245] before starting this tutorial. If you have not already done so you should also review the Introducing SurfBug [219] topic as this tool is used in this tutorial.
Source Code Go to code [247]
This tutorial assumes you have created a new SDK All-In-One [59] project..
Tutorial implementation steps:
  1. Identify which component that corresponds to the content that should be removed.

    For this we use the SurfBug [219] tool. Once the tool is activated (from http://localhost:8080/share/page/surfBugStatus) we can identify the component on the page as follows:

    In this case we have clicked on the "My Sites" dashlet, which brings up the above black information box where we can see the region-id, source-id, and scope values that we need to be able to hide the dashlet.
  2. Add a new Surf Extension Modules file called remove-page-content-extension-modules.xml to the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory (note. it is important to give this file a unique name when several Share JARs are installed, otherwise the last one wins):

    <extension>
        <modules>
            <module>
                <id>Remove content from Share (hide My Sites)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <components>
                    <component>
                        <region-id>component-1-1</region-id>
                        <source-id>user/{userid}/dashboard</source-id>
                        <scope>page</scope>
                        <sub-components>
                            <sub-component id="default">
                                <evaluations>
                                    <evaluation id="guaranteedToHide">
                                        <render>false</render>
                                    </evaluation>
                                </evaluations>
                            </sub-component>
                        </sub-components>
                    </component>
                </components>
            </module>
        </modules>
    </extension>

    What we are doing here is overriding the default sub-component and putting in a new evaluation for it. The targeted component is identified with the values we got via SurfBug. So we set region-id to component-1-1, source-id to user/{userid}/dashboard, and scope to page. Note that we are changing the source-id from the specific one for Administrators (i.e. user/admin/dashboard) to one that is valid for all user dashboards.

    The sub-component evaluation definition is quite simple, we just give it a unique "bogus" id and set rendering to false. This evaluation definition is guaranteed to hide the component.

    This module will be deployed automatically when the application server is started as we have the auto-deploy property set to true.

  3. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  4. Now, log in to Share (http://localhost:8080/share) and you will no longer see the "My Sites" dashlet on the Dashboard (that is, the "My Tasks" dashlet has taken its place):

    Note: A Surf Extension module like this can be deployed and undeployed during runtime. And this means that an Administrator can control when different customizations should be visible or hidden. This is managed via the Module deployment page that can be found at: http://localhost:8080/share/service/modules/deploy.
  5. Further information

    Some features introduced in this tutorial are explained in more detail in the following sections:

    Parametrized source-id mapping: Every Share user gets their own dashboard page, which enables them to customize the layout to suit their own needs, but each user dashboard is generated from a single preset. In this tutorial you specify user/{userid}/dashboard. Note the use of the userid variable. This allows you to change the appearance of the dashboard for any user, not just the admin user.

    Extending existing sub-components: When the dashboard pages were first created, the concept of sub-components in Surf did not exist. Consequently, if you search through the existing dashboard configuration files you will not find sub-components specified. Surf automatically converts these "legacy" components into the new extensible components containing a single sub-component with the ID "default".

    This allows you to add new content to these legacy components through sub-components, or customize the original content without affecting any new content. In the previous configuration XML, you can change the behaviour of the components through modification of the default sub-component.

    Note that multiple modules can extend the same component, which is why the deployment order of modules is important.

    Sub-component evaluations: Every sub-component can optionally have zero or more evaluations. Each evaluation acts like an AND gate to a series of evaluators where an evaluation is considered successful if no evaluators fail. If an evaluation contains no evaluators, it is still considered to have evaluated successfully because nothing has failed.

    The purpose of an evaluation is to change the behaviour of a sub-component in one of three ways:

    • Change the Web Script that renders the content by specifying a new URL.
    • Change the default properties (and/or provide new properties) that are passed to the Web Script.
    • Control whether or not the sub-component is actually rendered.

    In this example, you are simply overriding the default behaviour of the sub-component to prevent it from rendering by setting the <render> element to have a value of false (this defaults to true) if not defined.

    .
Parent topic: Pages [226]

Customizing (web script properties) the footer text for a Surf page

Name Customize the footer text for a Surf page via web script Properties change.
Extension Point Surf Extension Modules [41]
Description This tutorial demonstrates how to customize the page footer web script i18n properties to change the existing text in the footer. The tutorial walks through how to find the related component and web script.
Implementation Steps It is often necessary to customize different parts of a Surf web script. This tutorial shows you how to customize the i18n properties files for a web script. The approach looks something like this:
  1. Find the page component that corresponds to the content that should be changed.
  2. Identify the web script that is used to deliver the content.
  3. Identify what part of the web script need changing to achieve the customization, in this case the properties file, which contains the existing footer text for Alfresco Community Edition and Alfresco Community Edition.
  4. Create your customized version of the {web script id}.get_{lang}.properties file.
  5. Use a Surf Extension module to define the web script override.
Related Information This tutorial assumes that you are familiar with the Spring Surf development framework. If you are new to it then read up on it here [245] before starting this tutorial. If you have not already done so you should also review the Introducing SurfBug [219] topic as this tool is used in this tutorial.
Source Code Go to code [248]
This tutorial assumes you have created a new SDK All-In-One [59] project..
Tutorial implementation steps:
  1. Identify the web script that delivers the content that should be customized.

    For this we use the SurfBug [219] tool. Once the tool is activated (from http://localhost:8080/share/page/surfBugStatus) we can identify the web script as follows:

    Here we have scrolled to the bottom of the Dashboard page where the footer is located. Then we have clicked on the last component enclosed in red lines and representing the footer. This brings up the above black box that contains information about what web script that is delivering the content for the footer component (and then also the properties). In this case it is the footer.get.* web script in package org.alfresco.components.footer that we need to target. You can also identify the web script via the URL (that is, /components/footer).

  2. In the Share JAR project create a new web script override package org.alfresco.tutorials.customization.footer.i18n.

    The directory path that needs to be created is: aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-webscripts/org/alfresco/tutorials/customization/footer/i18n.

    We can choose any package path we want and then specify it in the Surf Extension Module, we will see this in a bit. However, it is important that we use a package path that will not clash with another Extension Module, deployed by some other JAR.

    For example, if we just used org.alfresco.tutorials.customization.footer and then another JAR was deployed with some other customization to the footer, using the same package path. Then if one extension module is undeployed its customizations will still be picked up if the other module is active. This is because both modules are using the same package path.

  3. Add our version of the web script properties file for English called footer.get_en.properties to the /tutorials/customization/footer/i18n directory:

    label.copyright=This is free software. Copyright Alfresco forever
    label.copyright.enterprise=This is the software you pay for. Copyright Alfresco forever
    To know what the property names are we first lookup the original properties file, which is located in the tomcat/webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/footer directory.
  4. Add a new Surf Extension Modules file called customize-webscript-i18n-props-extension-modules.xml to the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory (note. it is important to give this file a unique name when several Share JARs are installed, otherwise the last one wins):

    <extension>
        <modules>
            <module>
                <id>Customize i18n labels for Footer</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <customizations>
                    <customization>
                        <targetPackageRoot>org.alfresco.components.footer</targetPackageRoot>
                        <sourcePackageRoot>org.alfresco.tutorials.customization.footer.i18n</sourcePackageRoot>
                    </customization>
                </customizations>
            </module>
        </modules>
    </extension>

    This extension module identifies the package with the web script that we want to override by setting the targetPackageRoot property. When we have set what web script to override we use the sourcePackageRoot property to tell Alfresco Community Edition where to pick up the customized web script files.

    This module will be deployed automatically when the application server is started as we have the auto-deploy property set to true.

  5. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  6. Now, log in to Share (http://localhost:8080/share) and you will see the new footer text displayed as follows:

    Note: A Surf Extension module like this can be deployed and undeployed during runtime. And this means that an Administrator can control when different customizations should be visible or hidden. This is managed via the Module deployment page that can be found at: http://localhost:8080/share/service/modules/deploy.
Parent topic: Pages [226]

Customizing (web script controller) the WebView dashlet on the Dashboard page

Name Customizing the WebView dashlet on the Dashboard page
Extension Point Surf Extension Modules [41]
Description This tutorial demonstrates how to customize the WebView dashlet web script controller so the Alfresco Home page is displayed by default. The tutorial walks through how to find the related component and web script.
Implementation Steps It is often necessary to customize different parts of a Surf web script. This tutorial shows you how to customize the controller for a web script. The approach looks something like this:
  1. Find the page component that corresponds to the content that should be changed.
  2. Identify the web script that is used to deliver the content.
  3. Identify what part of the web script need changing to achieve the customization, in this case the controller as we can set the page that should be loaded by default in the model.
  4. Create your version of the {web script id}.get.js file.
  5. Use a Surf Extension module to define the web script override.

Related Information This tutorial assumes that you are familiar with the Spring Surf development framework. If you are new to it then read up on it here [245] before starting this tutorial. If you have not already done so you should also review the Introducing SurfBug [219] topic as this tool is used in this tutorial.
Source Code Go to code [249]
This tutorial assumes you have created a new SDK All-In-One [59] project..
Tutorial implementation steps:
  1. Add the WebView dashlet to the default dashboard for users.

    The WebView dashlet is not part of the default dashboard for users so we need to add it in order to be able to work with it when implementing this customization.

    The easiest way to add a dashlet permanently to the user dashboard is to define a new preset for the dashboard layout with id user-dashboard. Create a new presets directory under the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data directory.

    Now, add a file called presets.xml to the new presets directory:
    <?xml version='1.0' encoding='UTF-8'?>
    <presets>
        <!-- Override wll known preset used to generate the default User dashboard.
             Add the Web View Dashlet so we can check if customization works. -->
        <preset id="user-dashboard">
            <components>
                <!-- title
                <component>
                    <scope>page</scope>
                    <region-id>title</region-id>
                    <source-id>user/${userid}/dashboard</source-id>
                    <url>/components/title/user-dashboard-title</url>
                </component>
                -->
                <!-- dashboard components -->
                <component>
                    <scope>page</scope>
                    <region-id>full-width-dashlet</region-id>
                    <source-id>user/${userid}/dashboard</source-id>
                    <url>/components/dashlets/dynamic-welcome</url>
                    <properties>
                        <dashboardType>user</dashboardType>
                    </properties>
                </component>
                <component>
                    <scope>page</scope>
                    <region-id>component-1-1</region-id>
                    <source-id>user/${userid}/dashboard</source-id>
                    <url>/components/dashlets/my-sites</url>
                </component>
                <component>
                    <scope>page</scope>
                    <region-id>component-1-2</region-id>
                    <source-id>user/${userid}/dashboard</source-id>
                    <url>/components/dashlets/my-tasks</url>
                </component>
                <component>
                    <scope>page</scope>
                    <region-id>component-2-1</region-id>
                    <source-id>user/${userid}/dashboard</source-id>
                    <url>/components/dashlets/my-activities</url>
                </component>
                <component>
                    <scope>page</scope>
                    <region-id>component-2-2</region-id>
                    <source-id>user/${userid}/dashboard</source-id>
                    <url>/components/dashlets/webview</url>
                </component>
                <component>
                    <scope>page</scope>
                    <region-id>component-2-3</region-id>
                    <source-id>user/${userid}/dashboard</source-id>
                    <url>/components/dashlets/my-documents</url>
                    <properties>
                        <height>240</height>
                    </properties>
                </component>
            </components>
            <pages>
                <page id="user/${userid}/dashboard">
                    <title>User Dashboard</title>
                    <title-id>page.userDashboard.title</title-id>
                    <description>Users dashboard page</description>
                    <description-id>page.userDashboard.description</description-id>
                    <template-instance>dashboard-2-columns-wide-right</template-instance>
                    <authentication>user</authentication>
                </page>
            </pages>
        </preset>
    </presets>
    Here we have included the WebView dashlet as component-2-2, so it will be displayed in column 2 and row 2 in the Dashboard layout. If you do not know the url for the dashlet, then just add it manually to the Dashboard and use SurfBug to identify what url that is used.
  2. Identify the web script that delivers the content that should be customized.

    For this we use the SurfBug [219] tool. Once the tool is activated (from http://localhost:8080/share/page/surfBugStatus) we can identify the web script as follows:

    Here we have scrolled down a bit on the Dashboard page so we have the WebView dashlet in front of us. Then we have clicked on the WebView dashlet. This brings up the above black box that contains information about what web script that is delivering the content for the dashlet. In this case it is the webview.get.* web script in package org.alfresco.components.dashlets that we need to target. You can also identify the web script via the URL (that is, /components/dashlets/webview).

  3. In the Share JAR project create a new web script override package org.alfresco.tutorials.customization.webview.controller.

    The directory path that needs to be created is: aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-webscripts/org/alfresco/tutorials/customization/webview/controller.

    We can choose any package path we want and then specify it in the Surf Extension Module, we will see this in a bit. However, it is important that we use a package path that will not clash with another Extension Module, deployed by some other JAR.

    For example, if we just used org.alfresco.tutorials.customization.webview and then another JAR was deployed with some other customization to the WebView dashlet, using the same package path. Then if one extension module is undeployed its customizations will still be picked up if the other module is active. This is because both modules are using the same package path.

  4. Add our version of the web script controller file called webview.get.js to the /tutorials/customization/webview/controller directory:

    if (model.isDefault == true)
    {
        model.widgets[0].options.webviewTitle = "Alfresco!";
        model.widgets[0].options.webviewURI = "http://www.alfresco.com";
        model.widgets[0].options.isDefault = false;
    }
    This controller will be processed after the out-of-the-box WebView controller. So what we are doing is just adding some stuff to the model widgets to tell the dashlet to load the Alfresco home page by default.

    By inspecting the source of both the out-of-the-box controller and the template, you can work out what model properties the template is using (see tomcat/webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/dashlets). This allows you to determine whether or not you can update the model after the base controller but before the template to create the required result.

  5. Add a new Surf Extension Modules file called customize-webscript-controller-extension-modules.xml to the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory (note. it is important to give this file a unique name when several Share JARs are installed, otherwise the last one wins):

    <extension>
        <modules>
            <module>
                <id>Customize Web Script Controller for Web View Dashlet</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <customizations>
                    <customization>
                        <targetPackageRoot>org.alfresco.components.dashlets</targetPackageRoot>
                        <sourcePackageRoot>org.alfresco.tutorials.customization.webview.controller</sourcePackageRoot>
                    </customization>
                </customizations>
            </module>
        </modules>
    </extension>

    This extension module identifies the package with the web script that we want to override by setting the targetPackageRoot property. When we have set what web script to override we use the sourcePackageRoot property to tell Alfresco where to pick up the customized web script files.

    This module will be deployed automatically when the application server is started as we have the auto-deploy property set to true.

  6. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
    Note. when defining presets for sites and users know that they are stored in the database after first time usage. In this tutorial we defined a new user preset to display a slightly different user dashboard. If a user, such as admin that we most likely use with the SDK, has been logging in before this customization was applied, then that user will already have a user dashboard preset in the database. So the customization will not appear to work. But wipe out alf_data_dev, if you can, and restart and you will see that it works.
  7. Now, log in to Share (http://localhost:8080/share) and you will see the WebView dashlet loaded with the home page:

    Note: A Surf Extension module like this can be deployed and undeployed during runtime. And this means that an Administrator can control when different customizations should be visible or hidden. This is managed via the Module deployment page that can be found at: http://localhost:8080/share/service/modules/deploy.

The custom JavaScript is executed after the original. The original JavaScript sets up an initial model object, which the default FreeMarker template can use to render content. Controller extensions then have the opportunity to change that model and thus change the rendered output. Using this approach is dependent upon the template making use of the changed model content - just adding content to the model will have no effect unless the template is also updated to make use of the additional model data.

It is not always possible to use this approach to customize existing components, as it depends on how the JavaScript controller and template are implemented, but the approach is worth exploring.

Parent topic: Pages [226]

Customizing (web script template) the footer text for a Surf page

Name Customize the footer text for a Surf page via web script Template change.
Extension Point Surf Extension Modules [41]
Description This tutorial demonstrates how to customize the page footer web script template to add some extra text to the footer. The tutorial walks through how to find the related component and web script.
Implementation Steps It is often necessary to customize different parts of a Surf web script. This tutorial shows you how to customize the template for a web script. The approach looks something like this:
  1. Find the page component that corresponds to the content that should be changed.
  2. Identify the web script that is used to deliver the content.
  3. Identify what part of the web script need changing to achieve the customization, in this case the template.
  4. Create your customized version of the {web script id}.get.[html|json|..].ftl file.
  5. Use a Surf Extension module to define the web script override.
Related Information This tutorial assumes that you are familiar with the Spring Surf development framework. If you are new to it then read up on it here [245] before starting this tutorial. If you have not already done so you should also review the Introducing SurfBug [219] topic as this tool is used in this tutorial.
Source Code Go to code [250]
This tutorial assumes you have created a new SDK All-In-One [59] project..
Tutorial implementation steps:
  1. Identify the web script that delivers the content that should be customized.

    For this we use the SurfBug [219] tool. Once the tool is activated (from http://localhost:8080/share/page/surfBugStatus) we can identify the web script as follows:

    Here we have scrolled to the bottom of the Dashboard page where the footer is located. Then we have clicked on the last component enclosed in red lines and representing the footer. This brings up the above black box that contains information about what web script that is delivering the content for the footer component (and then also the controller). In this case it is the footer.get.* web script in package org.alfresco.components.footer that we need to target. You can also identify the web script via the URL (that is, /components/footer).

  2. In the Share JAR project create a new web script override package org.alfresco.tutorials.customization.footer.template.

    The directory path that needs to be created is: aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-webscripts/org/alfresco/tutorials/customization/footer/template.

    We can choose any package path we want and then specify it in the Surf Extension Module, we will see this in a bit. However, it is important that we use a package path that will not clash with another Extension Module, deployed by some other JAR.

    For example, if we just used org.alfresco.tutorials.customization.footer and then another JAR was deployed with some other customization to the footer, using the same package path. Then if one extension module is undeployed its customizations will still be picked up if the other module is active. This is because both modules are using the same package path.

  3. Add our customization of the web script template file in a file named footer.get.html.ftl located in the /tutorials/customization/footer/template directory:

    <@markup id="additional-footer" target="html" action="before" scope="global">
        <div id="additional-footer">
            Additional Footer!
        </div>
    </@markup>
    This template will be processed after the out-of-the-box footer template. What we are doing here is just telling the template engine that we want to insert some extra footer content before the out-of-the-box footer content. The location of the new footer content is determined by the action attribute, which we set to before to get our additional footer text displayed above the original footer text. Specifically it is displayed above the markup section tagged with the html id, which we specify with the target attribute.

    You can find out the available markup sections by looking in the original template file, which in this case is tomcat/webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/footer/footer.get.html.ftl:

    <@markup id="css" >
       <#-- CSS Dependencies -->
       <@link href="${url.context}/res/modules/about-share.css" group="footer"/>
       <@link href="${url.context}/res/components/footer/footer.css" group="footer"/>
    </@>
    
    <@markup id="js">
       <@script src="${url.context}/res/modules/about-share.js" group="footer"/>
    </@>
    
    <@markup id="widgets">
       <@createWidgets/>
    </@>
    
    <@markup id="html">
       <@uniqueIdDiv>
          <#assign fc=config.scoped["Edition"]["footer"]>
          <div class="footer ${fc.getChildValue("css-class")!"footer-com"}">
             <span class="copyright">
                <a href="#" onclick="Alfresco.module.getAboutShareInstance().show(); return false;"><img src="${url.context}/res/components/images/${fc.getChildValue("logo")!"alfresco-share-logo.png"}" alt="${fc.getChildValue("alt-text")!"Alfresco Community"}" border="0"/></a>
                <#if licenseHolder != "" && licenseHolder != "UNKNOWN">
                   <span class="licenseHolder">${msg("label.licensedTo")} ${licenseHolder}</span><br>
                </#if>
                <span>${msg(fc.getChildValue("label")!"label.copyright")}</span>
             </span>
          </div>
       </@>
    </@>
    In most cases there is only one markup section for additional HTML and it is called html. You can also see markup sections for css, js, and widgets, which can be used to inject for example additional JavaScript and Stylesheet files.
  4. Add a new Surf Extension Modules file called customize-webscript-template-extension-modules.xml to the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory (note. it is important to give this file a unique name when several Share JARs are installed, otherwise the last one wins):

    <extension>
        <modules>
            <module>
                <id>Customize Web Script Template for Footer</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <customizations>
                    <customization>
                        <targetPackageRoot>org.alfresco.components.footer</targetPackageRoot>
                        <sourcePackageRoot>org.alfresco.tutorials.customization.footer.template</sourcePackageRoot>
                    </customization>
                </customizations>
            </module>
        </modules>
    </extension>

    This extension module identifies the package with the web script that we want to override by setting the targetPackageRoot property. When we have set what web script to override we use the sourcePackageRoot property to tell Alfresco where to pick up the customized web script files.

    This module will be deployed automatically when the application server is started as we have the auto-deploy property set to true.

  5. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  6. Now, log in to Share (http://localhost:8080/share) and you will see the additional footer texts at the bottom of the Dashboard page:

    Note: A Surf Extension module like this can be deployed and undeployed during runtime. And this means that an Administrator can control when different customizations should be visible or hidden. This is managed via the Module deployment page that can be found at: http://localhost:8080/share/service/modules/deploy.
  7. If you want to test out the other FreeMarker extensibility operations, then you can update the footer.get.html.ftl file as follows:
    • To place the new footer content after the footer: <@region id="additional-content" target="html" action="after" scope="global"/>
    • To replace the content of the footer with the new content: <@region id= "additional-content" target="html" action="replace" scope="global"/>
    • To remove the footer region completely:<@region id="additional-content" target="html" action="remove"/>
Parent topic: Pages [226]

Adding a new Surf page to Share

This tutorial demonstrates how to add a new page to Alfresco Share.

In this tutorial you will see how to add a new page to Share. In the following tutorial you will see how to make this new page the default landing page. Source code for sample Surf page can be found here [163] .

Adding a new page requires a minimum of three files:

  • A Page definition file
  • A Template-Instance definition file
  • A FreeMarker template file
  1. This tutorial assumes you have created a new SDK All-In-One [59] project.
  2. Create a new folder called pages in aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data.
  3. In the pages folder create a new file called home-page.xml with the following contents:

                            
    <page>
        <template-instance>home-page</template-instance>
        <authentication>user</authentication>
    </page>                        
                            
                        

    This is the page definition file. This defines the page name (which is the file name) and a mapping to a Template-Instance that contains the content. It also defines the level of authentication required to view the page.

  4. In the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data folder, create a subfolder called template-instances.
  5. In the newly created template-instances folder, create a new file, also called home-page.xml with the following contents:

    
    <template-instance>
        <template-type>tutorials/home-page</template-type>
    </template-instance>
                         
                        

    This is the Template-Instance definition. This creates a mapping to the actual FreeMarker template that contains the content for the page.

  6. Create a new file in aio/aio-share-jar/src/main/resources/alfresco/web-extension/templates/tutorials called home-page.ftl with the following contents:

    
    <html>
        <head>
            <title>Tutorials Application</title>
        </head>
        <body>
            Welcome To Extreme Share Customization!
        </body>
    </html>                    
    
                        

    This is the FreeMarker template file. This contains the actual page content. The example shown here is trivial but this could also contain regions for binding Surf components if required.

  7. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  8. In your web browser open the following location http://localhost:8080/share/page/home-page.

    You will be prompted with the standard Share login screen.

    You are asked to log in because in the page definition file you set the authentication level to be user, that is the page is accessible to all logged in users. To create a page that can only be accessed by administrators using an authentication level of admin. To create a page that can be accessed by any user, including those who are not logged in, set the authentication level to none.

  9. Log in using your credentials.

    After logging in your new home page will be displayed.

    Note that the FreeMarker template for the new page can contain any HTML/JavaScript/CSS code necessary. You are not restricted to using YUI2 code as is used in the implementation of Share. It is possible to implement the new page in JQuery, Dojo, pure HTML, or any other valid code.

    As well as being able to re-use the standard Share authentication mechanism you are also able to access all the web scripts available on both the Web and Repository tiers. This means you can build your own UI around existing Alfresco Community Edition functionality.

    Note: When using resources such as images, JavaScript and CSS files, it is important to remember that they should be located under the META-INF folder in your JAR file. Also, it is necessary to use the /res prefix on subsequent requests to the resources. For example, to request the file META-INF/tutorials/example.css, the URL /share/res/tutorials/example.css would be used.
Parent topic: Pages [226]

Override Share sign in page

This tutorial demonstrates how to override the default Alfresco Share sign in page.
Surf applications can define a sign in page by configuring the login page-type mapping to reference a specific Page object. In Share this definition can be found in the surf.xml configuration file which sets the login page-type to map to the slingshot-login page.
  1. This tutorial assumes you have created a new SDK All-In-One [59] project.
  2. In the aio/aio-share-jar/src/main/resources/META-INF/share-config-custom.xml file, add the following contents:

       <config evaluator="string-compare" condition="WebFramework">
          <web-framework>
             <defaults>
                <page-type>
                   <id>login</id>
                   <page-instance-id>tutorials-login</page-instance-id>
                </page-type>
             </defaults>
          </web-framework>
       </config>
                            
  3. In the folder aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/pages, create the referenced page as tutorials-login.xml with the following contents:

    <page>
       <template-instance>tutorials-login</template-instance>
       <authentication>none</authentication>
    </page>
                        
    Attention: Note that the page definition sets the authentication level to none. This is necessary otherwise the user would be required to be authenticated, that is logged in, before the login page is displayed.
  4. In the folder aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/template-instances create another file also called tutorials-login.xml with the following content:

    <template-instance>
       <template-type>tutorials/tutorials-login</template-type>
    </template-instance>                 
                        

    This file creates the mapping between the Template-Instance object and the FreeMarker template that will actually render your new sign in page.

  5. In the aio/aio-share-jar/src/main/resources/alfresco/web-extension/templates/tutorials folder, create the new sign in page in the file tutorials-login.ftl, with the following content:

    <html>
       <head>
          <title>Tutorials Login</title>
       </head>
       <body>
          <h2>Tutorials Login</h2>  
          <form id="loginform" accept-charset="UTF-8" method="post" action="${url.context}/page/dologin">
             Username: <input type="text" id="username" name="username"/><br>
             Password: <input type="password" id="password" name="password"/><br>
             <input type="submit" id="btn-login" />
          </form>
       </body>
    </html>                        
                        
  6. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  7. View the page http://localhost:8080/share.

    Attention: Note that you might find that you are logged in automatically without being prompted for user name and password. This is because your validation might be cached in cookies. To resolve this simply remove cookies for your localhost and attempt to access http://localhost:8080/share again. This time you will see your custom login page.

    The new sign in page will be displayed.

Parent topic: Pages [226]

Making the new page the default

This tutorial demonstrates how to make the new page you created in the previous tutorial the default landing page.

In this tutorial you will see how to make a page the default landing page.

Surf supports the notion of a default page which is defined in the configuration for the entire website. By default the landing page is site-index.jsp, which redirects to the authenticated user's dashboard page. The site default landing page will be rendered when a request is mapped to the Spring MVC RequestDispatcher (which by default is at /page and /p) but no page is included in the request, for example, /share/page. This is also configured as the welcome-file in the Share web.xml file.

To change the landing page for the application, you can override the default site configuration for Alfresco Share. The site configuration used is defined in surf.xml and is set to slingshot.site.configuration by default.

  1. This tutorial assumes you have created a new SDK All-In-One [59] project.
  2. Create a folder called configurations in aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data
  3. In the configurations folder create a file called slingshot.site.configuration.xml, with the following contents:

    <configuration>
        <source-id>site</source-id>
        <properties>
            <root-page>home-page</root-page>
        </properties>
    </configuration>
                                
    Important: Note that the file is located on the web-extension path so that it is resolved before the Share default.
  4. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  5. Load the page http://localhost:8080/share.

    After logging in your new landing page will be displayed.

Parent topic: Pages [226]

Localizing Pages

Use this information to localize pages.

Introduction

You can provide i18n properties files with the same prefix that can then be accessed in the JavaScript controller by calling:

msg.get("<key>")

It is also possible to achieve this by calling from the FreeMarker template:

${msg(“<key>”)}

Traditionally, Share would pass all of the messages from a web script into the widgets that it instantiated by calling its .setMessages() function.

There are global properties files that can be used throughout Share. These are common.properties and slingshot.properties that can be found in /share/WEB-INF/classes/alfresco/messages.

The contents of all of these files will be active on the page by using the JavaScript global variable Alfresco.messages. This is a map with the attributes, global and scope:

  • global contains all the messages from the global properties files
  • scope is a map of widget name to messages map

The .setMessages() function of Share widgets adds its own name as a key into the scope map and assigns all the supplied messages as an object against that key. For example, if the Alfresco.DocumentList widget is instantiated then Alfresco.messages.scope['Alfresco.DocumentList'] can be used to access its messages.

Changes for the updated UI framework

The updated development approach in 4.2 and above is consistent with the pattern found in previous versions of Alfresco Community Edition, and have intentionally not followed the standard Dojo pattern. The latest approach uses the same Alfresco.messages object (although this can be reconfigured if you want to use a different root variable) and still sets the global and scope attributes.

If you create a widget with an i18nScope attribute then this is the scope into which the widget's encapsulated messages will be added. If no i18nScope attribute is defined then the messages will go into a scope called default (unless the widget extends another widget in which case it will inherit the parent's i18nScope attribute).

The i18n properties from the web script that processes the JSON model will automatically be placed into a new attribute of Alfresco.messages called page.

Whenever the .message() function is called from Alfresco/core/Core all applicable scopes are searched and the most specific result will be applied. These scopes are listed here:

  • Global
  • Page
  • Default scope
  • All inherited scopes
  • Widget scopes

When creating a custom widget there is a distinction to be drawn between:

  • Labels that never change
  • Variable labels that can be selected from
  • Custom labels

For example, the label for a menu item cannot realistically be included as part of the widget, but an error message could be. When accepting configurable labels they should be passed through the .message() function in case a message key (rather than a localized message) has been provided, as if no match is found then the value supplied to the method is returned.

This means that when constructing the JSON model for a page you could provide:

config: {
   label: "message.key"
}

or

config: {
   label: msg.get("message.key")
}

At first glance these might appear identical, but if the widget defines a message with the key message.key then this will override any message that the web script might be able to resolve.

Language Variations

As the widgets process locale-specific properties files in exactly the same way as web scripts, it is possible to reference a web script's properties file in the i18nRequirements attribute of a widget.

Summary

You have learned how i18n properties are handled in the updated UI framework approach to page and widget construction. It follows the Alfresco Community Edition approach rather than having adopted the standard Dojo approach. This has been done to achieve consistency with previous versions of Alfresco Community Edition.

Parent topic: Pages [226]

Document Library

These are tutorials for the Document Library.

  • Adding new actions to the Document Library [131]
  • Adding new metadata templates to the Document Library [251]
  • Adding a menu item to the "Create..." menu in DocLib [132]
  • Customizing a Surf JavaScript Widget in the Document Library [141]
Parent topic: Tutorials [4]

Adding new actions to the Document Library

Name Adding new actions to the Document Library
Extension Point Document Library [40]
Description

In many extension projects you want to customize the Document Library in Alfresco Share. And quite often this involves adding new actions that can be applied to the content in the library. These actions are referred to as "DocLib" actions, and unlike a lot of other functionality in Alfresco Community Edition, they do not use web scripts to implement their business logic, at least not directly, instead they hook into custom, or out-of-the-box, client-side JavaScript code.

Each action has a 16x16 icon, one or more text labels, and configuration to hook them into the Share application. Most actions by their nature do something, and it’s likely that they will make a call back to the repository to perform their work, which may require a custom repository Action or a custom repository web script.

This tutorial will demonstrate how to add a DocLib action that can be used to send documents as attachments in an email. The "Send-as-Email" action will be available for documents in Browse view and Details view. The implementation of this action will make use of a form to collect the email data, such as where to send the email, subject, etc. The email will be sent by a custom repository Action that is invoked by an out-of-the-box JavaScript function.

The tutorial will also show how a web script can be called from a DocLib action in a an easy way. And finally we will look at how to create an action that displays an external Web page.

Implementation Steps Adding a new DocLib action to the Document Library involves the following steps:
  1. Configure the action so it is known to Share (typically in a Surf Extension Module)
  2. Configure where the action should be visible (typically in a Surf Extension Module)
  3. (Optionally) Configure a form for the action if it requires input from the end-user, such as asking for email address, email subject, etc (typically in a Surf Extension Module)
  4. Add a custom icon
  5. (Optionally) Add an evaluator if the action should be visible based on a condition, such as previously executed or not
  6. (Optionally) Add a status indicator, can for example be used to show if an action has been applied to a content item
  7. Implement custom client side JavaScript code that should be called when action is invoked, or use one of the out-of-the-box JavaScript functions (e.g. onActionFormDialog - displays a form and then calls a Repo Action, onActionSimpleRepoAction - calls a Repo Action)
  8. (Optionally) Implement any repository Action or repository web script that should be invoked by the action
As we can see, implementing a DocLib action can involve quite a few steps and take some time. However, it can also be very simple as we will see with our DocLib action example that navigates to the Google search home page.
Related Information This tutorial assumes that you are familiar with the Document Library in Share. If you are new to it read up on it here [252] before starting this tutorial. Also, familiar yourself with how Surf Extension Modules [41] work as we will be creating one of those.
Source Code Go to code [253]
This tutorial assumes you have created a new SDK All-In-One [59] project. To try out the Send-As-Email DocLib action in this tutorial you will need to install a local SMTP server such as Fake SMTP [254].

This tutorial will demonstrate the following:

  • How to create a DocLib action that uses the out-of-the-box onActionFormDialog JavaScript function to collect data from the user via a form and then call a repository action with this data (Send-As-Email).
  • How to create a DocLib action that invokes a custom JavaScript function and displays a message (Show-Custom-Message)
  • How to create a DocLib action that invokes a custom repository web script (Call-Web-Script)
  • How to create a DocLib action that navigates to an external web page (Go-To-Google)

Tutorial implementation steps:

Preparations for the Send-As-Email DocLib action.

  1. Add a project with a repository Action that can send emails with attachments.

    This tutorial assumes that we have a repository Action available that can send emails with attachments. Currently the out-the-box mail repository action cannot send emails with attachments. So we need to include another custom Repo action that can do this. The SDK sample code has a repository JAR project with such an action that we can use. See sample action [255].

    Copy the whole add-action-repo JAR project into your All-In-One project. Then include it in the repository WAR project by updating the aio/pom.xml file as follows:
           
       <plugin>
         <groupId>org.alfresco.maven.plugin</groupId>
         <artifactId>alfresco-maven-plugin</artifactId>
         <version>${alfresco.sdk.version}</version>
            ...
            <platformModules>
              <!-- Share Services will be ignored if you are on Platform earlier than 5.1 -->
              <moduleDependency>
                  <groupId>${alfresco.groupId}</groupId>
                  <artifactId>alfresco-share-services</artifactId>
                  <version>${alfresco.share.version}</version>
                  <type>amp</type>
              </moduleDependency>
    
              <!-- Bring in custom Modules -->
              <moduleDependency>
                  <groupId>${project.groupId}</groupId>
                  <artifactId>aio-platform-jar</artifactId>
                  <version>${project.version}</version>
              </moduleDependency>
    
              <moduleDependency>
                  <groupId>${project.groupId}</groupId>
                  <artifactId>add-action-repo</artifactId>
                  <version>${project.version}</version>
              </moduleDependency>
    
              <!-- Bring in the integration tests -->
              <moduleDependency>
                 <groupId>${project.groupId}</groupId>
                 <artifactId>integration-tests</artifactId>
                 <version>${project.version}</version>
                 <classifier>tests</classifier>
               </moduleDependency>
            </platformModules>
            
    A repository action with the send-as-email ID is now available and we can call it from a DocLib action. It takes three parameters as can be seen in the implementation:
    public class SendAsEmailActionExecuter extends ActionExecuterAbstractBase {
        private static Log logger = LogFactory.getLog(SendAsEmailActionExecuter.class);
    
        public static final String PARAM_EMAIL_TO_NAME = "to";
        public static final String PARAM_EMAIL_SUBJECT_NAME = "subject";
        public static final String PARAM_EMAIL_BODY_NAME = "body_text";
    
        ...
    
        @Override
        protected void addParameterDefinitions(List<ParameterDefinition> paramList) {
            for (String s : new String[]{PARAM_EMAIL_TO_NAME, PARAM_EMAIL_SUBJECT_NAME, PARAM_EMAIL_BODY_NAME}) {
                paramList.add(new ParameterDefinitionImpl(s, DataTypeDefinition.TEXT, true, getParamDisplayLabel(s)));
            }
        }               
            
    Our Send-As-Email DocLib action will collect the values for these three parameters via a form.
  2. Start up the SMTP Server

    After downloading the FakeSMTP server, see link in the beginning of this tutorial, unpack and then start it with the following command:

    martin@gravitonian:~/apps/fakeSMTP$ java -jar fakeSMTP-1.13.jar -s -p 2525 
    It should start up immediately and listen on port 2525, you should see a UI that will display any incoming emails.

Implementing the Send-As-Email DocLIb Action.

  1. Add a new Surf Extension Module file and define the Send-As-Email action

    Call the file add-doclib-actions-extension-modules.xml and save it in the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory (note. it is important to give this file a unique name when several Share JARs are installed, otherwise the last one wins).

    Then define the Send-As-Email DocLib action as follows:

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocLibActions">
                        <actions>
                            <action id="alfresco.tutorials.doclib.action.sendAsEmail"
                                    icon="email"
                                    type="javascript"
                                    label="alfresco.tutorials.doclib.action.sendAsEmail.label">
                                <param name="function">onActionFormDialog</param>
                                <param name="itemKind">action</param>
                                <param name="itemId">send-as-email</param> <!-- Repository action id = Spring Bean id -->
                                <param name="mode">create</param>
                                <param name="destination">{node.nodeRef}</param>
                                <param name="successMessage">alfresco.tutorials.doclib.action.sendAsEmail.msg.success</param>
                                <param name="failureMessage">alfresco.tutorials.doclib.action.sendAsEmail.msg.failure</param>
                                <evaluator negate="true">alfresco.tutorials.evaluator.isEmailed</evaluator>
                            </action>
                        </actions>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension> 

    The different attributes and sub-elements for the action element have the following meaning:

    Name Description
    id

    The global identifier for this action. It's used when you refer to this action in other parts of the configuration, such as when defining where it should be visible.

    icon

    Share looks for an icon that starts with this name and ends with “-16.png”. So it will look for email-16.png in our case. Alfresco Community Edition expects the image file to be located in the /components/documentlibrary/actions directory. If not set, the id is used.

    type

    Sets the type of action; this can be either javascript (as in our example) if you want the action to execute some Java Script code, link if you want to invoke some external URL, or pagelink if you want to invoke a URL within the Share web application. More details around the different types:
    • link - accepts a href parameter that will be passed a nodeRef token for substitution, used for external links.
    • pagelink - accepts a page parameter that will be passed a nodeRef token for substitution, used for Share links.
    • javascript - accepts a function parameter with a JavaScript function that will get the current folder item as first argument.

    label

    Points to a property name in a resource file. The value of this property will be displayed in the UI as the action’s label. In our case the resource file is aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/aio-share-jar.properties

    param

    There can be one or more parameters set for an action. In case of a javascript action they will be passed into the Java Script code, there is one special parameter with the name function that sets the Java Script function that should be called when the action is executed. In case of a link action the parameters would typically be used to specify href and target. In case of a pagelink action a page parameter is used to specify a relative URL within the Share web application.

    evaluator

    Spring Bean id for an evaluator that is called by the system to find out if the action should be visible or not in the UI. An evaluator extends the org.alfresco.web.evaluator.BaseEvaluator class. You can negate the result of calling the evaluator by setting the negate attribute to true. In our case we do not want to show the Send-As-Email action if it has already been invoked on a content file.

    When the Send-As-Email action is invoked we want it to do the following:

    1. Display a form where the end-user can type in the values for the email address, email subject, and email body text.
    2. When the form is submitted it should automatically call a custom repository action with the information collected via the form.
    We achieve this by using the out-of-the-box JavaScript function called onActionFormDialog. The following table explains the parameters used with this function:
    Name Description
    itemKind

    The “kind” of item that the form is for, and that should be invoked when the form is submitted. For example, node, task, type, action (that is, repository action), mbean. In our case we are going to show a form that collects values for parameters used when invoking a repository action, so we specify itemKind as action.

    If you need multiple forms for the same itemKind and itemId then you can also add an extra parameter called formId. It is the form configuration to lookup, refers to the id attribute of the form element. If omitted the default form is used, that is, the form element without an id attribute.

    itemId

    The identifier for the item the form is for, this will be different for each “kind” of item, for an action it would be the Spring bean ID for the repository action definition, for a node it would be a NodeRef etc. In our case it is set to send-as-email, which matches a Spring Bean ID in the aio/aio-platform-jar/src/main/resources/alfresco/module/aio-platform-jar/context/service-context.xml context file.

    mode

    Mode the current form is in, can be view, edit or create, defaults to edit. In our case we are using the create mode as we want the form to be empty so we can collect new email information.

    destination

    Provides a destination for any new items created by the form. When present a hidden field is generated with a name of alf_destination. Note. This parameter is necessary even if the action is not creating any new items/nodes.

    successMessage

    A message to display when the DocLib action is executed successfully. It actually points to a property name in a resource file. In our case the resource file is aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/aio-share-jar.properties

    failureMessage

    A message to display when the DocLib action execution failed. It actually points to a property name in a resource file. In our case the resource file is aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/aio-share-jar.properties

  2. Add an i18n resource file that will contain all the labels and messages for the Send-As-Email action.

    We can use the existing aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/aio-share-jar.properties file for this. Add the following properties to it:

    alfresco.tutorials.doclib.action.sendAsEmail.label=Send as Email
    alfresco.tutorials.doclib.action.sendAsEmail.msg.success='{0}' successfully sent in email to {1}.
    alfresco.tutorials.doclib.action.sendAsEmail.msg.failure=Couldn't send '{0}' in email to {1}.
  3. Define where in the user interface the Send-As-Email action should be displayed.

    This is also done in the add-doclib-actions-extension-modules.xml file in a new section called actionGroups::

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocLibActions">
                        <actions>
                         ...
                        </actions>
                        
                         <actionGroups>
                            <actionGroup id="document-browse">
                                <action index="400" id="alfresco.tutorials.doclib.action.sendAsEmail" />
                            </actionGroup>
                            <actionGroup id="document-details">
                                <action index="400" id="alfresco.tutorials.doclib.action.sendAsEmail" />
                            </actionGroup>
                        </actionGroups>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension>
    In this sub-section we configure in what document library views the action should be visible and where in the list of actions it should be displayed (ordering). To refer to the action we use the id that was specified when the action was defined. The following table shows available actionGroups:
    Action Group Id Default usage
    document-browse

    Action is visible for documents on the Browse page

    document-details

    Action is visible for document on the Document Details page

    folder-browse

    Action is visible for folders on the Browse page

    folder-details

    Action is visible for folder on the Folder Details page

    document-link-browse

    Action is visible for links to documents on the Browse page

    document-link-details

    Action is visible for link to document on the Document Details page

    folder-link-browse

    Action is visible for links to folders on the Browse page

    folder-link-details

    Action is visible for link to folder on the Folder Details page

    The index argument is specifying the order of this action in the list of actions. The higher the number the lower it will be displayed in the action list. By having a look in the share-documentlibrary-config.xml configuration file located in the alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco directory of your Alfresco Community Edition 201911 GA installation, you can find out that the highest index for document-browse actions is 360 and for document-details actions 390. So if we set our index for the Send-As-Email action to 400 it should end up last in both of these action lists.

    If you want more examples of how Document Library actions can be defined and configured, have a look in the share-documentlibrary-config.xml file and the DocLibActions section.

  4. Add a custom icon for the Send-As-Email action.

    The icons for all the Document Library actions are stored in the tomcat/webapps/share/components/documentlibrary/actions directory in your Alfresco Community Edition installation. The system will try and load any custom Document Library action icons from this directory. Icons are loaded via the resource Servlet and action icons related to the Document Library are loaded with the http://localhost:8080/share/res/components/documentlibrary/actions/<icon>-16-png URL. This article is not about how to create a 16x16 icon in PNG format so copy one from the SDK sample source. In fact, copy all the icons that we need for all actions in this tutorial from this folder [256] and put them in the aio/aio-share-jar/src/main/resources/META-INF/resources/aio-share-jar/components/documentlibrary/actions directory of your project (you might have to create this directory path).

  5. Add an Evaluator for the Send-As-Email action.

    For demonstration purpose the send-as-email repository action is implemented so it sets the cm:emailed aspect on the document after it has been sent in an email. This will then be checked by this evaluator, which will disable the Send-As-Email DocLib action if the document has the cm:emailed aspect already applied.

    There are three parts to setting up an evaluator for a Document Library action:

    1. Configure it with the <evaluator> element in the action configuration (We have already done this)
    2. Create a Java class that extends the org.alfresco.web.evaluator.BaseEvaluator class
    3. Define a spring bean with an id matching the <evaluator> configuration element’s value and then set the class for the Spring bean to the one implemented in step 2
    Create a new Java class called CheckIfDocIsEmailedEvaluator in the aio/aio-share-jar/src/main/java/org/alfresco/tutorial/doclibaction/evaluator/ package (you will have to create the package path). Then implement the Java class like this:
    package org.alfresco.tutorial.doclibaction.evaluator;
    
    import org.alfresco.web.evaluator.BaseEvaluator;
    import org.json.simple.JSONArray;
    import org.json.simple.JSONObject;
    
    public class CheckIfDocIsEmailedEvaluator extends BaseEvaluator {
        private static final String ASPECT_EMAILED = "cm:emailed";
    
        @Override
        public boolean evaluate(JSONObject jsonObject) {
            try {
                JSONArray nodeAspects = getNodeAspects(jsonObject);
                if (nodeAspects == null) {
                    return false;
                } else {
                    if (nodeAspects.contains(ASPECT_EMAILED)) {
                        return true;
                    } else {
                        return false;
                    }
                }
            } catch (Exception err) {
                throw new RuntimeException("JSONException whilst running action evaluator: " + err.getMessage());
            }
        }
    }
    The evaluate method gets a JSON object passed in from which you can get all the information you need about the node that the action is being applied to. Here we use the getNodeAspects method to get all the aspects that have been applied to the node (for more methods look in the BaseEvaluator class). Then we just check if the cm:emailed aspect has been applied to the node (that is, file).

    Next thing we need to do is define a Spring Bean for this evaluator, this is done in the aio-share-jar-slingshot-application-context.xml file located in the aio/aio-share-jar/src/main/resources/alfresco/web-extension/. directory. Define the bean as follows:

    <bean id="alfresco.tutorials.evaluator.isEmailed"
           class="org.alfresco.tutorial.doclibaction.evaluator.CheckIfDocIsEmailedEvaluator" />
    Note here that the id has to match what was specified for the <evaluator> element in the action definition.

    It is not always necessary to create evaluators from scratch. There are a number of predefined evaluators (that is, out of the box evaluators ready to use):

    • Has aspect
    • Is mimetype
    • Property not Null
    • Site preset
    • Site / No Site
    • Container Type
    • Node Type
    • Always false
    • Value-based
    • Metadata value
    • Is Browser (type)
    • Is Portlet mode

    See the slingshot-documentlibrary-context.xml file located in the alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco directory of your Alfresco Community Edition 201911 GA installation for more information about out-of-the-box evaluators.

  6. Add a Status Indicator for the Send-As-Email action.

    Sometimes you might want to know if a document has been emailed without going in and checking if the cm:emailed aspects has been applied. This can be achieved by adding a so called status indicator. An indicator is displayed in the Document Library browse view and builds on the work we have already done with the evaluator.

    There are four parts to setting up an indicator for a Document Library action:

    1. Make sure you got an <evaluator> element in the action configuration (We have already done this) and that this evaluator has been implemented (We have already done this)
    2. Add an indicator configuration to the DocumentLibrary section configuration
    3. Add i18n label to the resource property file
    4. Add an image to be used as indicator to the components/documentlibrary/indicators directory

    The indicator configuration is also done in the add-doclib-actions-extension-modules.xml file and points to the evaluator previously implemented. It looks like this in the new DocumentLibrary section:

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocumentLibrary">
                        <indicators>
                            <indicator id="alfresco.tutorials.indicator.isEmailed"
                                       icon="email-16.png"
                                       index="100"
                                       label="alfresco.tutorials.indicator.isEmailed.label">
                                <evaluator>alfresco.tutorials.evaluator.isEmailed</evaluator>
                            </indicator>
                        </indicators>
                    </config>
    
                    <config evaluator="string-compare" condition="DocLibActions">
                    ...
                    </config>
    
                </configurations>
            </module>
        </modules>
    </extension>

    The different attributes and sub-elements for the indicator element have the following meaning:

    Name Description
    id

    The global identifier for this indicator.

    icon

    The name of the icon to display as the status indicator. Alfresco Community Edition expects the image file to be located in the /components/documentlibrary/indicators directory. If not specified, “id” is used. Note. In this case Alfresco Community Edition does not assume *-16.png format but you have to specify the complete file name.

    index

    Is used to order the indicator in the UI when there are several indicators displayed for a document. If we look in the share-documentlibrary-config.xml (in the alfresco5/tomcat/webapps/share/WEB-INF/classes/alfresco directory) configuration file we can see that the largest index for out-of-the-box indicators is 90, so by using 100 the emailed indicator will always be displayed last in the list.

    label

    Points to a property name in a resource file. The value of this property will be displayed in the UI as the indicators tool-tip. In our case the resource file is aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/aio-share-jar.properties

    evaluator

    Spring Bean id for an evaluator that is called by the system to find out if the indicator should be visible or not in the UI. An evaluator extends the org.alfresco.web.evaluator.BaseEvaluator class. You can negate the result of calling the evaluator by setting the negate attribute to true. In our case we do want to show the indicator if the Send-As-Email action has been invoked on a content file, so we don't negate.

    Now update the resource properties file with the value for the label, open the aio-share-jar.properties file and add the following property to it:

    alfresco.tutorials.indicator.isEmailed.label=This document has been emailed
    As an indicator image we will use the same one as is used for the action. Copy the email-16.png icon from the aio/aio-share-jar/src/main/resources/META-INF/resources/aio-share-jar/components/documentlibrary/actions directory to the aio/aio-share-jar/src/main/resources/META-INF/resources/aio-share-jar/components/documentlibrary/indicators directory (you might have to create the indicators directory).
  7. Add the form for the Send-As-Email action.

    The Send-As-Email action invokes the out-of-the-box onActionFormDialog JavaScript function, which expects there to be a form registered for the repository action that is invoked.

    The repository action that sends emails with attachments is registered with the id send-as-email (see aio/add-action-repo/src/main/resources/alfresco/module/add-action-repo/context/service-context.xml). We define a form for it as follows in the add-doclib-actions-extension-modules.xml file:

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocumentLibrary">
                    ...
                    </config>
    
                    <config evaluator="string-compare" condition="DocLibActions">
                    ...                
                    </config>
    
                    <config evaluator="string-compare"
                            condition="send-as-email"> <!-- ID for the Repository Action that this form is associated with -->
                        <forms>
                            <form>
                                <field-visibility>
                                    <show id="to"/>
                                    <show id="subject"/>
                                    <show id="body_text"/>
                                </field-visibility>
                                <appearance>
                                    <field id="to" label-id="alfresco.tutorials.doclib.action.sendAsEmail.form.field.to"/>
                                    <field id="subject" label-id="alfresco.tutorials.doclib.action.sendAsEmail.form.field.subject"/>
                                    <field id="body_text" label-id="alfresco.tutorials.doclib.action.sendAsEmail.form.field.body_text">
                                        <control template="/org/alfresco/components/form/controls/textarea.ftl" />
                                    </field>
                                </appearance>
                            </form>
                        </forms>
                    </config>
    
                </configurations>
            </module>
        </modules>
    </extension>

    Note here that the field identifiers (that is, the id attribute) need to match the parameters sent into the send-as-email repository action. See aio/aio-platform-jar/src/main/java/org/alfresco/tutorial/repoaction/SendAsEmailActionExecuter.java.

    Update the resource properties file with the field labels as follows, the property names must match what we defined in the form definition above (that is, the label-id values). In the aio-share-jar.properties file add the following properties:

    alfresco.tutorials.doclib.action.sendAsEmail.form.field.to=To
    alfresco.tutorials.doclib.action.sendAsEmail.form.field.subject=Subject
    alfresco.tutorials.doclib.action.sendAsEmail.form.field.body_text=Body Text
  8. Build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  9. Now, log in to Share (http://localhost:8080/share) and upload a document to some folder. You will see the new Send-As-Email action in the Browse view when hovering over the document and clicking More... in the pop-up menu:

    Clicking on the file name displays the Document Details view, where the Send-As-Email action should also be visible:

    Clicking on the Send-As-Email action will display the form for collecting email information:

    Filling in the form and clicking OK will call the send-as-email repository action, which will send the email with the file as attachment. The Repo action will also apply the cm:emailed aspect to the document. So we should be able to see the indicator on the file telling us the Send-As-Email action has been applied to it:

    If the FakeSMTP server is running we should see a new email picked up:

Implementing the Call-Web-Script DocLib Action.

  1. Define and configure the Call-Web-Script action

    Open the add-doclib-actions-extension-modules.xml Surf Extension module file that we have used so far in the tutorial, it is located in the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory.

    Then define the Call-Web-Script DocLib action as follows:

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocLibActions">
                        <actions>
                        ...
                            <action id="alfresco.tutorials.doclib.action.callWebScript"
                                    icon="callws"
                                    type="javascript"
                                    label="alfresco.tutorials.doclib.action.callWebScript.label">
                                <param name="function">onActionCallWebScript</param>
                                <param name="successMessage">alfresco.tutorials.doclib.action.callWebScript.msg.success</param>
                                <param name="failureMessage">alfresco.tutorials.doclib.action.callWebScript.msg.failure</param>
                            </action>
                        </actions>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension> 

    This action is also of type javascript in the same way the Send-As-Email action was. However, this action will call a custom JavaScript function called onActionCallWebScript. The callws-16.png icon for this action should already be available if you implemented the Send-As-Email action above.

  2. Add labels and messages for the Call-Web-Script to the i18n resource file .

    Open up the aio-share-jar.properties file locate din the aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/ directory. Then add the following properties to it:

    alfresco.tutorials.doclib.action.callWebScript.label=Call Web Script
    alfresco.tutorials.doclib.action.callWebScript.msg.success=Successfully called Web Script
    alfresco.tutorials.doclib.action.callWebScript.msg.failure=Failed to invoke Web Script
  3. Define where in the user interface the Call-Web-Script action should be displayed.

    This is done in the add-doclib-actions-extension-modules.xml file in the section called actionGroups::

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocLibActions">
                        <actions>
                         ...
                        </actions>
                        
                         <actionGroups>
                            <actionGroup id="document-browse">
                                ...
                                <action index="401" id="alfresco.tutorials.doclib.action.callWebScript" />
                            </actionGroup>
                            <actionGroup id="document-details">
                                ...
                                <action index="401" id="alfresco.tutorials.doclib.action.callWebScript" />
                            </actionGroup>
                        </actionGroups>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension>
    The Call-Web-Script action will be displayed in the same views as the Send-As-Email action. We give it an index of 401 so it is displayed just after the Send-As-Email action.
  4. Implement the custom JavaScript function that is invoked by the Call-Web-Script action.

    This is the first custom JavaScript function that we implement so we need a new file for it, call it custom-doclib-actions.js and put it in the aio/aio-share-jar/src/main/resources/META-INF/resources/aio-share-jar/components/documentlibrary directory.

    The implementation of the onActionCallWebScript function looks like this:

    (function () {
        YAHOO.Bubbling.fire("registerAction",
            {
                actionName: "onActionCallWebScript",
                fn: function org_alfresco_training_onActionCallWebScript(file) {
                    this.modules.actions.genericAction(
                        {
    
                            success: {
                                callback: {
                                    fn: function org_alfresco_training_onActionCallWebScriptSuccess(response) {
                                        Alfresco.util.PopupManager.displayPrompt(
                                            {
                                                title: this.msg("alfresco.tutorials.doclib.action.callWebScript.msg.success"),
                                                text: JSON.stringify(response.json),
                                                buttons: [
                                                    {
                                                        text: this.msg("button.ok"),
                                                        handler: function org_alfresco_training_onActionCallWebScriptSuccess_success_ok() {
                                                            this.destroy();
                                                        },
                                                        isDefault: true
                                                    },
                                                    {
                                                        text: this.msg("button.cancel"),
                                                        handler: function org_alfresco_training_onActionCallWebScriptSuccess_cancel() {
                                                            this.destroy();
                                                        }
                                                    }]
                                            });
    
                                    },
                                    scope: this
                                }
                            },
                            failure: {
                                message: this.msg("alfresco.tutorials.doclib.action.callWebScript.msg.failure",
                                    file.displayName, Alfresco.constants.USERNAME)
                            },
                            webscript: {
                                name: "sample/fileinfo?nodeRef={nodeRef}",
                                stem: Alfresco.constants.PROXY_URI,
                                method: Alfresco.util.Ajax.GET,
                                params: {
                                    nodeRef: file.nodeRef
                                }
                            },
                            config: {}
                        });
                }
            });
    })();

    The way we plug-in custom JavaScript action handlers is to call the YAHOO.Bubbling.fire(“registerAction”…) method. This will tell the system about the new action JavaScript code, and it will be plugged in after the out- of-the-box code to allow for customization and extensions.

    In the org_alfresco_training_onActionCallWebScript function we use the this.modules.actions.genericAction function to call a specific custom web script (or an out-of-the-box web script if we wanted to). The genericAction function is defined in the doclib-actions.js file located in the alfresco/tomcat/webapps/share/modules/documentlibrary directory of an Alfresco Community Edition installation. This function sets up the web script call based on the passed in parameters (that is, success.callback.fn, failure.message, webscript.name, and so on). There are a lot more parameters that we can use if we wanted to more stuff when calling the web script, such as firing an event after successful invocation.

    Here is a list from the documentation:

     /**
           * ACTION: Generic action.
           * Generic DocLib action based on passed-in parameters
           *
           * @method genericAction
           * @param action.success.event.name {string} Bubbling event to fire on success
           * @param action.success.event.obj {object} Bubbling event success parameter object
           * @param action.success.message {string} Timed message to display on success
           * @param action.success.callback.fn {object} Callback function to call on success.
           * <pre>function(data, obj) where data is an object literal containing config, json, serverResponse</pre>
           * @param action.success.callback.scope {object} Success callback function scope
           * @param action.success.callback.obj {object} Success callback function object passed to callback
           * @param action.success.activity.siteId {string} Site associated with activity
           * @param action.success.activity.activityType {string} Activity type to post
           * @param action.success.activity.page {string} Page to generate activity link to
           * @param action.success.activity.activityData {object} Metadata for activity type
           * @param action.failure.event.name {string} Bubbling event to fire on failure
           * @param action.failure.event.obj {object} Bubbling event failure parameter object
           * @param action.failure.message {string} Timed message to display on failure
           * @param action.failure.callback.fn {object} Callback function to call on failure.
           * <pre>function(data, obj) where data is an object literal containing config, json, serverResponse</pre>
           * @param action.failure.callback.scope {object} Failure callback function scope
           * @param action.failure.callback.obj {object} Failure callback function object passed to callback
           * @param action.webscript.stem {string} optional webscript URL stem
           * <pre>default: Alfresco.constants.PROXY_URI + "slingshot/doclib/action/"</pre>
           * @param action.webscript.name {string} data webscript URL name
           * @param action.webscript.method {string} HTTP method to call the data webscript on
           * @param action.webscript.queryString {string} Optional queryString to append to the webscript URL
           * @param action.webscript.params.siteId {string} current site
           * @param action.webscript.params.containerId {string} component container
           * @param action.webscript.params.path {string} path where file is located
           * @param action.webscript.params.file {string} file to be deleted
           * @param action.webscript.params.nodeRef {string} noderef instead of site, container, path, file
           * @param action.wait.message {string} if set, show a Please wait-style message during the operation
           * @param action.config {object} optional additional request configuration overrides
           * @return {boolean} false: module not ready
           */

    The web script we are going to call is registered on the sample/fileinfo?nodeRef={nodeRef} URL. This is a new custom web script that just takes a node reference as a parameter and then fetches some properties for this node in the controller. The web script template will send back a JSON response with the data for these properties. We will implement it in the next step. The full web script URL that is invoked when the action is executed looks something like this:

    http://localhost:8080/share/proxy/alfresco/sample/fileinfo?nodeRef=workspace://SpacesStore/cbb63e68-9884-4d24-abb3-28aaf8677169

    The call to the repository web script is proxied via Share so authentication credentials etc are managed automatically for us. There will be no Login dialog popping up.

    If the web script is invoked successfully we call the Alfresco.util.PopupManager.displayPrompt( function from the success callback to display the response from the web script. The success callback is implemented with the org_alfresco_training_onActionCallWebScriptSuccess function.

  5. Implement the File Info web script that is indirectly invoked by the Call-Web-Script action.

    We will add this web script to the repository JAR project that comes with the All-In-One project when it is generated.

    Add a web script descriptor file called file-info.get.desc.xml to the aio/aio-platform-jar/src/main/resources/alfresco/extension/templates/webscripts directory. Define it as follows:

    <webscript>
        <shortname>Sample Webscript that returns Audit data</shortname>
        <description>Returns the audit data for file with passed in node reference</description>
        <url>/sample/fileinfo?nodeRef={nodeRef}</url>
        <authentication>user</authentication>
        <format default="json"></format>
    </webscript>

    Then add the controller file called file-info.get.js:

    var nodeRef = args["nodeRef"];
    var fileNode = search.findNode(nodeRef);
    
    model["name"] = fileNode.name;
    model["creator"] = fileNode.properties.creator;
    model["createdDate"] = fileNode.properties.created;
    model["modifier"] = fileNode.properties.modifier;
    model["modifiedDate"] = fileNode.properties.modified; 

    Finally add the template file called file-info.get.json.ftl:

    <#assign datetimeformat="EEE, dd MMM yyyy HH:mm:ss zzz">
    {
        "name"          : "${name}",
        "creator"       : "${creator}",
        "createdDate"   : "${createdDate?string(datetimeformat)}",
        "modifier"      : "${modifier}",
        "modifiedDate"  : "${modifiedDate?string(datetimeformat)}"
    }
  6. The implementation of the Call-Web-Script DocLib action is now complete, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  7. Now, log in to Share (http://localhost:8080/share) and upload a file to some folder. You will see the new Call-Web-Script action in the Browse view when hovering over the file and clicking More... in the pop-up menu:

    Note that the Send-As-Email action might not be displayed in the pop-up menu if it has already been invoked. Invoking the "Call Web Script" action will display the following dialog with the web script JSON response (if the invocation was successful):

Implementing the Show-Custom-Message DocLib Action.

  1. Define and configure the Show-Custom-Message action

    This action is just executing some JavaScript code that shows a message, it will not call a repository action or a repository web script. It just demonstrates how to invoke some JavaScript code on the client side without involving the repository and the server side.

    Open the add-doclib-actions-extension-modules.xml Surf Extension module file that we have used so far in this tutorial, it is located in the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory.

    Then define the Show-Custom-Message DocLib action as follows:

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocLibActions">
                        <actions>
                        ...
                            <action id="alfresco.tutorials.doclib.action.showCustomMessage"
                                    icon="showmsg"
                                    type="javascript"
                                    label="alfresco.tutorials.doclib.action.showCustomMessage.label">
                                <param name="function">onShowCustomMessage</param>
                            </action>
                        </actions>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension> 

    This action is also of type javascript in the same way the previous actions have been.This action will call a custom JavaScript function called onShowCustomMessage. The showmsg-16.png icon for this action should already be available if you implemented the Send-As-Email action above.

  2. Add labels and messages for the Show-Custom-Message to the i18n resource file .

    Open up the aio-share-jar.properties file locate din the aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/ directory. Then add the following properties to it:

    alfresco.tutorials.doclib.action.showCustomMessage.text=Showing custom message for {0} and {1}
    alfresco.tutorials.doclib.action.showCustomMessage.label=Show Message
  3. Define where in the user interface the Show-Custom-Message action should be displayed.

    This is done in the add-doclib-actions-extension-modules.xml file in the section called actionGroups::

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocLibActions">
                        <actions>
                         ...
                        </actions>
                        
                         <actionGroups>
                            <actionGroup id="document-browse">
                                ...
                                <action index="402" id="alfresco.tutorials.doclib.action.showCustomMessage" />
                            </actionGroup>
                            <actionGroup id="document-details">
                                ...
                                <action index="402" id="alfresco.tutorials.doclib.action.showCustomMessage" />
                            </actionGroup>
                        </actionGroups>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension>
    The Show-Custom-Message action will be displayed in the same views as the other actions that we have implemented. We give it an index of 402 so it is displayed just after the Call-Web-Script action.
  4. Implement the custom JavaScript function that is invoked by the Show-Custom-Message action.

    This JavaScript function can go into the same file as the Call-Web-Script function, open the custom-doclib-actions.js file located in the aio/aio-share-jar/src/main/resources/META-INF/resources/aio-share-jar/components/documentlibrary directory and add the code as follows:

    (function () {
        YAHOO.Bubbling.fire("registerAction",
            {
                actionName: "onShowCustomMessage",
                fn: function org_alfresco_training_onShowCustomMessage(file) {
                    Alfresco.util.PopupManager.displayMessage(
                        {
                            text: this.msg("alfresco.tutorials.doclib.action.showCustomMessage.text",
                                file.displayName, Alfresco.constants.USERNAME)
                        });
                }
            });
    
        YAHOO.Bubbling.fire("registerAction",
            {
                actionName: "onActionCallWebScript",
               ...
            });
    })();

    The only thing we do in this action code is to display a message with the help of the Alfresco.util.PopupManager.displayMessage function.

  5. The implementation of the Show-Custom-Message DocLib action is now complete. This is probably the smallest DocLib action backed by JavaScript code that you might come across. To try it out build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  6. Now, log in to Share (http://localhost:8080/share). You will see the new Show-Custom-Message action in the Browse view when hovering over a file and clicking More... in the pop-up menu:

    Note that the Send-As-Email action is not displayed in the pop-up menu as it has already been invoked, see the indicator. Invoking the "Show Message" action will display the following message temporary:

Implementing the Go-to-Google DocLib Action.

  1. Define and configure the Go-to-Google action.

    This action is different from all the others that we have implemented in that it is not backed by a specific JavaScript function. Instead it is of type link and just takes you to the Google search home page when clicked.

    Open the add-doclib-actions-extension-modules.xml Surf Extension module file that we have used so far in this tutorial, it is located in the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory.

    Then define the Go-to-Google DocLib action as follows:

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocLibActions">
                        <actions>
                        ...
                            <action id="alfresco.tutorials.doclib.action.goToGoogle"
                                    icon="google"
                                    type="link"
                                    label="alfresco.tutorials.doclib.action.goToGoogle.label">
                                <param name="href">http://www.google.com</param>
                                <param name="target">_blank</param>
                            </action>
                        </actions>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension> 

    The google-16.png icon for this action should already be available if you implemented the Send-As-Email action above.

  2. Add the label for the Go-to-Google action to the i18n resource file .

    Open up the aio-share-jar.properties file locate din the aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/ directory. Then add the following property to it:

    alfresco.tutorials.doclib.action.goToGoogle.label=Go to Google
  3. Define where in the user interface the Go-to-Google action should be displayed.

    This is done in the add-doclib-actions-extension-modules.xml file in the section called actionGroups::

    <extension>
        <modules>
            <module>
                <id>Add Document Libarary Actions (Send-as-Email, Call WS, Show Msg, Go to Google)</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocLibActions">
                        <actions>
                         ...
                        </actions>
                        
                         <actionGroups>
                            <actionGroup id="document-browse">
                                ...
                                <action index="403" id="alfresco.tutorials.doclib.action.goToGoogle" />
                            </actionGroup>
                            <actionGroup id="document-details">
                                ...
                                <action index="403" id="alfresco.tutorials.doclib.action.goToGoogle" />
                            </actionGroup>
                            <actionGroup id="folder-browse">
                                <action index="403" id="alfresco.tutorials.doclib.action.goToGoogle" />
                            </actionGroup>
                        </actionGroups>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension>
    The Go-to-Google action will be displayed in the same views as the other actions that we have implemented, plus for folders when in Browse view. We give it an index of 403 so it is displayed after all the other custom DocLib actions that we have implemented.
  4. The implementation of the Go-to-Google DocLib action is now complete, not much to it really, it's very easy to add a DocLib action for navigating to an external page. To try it out build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  5. Now, log in to Share (http://localhost:8080/share). You will see the new Go-to-Google action in the Browse view when hovering over for example a folder and clicking More... in the pop-up menu:

    Note that the other DocLib actions are not visible when you look at the Folder actions. This is because they have not been configured to be visible for this view type. Invoking the "Go to Google" action will open up www.google.com in a different tab in the Browser.
Parent topic: Document Library [227]

Adding new metadata templates to the Document Library

Name Adding new metadata templates to the Document Library
Extension Point Document Library [40]
Description

When custom content models are deployed to the repository it is sometimes a requirement to display properties from these in the Document Library Browse view. This can be done with so called Metadata Templates, which are tied to an evaluator that decides if the template is applicable or not to the content item in question, such as a folder or a file.

If there is no specific Metadata Template defined for a content item type then it falls back on a default Metadata template that looks like this (all out-of-the-box Metadata Templates can be found in alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco/share-documentlibrary-config.xml):
<template id="default">
    <banner index="10" id="lockBanner" evaluator="evaluator.doclib.metadata.hasLockBanner">{lockBanner}</banner>
    <banner index="20" id="syncTransientError" evaluator="evaluator.doclib.metadata.hasSyncTransientErrorBanner">{syncTransientError}</banner>
    <banner index="30" id="syncFailed" evaluator="evaluator.doclib.metadata.hasSyncFailedBanner">{syncFailed}</banner>
    <line index="10" id="date">{date}{size}</line>
    <line index="20" id="description" view="detailed">{description}</line>
    <line index="30" id="tags" view="detailed">{tags}</line>
    <line index="40" id="categories" view="detailed" evaluator="evaluator.doclib.metadata.hasCategories">{categories}</line>
    <line index="50" id="social" view="detailed">{social}</line>
 </template> 

This template gives you the basic information for the node, such is in the following example for a file:

This tutorial will demonstrate how to add a custom DocLib Metadata Template for a custom type from a content model that comes with the SDK Samples. This content model has a type called acme:document that contains a property called acme:documentId (for more info see aio/aio-platform-jar/src/main/resources/alfresco/module/aio-platform-jar/model/content-model.xml). We will create a new template that displays this custom property. The template will be based on the default template that you can see above and the property will use the default presentation rendering.

The tutorial will also show how you can render a property in a custom way in your Metadata template.

Implementation Steps Adding a new Metadata Template to the Document Library involves the following steps:
  1. Configure the template so it is known to Share (typically in a Surf Extension Module)
  2. Add an evaluator that controls for what content nodes (i.e. file, folder, etc.) the template is applicable
  3. Add property labels to the i18n resource file(s)
  4. (Optionally) Implement custom client side JavaScript code that renders the property in a custom way
Related Information This tutorial assumes that you are familiar with the Document Library in Share. If you are new to it read up on it here [252] before starting this tutorial. Also, familiar yourself with how Surf Extension Modules [41] work as we will be creating one of those.
Source Code Go to code [257]
This tutorial assumes you have created a new SDK All-In-One [59] project.

This tutorial will demonstrate the following:

  • How to create a custom DocLib Metadata Template for the acme:document type, which is part of the SDK AIO project by default. The template will be based on the default one but will also display the acme:documentId property.
  • How to define an evaluator for a custom content type
  • How to add another field to the custom template that displays the acme:documentId property in a custom way (i.e. custom rendering)

Tutorial implementation steps:

Implementing the custom Metadata Template for the acme:document type.

  1. Add a new Surf Extension Module file and define the Metadata Template

    Call the file add-metadata-template-doclib-extension-modules.xml and save it in the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory (note. it is important to give this file a unique name when several Share JARs are installed, otherwise the last one wins).

    Then define the custom Metadata Template as follows:

    <extension>
        <modules>
            <module>
                <id>Add Acme Document Metadata Template</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocumentLibrary">
                        <metadata-templates>
                            <template id="acmeDocMetadataTemplate">
                                <evaluator>alfresco.tutorials.evaluator.isAcmeDocument</evaluator>
                                <banner index="10" id="lockBanner" evaluator="evaluator.doclib.metadata.hasLockBanner">{lockBanner}</banner>
                                <banner index="20" id="syncTransientError" evaluator="evaluator.doclib.metadata.hasSyncTransientErrorBanner">{syncTransientError}</banner>
                                <banner index="30" id="syncFailed" evaluator="evaluator.doclib.metadata.hasSyncFailedBanner">{syncFailed}</banner>
                                <line index="10" id="date">{date}{size}</line>
                                <line index="20" id="description" view="detailed">{description}</line>
                                <line index="30" id="tags" view="detailed">{tags}</line>
                                <line index="40" id="categories" view="detailed" evaluator="evaluator.doclib.metadata.hasCategories">{categories}</line>
                                <line index="50" id="acmeDocId" view="detailed">{acme_documentId org.alfresco.tutorial.label.acme_documentId}</line>
                                <line index="60" id="social" view="detailed">{social}</line>
                            </template>
                        </metadata-templates>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension> 

    What we have done here is basically copied the metadata template with the identifier <template id="default"> from the /alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco/share-documentlibrary-config.xml configuration file. Then added the line with id="acmeDocId" just before the social properties. We have also added a custom evaluator to the template that will only return true if the node in question has the type acme:document applied.

    The different attributes and sub-elements for the template element have the following meaning:

    Name Description
    template id

    The global identifier for this template. Make sure to change it after copying from out-of-the-box templates, otherwise you will override those. So change it from default to acmeDocMetadataTemplate.

    banner

    Message banner that will display above the node name. A common message that you might see is the one about a node being locked by another user for editing.

    banner id

    Unique identifier for this banner item.

    line

    One line in the template displaying label and value for a property. The text content of the line element consist of the property value and optionally the label to use, such as {lockBanner} and {acme_documentId org.alfresco.tutorial.label.acme_documentId}. The Acme Doc Id line specifies the content model property we want to display (i.e. acme:documentId, note that we use underscore instead of colon when specifying the type in the template) and the label we want to use (i.e. org.alfresco.tutorial.label.acme_documentId).

    line id

    Unique identifier for this line item.

    index

    For banner items: determines the order the banner messages are displayed in. The lower the index the higher up it is displayed.

    For line items: determines the order the properties are displayed in. The lower the index the higher up it is displayed.

    evaluator

    Determines the overall applicability of this template for a content node (e.g. file, folder etc.), if it evaluates to false then the template will not be used and it falls back on the default one. A banner or line item can also have a boolean evaluator associated with it that will determine if the item should be displayed or not.

    view

    Determines in what Browse view the line item should be displayed. Can be simple or detailed. If not specified the property will be displayed in both views (e.g. date in above template). So our acmeDocId line item will only be displayed in the detailed view.
  2. Add an i18n resource file that will contain the property labels and messages for the Metadata Template.

    We can use the existing aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/aio-share-jar.properties file for this. Add the following property to it:

    org.alfresco.tutorial.label.acme_documentId=Acme Doc ID
  3. Define a custom evaluator for the custom Metadata Template.

    This evaluator should return true if the node is of type acme:document. This is done in the aio/aio-share-jar/src/main/resources/alfresco/web-extension/aio-share-jar-slingshot-application-context.xml Spring context file:

    ...
        <bean id="alfresco.tutorials.evaluator.isAcmeDocument" parent="evaluator.doclib.action.nodeType">
            <property name="types">
                <list>
                    <value>acme:document</value>
                </list>
            </property>
        </bean>
    </beans>
    Here we are using a built in evaluator called evaluator.doclib.action.nodeType. It can be used to evaluate if a node is of a specific type. We set the bean id so it matches what we specified above as <metadata-template><evaluator>. We add only the content type QName for the Acme Document type (i.e. acme:document) in the list.

    There are a number of predefined evaluators (i.e. out of the box evaluators ready to use):

    • Has aspect
    • Is mimetype
    • Property not Null
    • Site preset
    • Site / No Site
    • Container Type
    • Node Type
    • Always false
    • Value-based
    • Metadata value
    • Is Browser (type)
    • Is Portlet mode

    See the slingshot-documentlibrary-context.xml file located in the alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco directory of your Alfresco Community Edition installation for more information about out-of-the-box evaluators.

  4. The implementation of the custom Metadata Template is now complete. However, before we start the server up we need to make sure we have the Share JAR installed that provides the Create Acme Document [258] feature. This will make it easy to create a new text document with the specific acme:document type so we can test our new Metadata Template. Download the source and include the JAR in your AIO project.
  5. Build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  6. Now, login to Share (http://localhost:8080/share) and you will see the new Create... | Create an Acme Text Document menu item as follows:

    Clicking the new menu item brings up a form that looks like this:

    Note the custom field for the document identifier at the bottom of the form. Fill in some values for the Name, Title, and Descriptor fields. Give the Document Identifier a value of DOC001 and then click the Create button.
  7. The Acme Document file should now display in the Browse view with the custom metadata template:

Implementing custom rendering for the Document Identifier field.

  1. Add an extra field representing the Acme Doc Id custom rendered

    Open the add-metadata-template-doclib-extension-modules.xml Surf Extension module file again, it is located in the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory.

    Then update the Metadata Template definition so it has the extra Acme Document Id field at the end:

    <extension>
        <modules>
            <module>
                <id>Add Acme Document Metadata Template</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocumentLibrary">
                        <metadata-templates>
                            <template id="acmeDocMetadataTemplate">
                                <evaluator>alfresco.tutorials.evaluator.isAcmeDocument</evaluator>
                                <banner index="10" id="lockBanner" evaluator="evaluator.doclib.metadata.hasLockBanner">{lockBanner}</banner>
                                <banner index="20" id="syncTransientError" evaluator="evaluator.doclib.metadata.hasSyncTransientErrorBanner">{syncTransientError}</banner>
                                <banner index="30" id="syncFailed" evaluator="evaluator.doclib.metadata.hasSyncFailedBanner">{syncFailed}</banner>
                                <line index="10" id="date">{date}{size}</line>
                                <line index="20" id="description" view="detailed">{description}</line>
                                <line index="30" id="tags" view="detailed">{tags}</line>
                                <line index="40" id="categories" view="detailed" evaluator="evaluator.doclib.metadata.hasCategories">{categories}</line>
                                <line index="50" id="acmeDocId" view="detailed">{acme_documentId org.alfresco.tutorial.label.acme_documentId}</line>
                                <line index="60" id="social" view="detailed">{social}</line>
                                <line index="70" id="acmeDocIdCustom" view="detailed">{acmeDocumentIdCustomRendition org.alfresco.tutorial.label.acme_documentId}</line>
                            </template>
                        </metadata-templates>
                    </config>
    
                    <config evaluator="string-compare" condition="DocLibCustom">
                        <dependencies>
                            <js src="components/documentlibrary/custom-metadata-template-renderer.js"/>
                        </dependencies>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension> 

    Here we have added an extra line identified with the id="acmeDocIdCustom" that will represent the custom rendered document identifier. The custom rendering will be done via some client side JavaScript code that is going to be associated with the line via the property name acmeDocumentIdCustomRendition. The custom JavaScript code will be loaded via the above DocLibCustom definition that loads a new JavaScript file called custom-metadata-template-renderer.js. This file needs to be created next.

  2. Add custom JavaScript file with the rendering code

    Add a JavaScript file called custom-metadata-template-renderer.js to the aio/aio-share-jar/src/main/resources/META-INF/resources/aio-share-jar/components/documentlibrary directory. Then add the following function:

    (function () {
        YAHOO.Bubbling.fire("registerRenderer",
            {
                propertyName: "acmeDocumentIdCustomRendition",
                renderer: function acmeDocumentId_renderer(record, label) {
                    var jsNode = record.jsNode,
                        properties = jsNode.properties,
                        html = "";
                    var acmeDocId = properties["acme:documentId"] || "";
                    html = '<span>' + label + '<h2>' + acmeDocId + '</h2></span>';
    
                    return html;
                }
            });
    })();
    The important thing here is that the propertyName matches what we got in the line element text content in the metadata template, which is acmeDocumentIdCustomRendition. There is not much of a fancy rendering going on here, we just change the presentation of the property value so it is displayed as header <h2>.
  3. The implementation of the custom rendering of the property is now complete, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  4. Now, log in to Share (http://localhost:8080/share) and look at the file previously created, you should see the extra Acme Doc Id field with the value in h2 style:

Parent topic: Document Library [227]

Adding a menu item to the "Create..." menu in DocLib

Name Adding a menu item to the "Create..." menu in Document Library
Extension Point Surf Extension Modules [41]
Description This tutorial demonstrates how to add a new menu item called "Create an Acme Text Document" to the "Create..." menu that is available in the browse view in the Document Library. When the new menu item is selected it will prompt the user for document name, title, description, and text content. When the user clicks Create to create the document it will be created with a custom type set. Because the document is created with a custom type we also need to configure a "create" form for this type, which this tutorial shows how to do.

The general take away from this tutorial is that most of the configuration that is normally done in the share-config-custom.xml file can also be done with Surf Extension Modules, which makes it possible to enable and disable the configuration at runtime.

Implementation Steps Adding a new content create item in the Document Library usually involves the following steps:
  1. Create a custom content model, with the type that should be set on content when using the new create content action.
  2. Generate/Use a Repo JAR project to contain the custom model
  3. Create a Surf Extension Module containing the create action definition and the form definition.
  4. Generate/Use a new Share JAR project to contain the Surf Extension Module
Related Information This tutorial assumes that you are familiar with the Document Library in Share. If you are new to it read up on it here [75] before starting this tutorial. Also, familiar yourself with how you can create a text document via the Create... | Plain Text... menu item as it is similar to what we are going to do in this tutorial.
Source Code Go to code [258]
This tutorial assumes you have created a new SDK All-In-One [59] project.

Sometimes when you have a custom content model it is useful to be able to create new documents with a custom type set automatically, and at the same time also collect values for the type's custom properties. All directly from the Share user interface. This can be done by adding menu items to the Create... menu in the Document Library.

Tutorial implementation steps:

  1. Add a custom content model and type.

    This tutorial assumes that we have a custom type to work with. So we are going to create one in a new custom content model and include it in the Repo JAR that comes with the All-In-One project.

    The Repo JAR already got a file where we can start adding our custom content model. Open up the aio/aio-platform-jar/src/main/resources/alfresco/module/aio-platform-jar/model/content-model.xml file and update it so it looks like this:
    <?xml version="1.0" encoding="UTF-8"?>
    <model name="acme:contentModel" xmlns="http://www.alfresco.org/model/dictionary/1.0">
    
        <!-- Optional meta-data about the model -->
        <description>Document Model for the fictional company Acme</description>
        <author>James Alfresco</author>
        <version>1.0</version>
    
        <imports>
            <!-- Import Alfresco Dictionary Definitions -->
            <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
            <!-- Import Alfresco Content Domain Model Definitions -->
            <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
            <!-- Import Alfresco System Model Definitions -->
            <import uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
        </imports>
    
        <!-- Custom namespace for your domain -->
        <namespaces>
            <namespace uri="http://www.acme.org/model/content/1.0" prefix="acme"/>
        </namespaces>
    
        <!-- ===============================================================================================================
            Constraints, Types, and Aspects go here...
            -->
        <types>
            <!--
                Enterprise-wide Document root type
            -->
            <type name="acme:document">
                <title>Base document type</title>
                <parent>cm:content</parent>
                <properties>
                    <property name="acme:documentId">
                        <title>Document Identification Number</title>
                        <type>d:text</type>
                    </property>
                </properties>
            </type>
    
        </types>
        ...
    </model>
    Here we have defined a new name space for the model called acme, and then added a new type to it called document. The type has one custom property called documentId that can be used to keep an identifier for the document for easier lookup. We will use this type when creating the text document from the Share UI.
  2. Add a new Surf Extension Modules file called add-create-menuitem-doclib-extension-modules.xml to the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions directory (note. it is important to give this file a unique name when several Share JARs are installed, otherwise the last one wins):

    <extension>
        <modules>
            <!-- This module is dependent on the custom content model setup in the repo-amp module -->
            <module>
                <id>Add a new menu item to Create... menu in DocLib</id>
                <version>1.0</version>
                <auto-deploy>true</auto-deploy>
                <configurations>
                    <config evaluator="string-compare" condition="DocumentLibrary">
                        <create-content>
                            <content id="acme-plain-text" label="create.acmedoc.menu.item.label" icon="text" type="pagelink">
                                <param name="page">create-content?destination={nodeRef}&amp;itemId=acme:document&amp;mimeType=text/plain</param>
                            </content>
                        </create-content>
                    </config>
    
                    <config evaluator="model-type" condition="acme:document">
                        <forms>
                            <form>
                                <field-visibility>
                                    <show id="cm:name"/>
                                    <show id="cm:content" force="true"/>
                                    <show id="cm:title" force="true"/>
                                    <show id="cm:description" force="true"/>
                                    <show id="acme:documentId" force="true"/>
                                    <show id="mimetype"/>
                                    <show id="app:editInline" force="true"/>
                                </field-visibility>
                                <appearance>
                                    <field id="cm:name">
                                        <control>
                                            <control-param name="maxLength">255</control-param>
                                        </control>
                                    </field>
                                    <field id="cm:title">
                                        <control template="/org/alfresco/components/form/controls/textfield.ftl"/>
                                    </field>
                                    <field id="cm:content" label-id="">
                                        <control>
                                            <control-param name="editorAppearance">explorer</control-param>
                                        </control>
                                    </field>
                                    <field id="acme:documentId">
                                        <control template="/org/alfresco/components/form/controls/textfield.ftl"/>
                                    </field>
                                    <field id="mimetype">
                                        <control template="/org/alfresco/components/form/controls/hidden.ftl">
                                            <control-param name="contextProperty">mimeType</control-param>
                                        </control>
                                    </field>
                                    <field id="app:editInline">
                                        <control template="/org/alfresco/components/form/controls/hidden.ftl">
                                            <control-param name="contextProperty">editInline</control-param>
                                        </control>
                                    </field>
                                </appearance>
                            </form>
                        </forms>
                    </config>
                </configurations>
            </module>
        </modules>
    </extension>
    This extension module first configures the new menu item for the Create... menu. These create-content menu items can be of three different types (matching the usual DocLib action configuration):
    • link - accepts a href param that will be passed a nodeRef token for substitution, used for external links.
    • pagelink - accepts a page param that will be passed a nodeRef token for substitution, used for Share links.
    • javascript - accepts a function param of an action that will get the current folder item as first argument.
    This new menu item should create a new text document with a custom type applied so the page parameter is set to point to the create form page in Share (that is, /create-content?destination={nodeRef}...). And the itemId=acme:document parameter specifies what custom type that should be applied to the new text document. The create-content page has a form manager that will look for a create form matching the acme:document type, so we need to define one.

    To create a new form for the create-content page we define a configuration section matching the <config evaluator="model-type" condition="{content model type}"> pattern. The easiest way to get going with these types of forms is to lookup the form for the type that the new custom type is extending, in our case the cm:content type. All the out-of-the-box form definitions can be found in the tomcat/webapps/share/WEB-INF/classes/alfresco/share-form-config.xml file. Search for <config evaluator="model-type" condition="cm:content"> to get the form. Then just change the condition and add any custom properties such as acme:documentId.

  3. Add an i18n resource file with the create content action label.

    We can use the existing aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/aio-share-jar.properties file for this. Add the following property to it:

    create.acmedoc.menu.item.label=Create an Acme Text Document
    It is also possible to skip these resource label properties all together, and just type in the label directly in the create action definition, if for example the system should only support English:
    <content id="acme-plain-text" label="Create an Acme Text Document" icon="text" type="pagelink"> 
  4. The implementation of this sample is now done, build and start the application server as follows:

    /all-in-one$ ./run.sh build_start
  5. Now, log in to Share (http://localhost:8080/share) and you will see the new Create... | Create an Acme Text Document menu item as follows:

    Clicking the new menu item brings up a form that looks like this:

    Note the custom field for the document identifier at the bottom of the form.
    If you wanted to hide all other file and folder create actions, and just display your custom one. Then use replace="true" in the configuration so it looks like this:
    <config evaluator="string-compare" condition="DocumentLibrary" replace="true">
                            <create-content>...
    Further on, if you wanted to for example display the other create actions only for users and groups with certain permissions you could use the following type of configuration:
    <config evaluator="string-compare" condition="DocumentLibrary" replace="true">
                                ...
        <content id="folder" label="create-content.folder" icon="folder" index="5" type="javascript">
           <param name="function">onNewFolder</param>
           <permissions>
              <permission allow="true">CustomCreateContentPermission</permission>
           </permissions>
        </content>...
    See here [259] for more information about how to create custom permissions.
    Note: A Surf Extension module like this can be deployed and undeployed during runtime. And this means that an Administrator can control when different customizations should be visible or hidden. This is managed via the Module deployment page that can be found at: http://localhost:8080/share/service/modules/deploy.
Parent topic: Document Library [227]

Customizing a Surf JavaScript Widget in the Document Library

Name Customizing a Surf JavaScript Widget in the Document Library
Extension Point Surf Widgets [46] and Surf Extension Modules [41]
Description This tutorial demonstrates how to customize an existing Surf JavaScript Widget by extending the out-of-the-box Documentlist widget so it shows a message every time a filter is changed.

In previous versions of Alfresco Community Edition it was only possible to customize JavaScript widgets by copying existing code, modifying it, and then copying it onto the web extensions path. This was not efficient as it created a maintenance burden as the code needed to be managed through changes to the original widget.

Now logic and metadata about widget instantiation has been moved from the FreeMarker templates and moved into the JavaScript controller as this is easier to customize. The metadata is stored as a standardized object structure in the model, which is then processed by a new custom directive in the FreeMarker template to output the JavaScript code necessary to instantiate the specified widgets.

Existing JavaScript controller extension capabilities can be used so that Surf Extension Modules [41] can modify the default metadata object(s) to change the following:

  • The name of the JavaScript widget to be instantiated
  • The arguments passed when instantiating the widget
  • The variable that the instantiated widget is assigned to
  • Whether or not i18n messages are set for the widget
  • Whether or not additional options are applied to the widget
  • The additional options that should be applied to the widget

FreeMarker templates use a common "boiler-plate" structure to ensure consistency across web script rendered components. Updated resource handling features in Surf are used to move all the CSS and JavaScript dependency requests into the template and remove the associated *.head.ftl file. A consistent pattern of <@markup> directives is used throughout the template to further enhance customization options.

The general take away from this tutorial is that most JavaScript Widget customizations that was previously done by changing out-of-the-box JavaScript code, can now be done via Surf Extension Modules [41] and JavaScript object extensions.

Implementation Steps Customizing the Documentlist Widget in the Document Library involves the following steps:
  1. Generate/Use a Share JAR project (either stand-alone or as part of an All-in-One project)
  2. Create a Surf Extension Module containing the mapping between out-of-the-box widget and custom widget.
  3. Implement the custom Widget
  4. Override the documentlist-v2 web script to swap in the custom Widget
  5. Deploy the Surf Extension Module manually if it is not auto-deployed
Related Information This tutorial assumes that you are familiar with the Document Library in Share. If you are new to it, see Share Document Library [75] before starting this tutorial. Also, familiar yourself with how you can switch between different filters in the Document Library (that is, the navigation menu to the left in the DocLib).
This tutorial assumes you have created a new SDK All-In-One [59] project.

Tutorial implementation steps:

  1. In the AIO project open up the file aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions/aio-share-jar-example-widgets.xml. This Surf Extension Module file is automatically generated when the AIO project is created.
  2. Add the following module configuration (there is already a module defined, add this one after it):

    <module>
    	<id>Custom DocumentList Widget</id>
    	<description>Instantiate a custom DocumentList widget</description>
    	<customizations>
    		<customization>
    			<targetPackageRoot>org.alfresco.components.documentlibrary</targetPackageRoot>
    			<sourcePackageRoot>org.alfresco.tutorials.customization</sourcePackageRoot>
    		</customization>
    	</customizations>
    </module>                    
  3. Create the following directory: aio/aio-share-jar/src/main/resources/META-INF/resources/aio-share-jar/doclib/extension.
  4. In the doclib/extension directory create the custom Document list JavaScript Widget in the file custom-documentlist.js:

    // Declare namespace
    if (typeof Tutorials == undefined || !Tutorials) { var Tutorials = {}; }
    if (!Tutorials.custom) { Tutorials.custom = {}; }
    (function()
    {
      // Define constructor
      Tutorials.custom.DocumentList = function CustomDocumentList_constructor(htmlId)
      {
        Tutorials.custom.DocumentList.superclass.constructor.call(this, htmlId);
        return this;
      };
    
      // Extend default DocumentList
      YAHOO.extend(Tutorials.custom.DocumentList, Alfresco.DocumentList,
      {
        onFilterChanged: function CustomDL_onFilterChanged(layer, args)
        {
          // Call super class method
          Tutorials.custom.DocumentList.superclass.onFilterChanged.call(this, layer,args);
    
          // Pop-up a message
          Alfresco.util.PopupManager.displayMessage({
            text: "Filter Changed!"
          });
        }
      });
    })();                    
  5. Create the following directory: aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-webscripts/org/alfresco/tutorials/customization.
  6. In the tutorials/customization directory create the file documentlist-v2.get.html.ftl with the following contents:

    <@markup id="custom-documentlist-dependencies" target="js" action="after" scope="global">
      <@script src="${url.context}/res/doclib/extension/custom-documentlist.js" group="documentlibrary"/>
    </@markup>                    

    This loads our custom JavaScript widget class after the out-of-the-box JavaScript files used by the Document List widget.

  7. In the same tutorials/customization directory create the file documentlist-v2.get.js with the following contents:

    // Find the default DocumentList widget and replace it with the custom widget
    for (var i=0; i<model.widgets.length; i++) {
      if (model.widgets[i].id == "DocumentList") {
        model.widgets[i].name = "Tutorials.custom.DocumentList";
      }
    }

    This code changes the widget that is instantiated.

  8. Run /aio$ ./run.sh build_start to build and start up the customization.
  9. In Share, (http://localhost:8080/share/page/modules/deploy) deploy the new module.
  10. In Share, navigate to a Document Library within a Site. Changing the Document Filter (for example changing the view) will result in a pop up displaying the "Filter Changed!" message.
Parent topic: Document Library [227]

Conditional Rendering (Evaluators)

Tutorials related to conditional rendering of content and operations. We call this evaluations and implementation of evaluators.

.

  • Sub-Component Evaluations [120] This tutorial explores Evaluations and introduces Evaluators, demonstrating how they are defined and used.
  • Improving your Sub-Component Evaluations code [121] This tutorial improves your previous code by eliminating the hard-coded site name.
  • Selecting an evaluator [119] This tutorial demonstrates how to select an evaluator.
  • Creating a custom evaluator [118] This tutorial demonstrates how to create a custom evaluator.
Parent topic: Tutorials [4]

Sub-Component Evaluations

This tutorial explores Evaluations and introduces Evaluators, demonstrating how they are defined and used.
This tutorial assumes you have created a new SDK All-In-One [59] project.
This tutorial creates a module that conditionally renders the Site Members component on the site dashboard. If the site has a certain name the Site Members component will not be rendered.
  1. Open the file aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions/aio-share-jar-example-widgets.xml and add the following module definition:

    
    <extension>
        <modules>
        ...
            <module>
              <id>Module  (Conditionally Hide Site Members)</id>
                <components>
                   <component>
                      <region-id>component-1-1</region-id>
                      <source-id>site/{site}/dashboard</source-id>
                      <scope>page</scope>
                      <sub-components>
                         <sub-component id="default">
                            <evaluations>
                              <evaluation id="HideIfTestSite">
                                <evaluators>
                                     <evaluator type="beans.evaluator" ></evaluator>
                                 </evaluators>
                                  <render>false</render>
                              </evaluation>
                             </evaluations>
                           </sub-component>
                       </sub-components>
                    </component>
                </components>
            </module>
        </modules>
    </extension>
             

    Site dashboards are similar to user dashboards in that a new configuration is created from a preset for each new site. This means you need to target your Component using the site parameter.

    Attention: The Evaluator type attribute must map to a Spring bean ID defined in the application context. Therefore, you need to create this Evaluator and define it as a Spring bean.
  2. In the aio/aio-share-jar/src/main/java/org/alfresco/tutorial package create a new class called SiteMembersEvaluator.
  3. In the Package Explorer double-click the file SiteMembersEvaluator.java to load it into the Eclipse editor (if not already showing).
  4. Replace the code currently in SiteMembersEvaluator.java, with the following content:

    package org.alfresco.tutorial;
    import java.util.Map;
    import org.springframework.extensions.surf.RequestContext;
    import org.springframework.extensions.surf.extensibility.impl.DefaultSubComponentEvaluator;
    
    public class SiteMembersEvaluator  extends DefaultSubComponentEvaluator
    {
         public boolean evaluate(RequestContext context, Map<String, String> params)
         {
                boolean result;
                String site = context.getUriTokens().get( "site" );
                if (site ==  null )
                {
                    site = context.getParameter( "site" );
                 }
                 result = (site !=  null && site.equals( "test-site" ));
                 return result;
         }
    }
    
    
    Attention: At this point your code will be showing compile errors. You will need to link in the required Spring Framework libraries.

    The Spring bean compares the site name with the hard-coded string "test-site" (the site URL). The returned result will be true if the site URL matches the hard-coded string, causing the evaluator to be true, thus preventing rendering of the Site Members component.

  5. Open the aio/aio-share-jar/src/main/resources/alfresco/web-extension/aio-share-jar-slingshot-application-context.xml file and add the Spring Bean configuration as follows:

    ...
    <beans>
    	<bean id="beans.evaluator" class="org.alfresco.tutorial.SiteMembersEvaluator" />
    </beans>
    
                            

    This configuration file maps the Java class to the Spring Bean ID.

  6. Build the project with maven. For example:

    /all-in-one$ ./run.sh build_start                                                
                        
  7. Log in to Share and create two new sites: Test Site and Sample Site.

    As the new module has not yet been deployed, you will see the Site Members on both sites.

  8. Now go to the Module Deployment WebScript http://localhost:8080/share/page/modules/deploy.
  9. Deploy the new module, Module (Conditionally Hide Site Members), as described in previous tutorials.

    When you refresh the dashboard pages for the Test Site and Sample Site sites, you will see that the Site Members component is displayed for Sample Site, but not for the Test Site.

Parent topic: Conditional Rendering (Evaluators) [228]

Improving your Sub-Component Evaluations code

This tutorial improves your previous code by eliminating the hard-coded site name.
In the previous tutorial you conditionally displayed a component based on a hard-coded string (the site URL). In this tutorial you will improve your code to pass the site URL as a parameter.
  1. Update the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/extensions/aio-share-jar-example-widgets.xml file so that the Evaluator is configured as follows:

                                
    <evaluator type="beans.evaluator">      
       <params>
         <site>sample-site</site> 
       </params>
    </evaluator>   
    
    Note that the site to evaluate on is now passed as a parameter.
    Note:
    When setting Evaluator parameters, the element name is the parameter key and the element value is the parameter value. All the configured parameters will be passed as the params argument when the evaluate method is called. Parameters can also accept token substitution – so you could set a parameter of:
    <site>{site}</site>

    This would pass in whatever the name of the site was. This would cause the Evaluator to pass, regardless of the site dashboard being displayed.

  2. Modify evaluate method in the SiteMembersEvaluator class as follows:

                     
    public boolean evaluate(RequestContext context, Map<String, String> params)   
    {        
        boolean result;        
        String site = context.getUriTokens().get( "site" );        
        if (site ==  null )        
        {            
           site = context.getParameter( "site" );        
         }          
         String targetSite = params.get( "site" );        
         result = (site !=  null && site.equals(targetSite));        
         return result;   
    }     
    
    
  3. Rebuild and redeploy the JAR by running the project build file.
  4. Restart the application server.

    You will now see that when you visit Sample Site the Site Members component is hidden and when visiting the Test Site site the Site Members component is displayed.

Parent topic: Conditional Rendering (Evaluators) [228]

Selecting an evaluator

This tutorial demonstrates how to select an evaluator.
This tutorial assumes you have at least one module deployed (it can be any of the modules you created in previous tutorials).
When a module is deployed it is evaluated before being processed. This evaluation determines whether or not the module is to be executed. By default the default evaluator, default.extensibility.evaluator is applied to determine if the module should be executed. However, it is possible to select a different evaluator to be applied, from a list of provided evaluators, or a custom coded evaluator. This can be done through configuration or through the Share Module Deployment user interface. This tutorial looks at how this is achieved in practice.
  1. In your browser navigate to http://localhost:8080/share/page/modules/deploy.

    Your modules and deployed modules will be displayed.

  2. Ensure that at least one module is deployed.
  3. Click on the deployed module and the Evaluator selector will appear to the right.
  4. In the Evaluator list box select the config.approval.evaluator evaluator.

    The evaluator properties will be displayed. In this case the config.approval.evaluator evaluator has the key apply. For this evaluator if the apply key is set to true the module will be processed. If, however the apply key is set to false the module will not be processed.

  5. Set the apply key to false.
  6. Click Update to register your changes.
  7. Click Apply Changes to have your changes saved to the database.
  8. Now, in another tab, browse to your dashboard to see if the module is applied or not.

    You will see that the module is no longer processed.

  9. Now back in the Module Deployment tab set the evaluator apply key to true.
  10. Click Update.
  11. Click Apply Changes.
  12. Navigate back to your dashboard tab and refresh.

    You will now see that your module is applied again.

You have seen how to use the Share Module Deployment page to set an evaluator for a module. This can also be achieved through configuration. In the module configuration file (extension-modules.xml in previous tutorials) you would add some XML to apply the evaluator:
...
<module>
    <id>Module (New Content)</id>
    <evaluator type="config.approval.evaluator">
        <params>
            <apply>false</apply>
        </params>
    </evaluator>
...
Parent topic: Conditional Rendering (Evaluators) [228]

Creating a custom evaluator

This tutorial demonstrates how to create a custom evaluator. You will create a custom evaluator class, wire it into Alfresco as a Spring bean, and learn how to use evaluator properties.
This tutorial assumes you have completed previous tutorials. This tutorial assumes you will be using the admin account. For testing convenience it will be useful if you create another user account before starting this tutorial.
In the previous tutorial you saw that the provided evaluators can be applied to a module. Different modules can have different evaluators applied to them. As well as using the provided evaluators, it is possible to create your own custom evaluator. This tutorial shows how to create a simple custom evaluator.
  1. In the aio/aio-share-jar/src/main/java/org/alfresco/tutorial package create a new class called SimpleModuleEvaluator.
  2. Add the following code to the class:

    package org.alfresco.tutorial;
    
    import java.util.Map;
    
    import org.springframework.extensions.surf.RequestContext;
    import org.springframework.extensions.surf.extensibility.ExtensionModuleEvaluator;
    
    public class SimpleModuleEvaluator implements ExtensionModuleEvaluator
    {
        public static final String USER_PROP = "user";
    
        public boolean applyModule(RequestContext context, Map<String, String> evaluationProperties)
        {
            String currUser = context.getUser().getId();
            String targetUser = evaluationProperties.get(USER_PROP);
            return (targetUser != null && targetUser.equals(currUser));
        }
    
        public String[] getRequiredProperties()
        {
            return new String[] { USER_PROP};
        }
    }
    
  3. Open the aio/aio-share-jar/src/main/resources/alfresco/web-extension/aio-share-jar-slingshot-application-context.xml file.
  4. Add a bean for your new evaluator:

    <?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="beans.evaluator" class="beans.SiteMembersEvaluator" />
    	<bean id="beans.simple.module.evaluator" class="org.alfresco.tutorial.SimpleModuleEvaluator" />
    </beans>
    
  5. Rebuild your project using the Ant build script as done in previous tutorials.
  6. Restart your application server.
  7. In a browser tab navigate to http://localhost:8080/share/page/modules/deploy.
  8. Click on a deployed module, to display the list of evaluators.
  9. From the list of evaluators select the evaluator, beans.simple.module.evaluator.

    You will see the evaluator property "user".

    Important: If you do not see the properties then try the following: close the Module Deployment tab. Create a new tab. Reload the Module Deployment page.
  10. Set the user evaluator property to admin.
  11. Click Update.
  12. Click Apply Changes.
  13. In another tab navigate to the admin dashboard.

    You will see that the module has been applied.

  14. Log out from the admin account and log into another account.

    On the new user account dashboard you will see that the module is not applied. This is because the module was applied conditionally to the user; the module is only applied for the admin user, as was set by using the evaluator property.

Parent topic: Conditional Rendering (Evaluators) [228]

Styling

Tutorials that deal with styling of the Share user interface. For example, how to create a custom theme.

  • Adding a custom Share Theme [93]
  • Customizing the Share Header Style (Aikau) [142]
Parent topic: Tutorials [4]

Adding a custom Share Theme

Name Adding a custom Share Theme
Extension Point Share Themes [39]
Description This tutorial demonstrates how to customize the look and feel of the Share user interface by creating a custom theme. The Share user interface is currently implemented with both Aikau code and YUI code. This has an impact on what you need to do to style the whole Share UI, as Aikau components are styled differently from YUI components.
Implementation Steps The following steps are usually needed to customize the Share UI using a custom theme:
  1. Copy one of the existing out-of-the-box themes and use it as a basis for the new theme
  2. Give the new theme a name
  3. Update the CSS files with the new theme name
  4. Create an XML file <new theme name>.xml to let Share know about the new theme
  5. Replace logos and other images
  6. Customize the look and feel of YUI components by updating presentation.css and skin.css
  7. Customize the look and feel of Aikau components by overriding LESS variables in <new theme name>.xml
Important: If you are upgrading to a newer Alfresco Community Edition version, and you are using a custom theme, then it is important to make sure that whatever out-of-the-box theme your custom theme is base on (such as Green Theme) has not changed between Alfresco Community Edition versions. For example, upgrading from version 5.0 to 5.1 will mean that all the out-of-the-box themes will have an extra images/logo-entreprise.png file. So if you upgrade to a newer version you will also have to upgrade your custom theme to match.
Related Information This tutorial assumes that you are familiar with the Share architecture. If you are new to it then read up on it here [245] before starting this tutorial.
Source Code Go to code [246]
This tutorial assumes you have create a new SDK All-In-One [59] project.
Tutorial implementation steps:
  1. Create a new themes directory under: aio/aio-share-jar/src/main/resources/META-INF.
  2. Copy an existing theme, such as the Green Theme, from alfresco/tomcat/webapps/share/themes/greenTheme into the new aio/aio-share-jar/src/main/resources/META-INF/themes directory.

    As you might have guessed, this requires you to actually download and install Alfresco Community Edition. To avoid having to do that you can also run the all-in-one project once and you will have the required theme resources in all-in-one/share/target/share/themes

  3. Rename the theme directory to a custom theme name.

    Change the aio/aio-share-jar/src/main/resources/META-INF/themes/greenTheme directory name to aio/aio-share-jar/src/main/resources/META-INF/themes/tutorialTheme.

  4. Update the theme name in the CSS files.

    In both the aio/aio-share-jar/src/main/resources/META-INF/themes/tutorialTheme/presentation.css file and the aio/aio-share-jar/src/main/resources/META-INF/themes/tutorialTheme/yui/assets/skin.css file, search for the greenTheme name and replace it with tutorialTheme.

  5. Add a theme's descriptor file called tutorialTheme.xml to the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/themes directory (you would have to create the themes directory too):

    <?xml version='1.0' encoding='UTF-8'?>
    <theme>
       <title>Tutorial Theme</title>
       <title-id>theme.tutorialTheme</title-id>
       <css-tokens>
       </css-tokens>
    </theme>

    The theme ID will be determined by the file name, so from now on this theme is identified by tutorialTheme. The title elements determine the title of the theme in the UI. The css-token element will be covered later on in this tutorial.

  6. Optionally add an i18n resource file with the Theme title label.

    We can use the existing aio/aio-share-jar/src/main/resources/alfresco/web-extension/messages/aio-share-jar.properties file for this. Add the following property to it:

    theme.tutorialTheme=Tutorial Theme
    The property name theme.tutorialTheme must match what you specified in the tutorialTheme.xml file as title-id. If you don't bother defining any resource label properties in this file, then the theme title will be taken from the title element in the tutorialTheme.xml file.
  7. Replace logos.

    A basic customization of Share usually involves replacing the Alfresco logos with company specific logos. The following list explains where the different logo image files are displayed in the user interface:

    • aio/aio-share-jar/src/main/resources/META-INF/themes/tutorialTheme/images/app-logo-48.png - this is the logo that shows in the title of each page.
    • aio/aio-share-jar/src/main/resources/META-INF/themes/tutorialTheme/images/logo.png - this is the logo that shows up on the Login page.
    Replace these with whatever logos you want, and make sure to keep the same image sizes.
  8. Customize the YUI components in the Share user interface.

    This is done by updating CSS styles in the aio/aio-share-jar/src/main/resources/META-INF/themes/tutorialTheme/presentation.css file and the aio/aio-share-jar/src/main/resources/META-INF/themes/tutorialTheme/yui/assets/skin.css file.

    For example, to get a theme with more of a purple touch go through and replace the greenish colours as follows:
    Green      Purple
    #92c15f -> #BDA0CB
    #00AE42 -> #7F00FF
    #5FAC34 -> #BF5FFF
    #3a6c38 -> #2E0854
    #009300 -> #A020F0
    #008F22 -> #A020F0
    #D4F8C4 -> #ECC8EC
    Replace only the color and background-color css tokens. To do more detailed customizations you will have to change the CSS classes in these files and see what the result looks like. This styling will not affect the Share Header with the menu and title, and any other Aikau components, how to do this is covered in the next step.
  9. Customize the Aikau components in the Share user interface.

    This is done by overriding LESS variables in the aio/aio-share-jar/src/main/resources/alfresco/web-extension/site-data/themes/tutorialTheme.xml file. Update it to look like this:

    <?xml version='1.0' encoding='UTF-8'?>
    <theme>
       <title>Tutorial Theme</title>
       <title-id>theme.tutorialTheme</title-id>
       <css-tokens>
          <!-- Aikau related LESS variables (requires Aikau 1.0.18) -->
          <less-variables>
             @header-background-color: #A020F0;         <!-- Purple -->
             @header-font-color: yellow;
             @header-hover-background-color: #BDA0CB;   <!-- Purple candy -->
             @header-hover-font-color: #ccc;
             @header-focus-background-color: #8A2BE2;
             @header-focus-font-color: red;
             @header-menubar-font-color: yellow;
             @header-dropdown-menu-font-color: yellow;
    
             @dashlet-background: #fff;
             @dashlet-border: 1px solid #bababa;
             @dashlet-border-radius: 0;
             @dashlet-title-background: #92c15f linear-gradient(to bottom, #a3d07a, #92c15f);
             @dashlet-title-border-bottom: 0;
             @dashlet-title-border-radius: 0;
             @dashlet-title-color: #fff;
             @dashlet-toolbar-background: #A020F0;
             @dashlet-toolbar-border-bottom: 1px solid #d3d3d3;
             @dashlet-body-background: #f9fcfd;
             @dashlet-body-border-radius: 0;
          </less-variables>
       </css-tokens>
    </theme>
    
    Here we have done some customizations to the Share header style by overriding a number of LESS variables starting with @header-. We also customize the styling of Aikau Dashlets by overriding the @dashlet- LESS variables. Note that by default there are no Aikau dashlets on any of the Dashboards. You would have to install for example the Reporting and Analytics module to get some Aikau dashlets, which you could then style like this.

    You are probably wondering where all the LESS variable names are coming from, and what others there are that can be overridden? Have a look in the defaults.less [239] file in the Aikau project. It contains the default values for all the LESS variables that are used by the Aikau components' CSS files.

    Note: The LESS variable substitution feature is only available in Aikau version 1.0.18 or newer. So you may need to u