Skip navigation
All Places > Alfresco Content Services (ECM) > Blog > 2011 > November
2011

Introduction



.


In Alfresco Community 4.0.a we included a lot of customization improvements to Share. One of them was to be able to swap out components on a page. This means that you can:






  • Add new components to a page


  • Remove a components from a page


  • Replace a component on a page




This post will show how you can do this using 1 single config file that you later will be able to deploy and undeploy (without restarting the server) using the module deployment ui in Share. In this blog post we will use Share's 'Document details' page to show these features, but once you have done this tutorial you will be able to switch out components on any page (except the dashboards which instead can be changed using Share's 'Customize dasboard' ui).


.


First lets take a look at the out of the box panels available on the Document Details page in Alfresco Community 4.0.a (the image to the left) and how the panels will look after we have applied the customization in this tutorial (the image to the right).


.




In short we will:


.






  1. Create a custom 'Acme Sample' document panel component


  2. Declare an extension module xml file for Acme which will.



    • Add the new 'Acme Sample' panel component above the 'Actions' panel.


    • Remove the 'Tags' panel component.


    • Replace the 'Permissions' panel with the 'Acme Sample' document panel.




  3. Package your extension as a .jar


  4. Deploy extension on your server




(In other words the 'Acme Sample' document panel will appear 2 times)


.


Note! If you want the full example source code, use this link: acme-test-extension.zip. Don't forget to rename it from .zip to .jar before placing it in TOMCAT_HOME/shared/lib!


Create a custom 'Sample' document panel for Acme



.


Create a minimal document details panel by first creating the following directoy structure /alfresco/site-webscripts/acme/components/document-details/ and inside it create the following files.


sample.get.desc.xml





<webscript>

   <shortname>Acme Test Sample</shortname>

   <description>Acme Test Document sample panel</description>

   <url>/acme/components/document-details/sample</url>

</webscript>




sample.get.html.ftl





<#assign el=args.htmlid?js_string/>

<div class='document-details-panel'>

  <h2 id='${el}-heading' class='thin dark'>${msg('heading')}</h2>

  <div>${msg('text')}</div>

  <script type='text/javascript'>//<![CDATA[

    Alfresco.util.createTwister('${el}-heading', 'DocumentMetadata');

  //]]></script>

</div>




sample.get.properties





heading=Acme Sample

text=This is a demo




Declare an extension module xml



.



Create the following directory structure: /alfresco/site-data/extensions/.

Inside that directory create the following file:


acme-test-extension.xml





<extension>

   <modules>

      <module>

         <id>Acme :: Test - Document Details Page</id>

         <evaluator type='default.extensibility.evaluator'/>

         <components>

            <!-- SWAP COMPONENT CODE GOES HERE -->

         </components>

      </module>

   </modules>

</extension>




When we write the code to swap components we will use 3 elements (<scope>, <region-id> & <source-id>) to identify 'where' our new components shall be added. But instead of diving into a detailed description of how pages, templates & regions work in Share I will just give you an extremely cut down version:


.


Each page (document-details.xml) uses a template (document-details.ftl) in which regions are declared (<@region id='document-actions' scope='template'/> into which one or more webscript components can be inserted.


.


If you didn't understand any of this, don't worry. All you really need is to identify the values for the 3 elements, something that can be done using a Share developer tool called SurfBug, just do the following:


.






  1. Go to http://localhost:8080/share/page/surfBugStatus


  2. Click the 'Enable SurfBug' button


  3. Navigate to the 'Document Details' (you should now see each component being surrounded by a red border)


  4. To identify the region-id, source-id & scope for the 'Actions' panel, simply click on the 'Actions' panel.


  5. You should now see a popup displaying information about the 'Actions' panel, you will find the values you are looking for under the 'Component Details' section.




.



If you haven't got a server running you can see the popup displayed bu SurfBug in the image below:



.



In the sections below I will show you how to use these values to add, remove and replace components on the page.

Add a new custom panel component above the 'Actions' panel.



Place the following code inside the place-holder-comment in the acme-test-extension.xml file mentioned above.



<component>

   <scope>template</scope>

   <region-id>document-actions</region-id>

   <source-id>document-details</source-id>

   <sub-components>

      <sub-component id='acme-sample' index='-1'>

         <evaluations>

            <evaluation id='acme-test-addSample'>

               <url>/acme/components/document-details/sample</url>

            </evaluation>

         </evaluations>

      </sub-component>

   </sub-components>

</component>




As you can we are using the 3 values to identify the region in which to add our sample component.



We then declare a <sub-component> element, you can declare as many as you like and therefore add as many components as you like. Note that we give the <sub-component> a unique id. The cool thing about that is that the id can be referenced from another extension module (which then can remove it). Therefor make sure you namespace your id with a company name.



The <evaluations> & <evaluation> elements are there to make it possible for a <subcomponent> to have multiple outcomes (the outcome being the <url> element pointing to the webscript to use). The outcome depends on which <evaluation> that returned true. In this example we are only using one <evaluation> element and it has no <evaluator> child elements, meaning we are actually not 'evaluating' anything and that the <evaluation> therefor always will 'win' (be the the onw who decides the outcome).



Note that the <url> element is pointing to our Acme 'Sample' panel we created earlier.

Remove the 'Tags' panel component



Place the following code inside the place-holder-comment in the acme-test-extension.xml file mentioned above.



<!-- Remove Tag Panel -->

<component>

   <scope>template</scope>

   <region-id>document-tags</region-id>

   <source-id>document-details</source-id>

   <sub-components>

      <sub-component id='default'>

         <evaluations>

            <evaluation id='acme-test-removeTags'>

               <render>false</render>

            </evaluation>

         </evaluations>

      </sub-component>

   </sub-components>

</component>




If you use SurfBug and this time click on the 'Tags' panel you can see from where the values for our first 3 elements are coming from. More interesting though is that we have set the id of the <sub-component> to 'default'. The reason for this is that we are actually not creating a new <sub-component> here, instead we are referencing the 'Tags' panel component.


.


The reason it is called 'default' is because when it was added to the page it didn't provide an explicit id. The <sub-component> id is also something you can find by using SurfBug, just look at the 'ID' field under the 'Sub-Component Details', the part after the '#' character is the id.


.


This means that what we are doing is to extend the 'default' <sub-component> (the 'Tags' panel) with a new <evaluation> element, and since it has no <evaluator> child elements it will always 'win' and decide the outcome. The outcome in this case though is NOT a <url> element, instead it is <render>false</render> which tells Share that no component at all shall be rendered.


Replace the 'Permissions' panel with the 'Sample' document panel.



Place the following code inside the place-holder-comment in the acme-test-extension.xml file mentioned above.



<!-- Replace Permissions with Sample Panel -->

<component>

   <scope>template</scope>

   <region-id>document-permissions</region-id>

   <source-id>document-details</source-id>

   <sub-components>

      <sub-component id='default'>

         <evaluations>

            <evaluation id='acme-test-replaceWithSample'>

               <url>/acme/components/document-details/sample</url>

            </evaluation>

         </evaluations>

      </sub-component>

   </sub-components>

</component>




In this example we have actually not introduced any new concepts, its just a combination of the previous two examples.



We use the 'default' id to add in a new <evaluation> to  the 'Permissions' panel. Since the <evaluation> has no <evaluator> child elements it will always 'win' and decide the outcome, in this case a <url> element pointing to Acme's 'Sample' panel component.

Package your extension as a .jar



As the last piece of the puzzle lets make sure to package everything up in a jar file so you can share it with your colleagues or easily deploy it on servers. Simply do the following:



  1. Use your terminal/dos window and change into the directory containing the directory structure you have created.



    • Note! it shall only contain the directory 'alfresco'




  2. Type the following command to jar it up:



    • jar cvf acme-test-extension.jar *




Deploy extension on your server



Now you have a nice extension packaged as a jar, so lets go ahead and use it, simply...



  1. Place it in your TOMCAT_HOME/shared/lib


  2. Restart your server


  3. Go to the deplyment ui http://localhost:8080/share/page/modules/deploy and make sure to enable your module by adding it and then click the 'Apply' button.


  4. Go to the document details page and you should now see 2 'Sample' panels but no 'Tags' or 'Permissions' panels.


I hope you enjoyed this blog post. If there was something you didn't understand, don't hesitate to add a comment. Once you get it you will find its a really easy and powerful way of extending your Share installation.



Cheers,



:: Erik

Introduction



In the first part of this tutorial I demonstrated how you could add a new page to Alfresco Share and have it protected by the standard authentication mechanism. I also showed how you can make that new page the landing page for your Alfresco Share by setting it as the root page in the Spring Surf configuration. In the second part of this tutorial I'll show how you can override the default Alfresco Share login page and in next blog I'll show how to customize and re-use existing pages and access core services.

Step 3: Override the default login page



Spring Surf applications can define a login page by configuring the 'login' page-type mapping to reference a specific Page object. In Alfresco 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.



To override this definition you should create a (or add to your existing) 'share-config-custom.xml' file and place it in 'alfresco/web-extension' folder (this location can quite happily exist within the JAR file that we started creating in the first part of this tutorial). Add the following content to this file:

<alfresco-config>

   <config evaluator='string-compare' condition='WebFramework'>

      <web-framework>

         <defaults>

            <page-type>

               <id>login</id>

               <page-instance-id>blog-login</page-instance-id>

            </page-type>

         </defaults>

      </web-framework>

   </config>

</alfresco-config>


We now need to create the Page that we have referenced, create a file called: 'blog-login.xml' and place it in the 'alfresco/site-data/pages' path of your JAR. It should contain the following:

<page>

   <template-instance>blog-login</template-instance>

   <authentication>none</authentication>

</page>


Notice that the Page definition sets and <authentication> value of 'none' - this is important to ensure that Surf doesn't attempt to redirect to the login page when the user isn't authenticated causing an infinite loop!



Next create a file called 'blog-login.xml' and place it in the 'alfresco/site-data/template-instances' path of your JAR. It should contain the following:

<template-instance>

   <template-type>blog/demo/blog-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 login page. Create this file 'blog-login.ftl' and place it in the 'alfresco/templates/blog/demo' path of your JAR. This file should contain the following code to render a login page (NOTE: this is a completely stripped down login page - I'm not trying to win any awards for style, accessibility or i18n here... I'm just demonstrating the bare minimum required!!):

<html>

   <head>

      <title>Blog Application Login</title>

   </head>

   <body>

      <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>


Build your JAR, re-deploy it to '<tomcat-home>/webapps/share/WEB-INF/lib' and restart Tomcat (assuming you're using Tomcat - adjust as necessary for your web server). Now when you attempt to view 'http://localhost:8080/share' (adjust port as necessary) you will be prompted with your new login page and following successful authentication will be taken to your new landing page.



New login screen

Spring Surf Presets

Posted by ddraper Nov 1, 2011

Introduction

I started writing this post back in November last year but never got around to finishing it. Since some questions came up about presets at DevCon in San Diego last week I thought it would make sense to revisit and finally post it. It really just provides some general information on how to configure an alternative Presets Manager for Alfresco Share and provides an example of how to switch from using the repository to the local file system for preset generated Spring Surf objects. In the future I'll try and post some more information specifically relating to customizing the Alfresco Share default presets, but hopefully this will be useful in the meantime!

Background

When any user logs into Share they are presented with their own unique dashboard that is a Spring Surf Page object in its own right. This page does not exist until the user logs in for the first time at which point it generated and persisted in the repository for re-use. This generation is achieved through the use of 'presets'.

 

Presets are essentially parameterised abstract Spring Surf artefact definitions (Pages, Template-Instances, Components, etc) defined in XML files that can a token map can be applied to generate concrete org.springframework.extensions.surf.ModelObject instances. They are managed and can be used through the org.springframework.extensions.surf.PresetsManager that is registered in the 'spring-surf-presets-context.xml' Spring configuration file as the 'webframework.presets.manager' bean. This configuration file is used to define what type of files contain Preset configuration and where to look for them.

 

By default Presets can be defined in 'presets.xml' files located in either 'classes/alfresco/site-data/presets' or 'classes/alfresco/web-extension/site-data/presets' (the use of 'alfresco' is a remnant of the morphing of Alfresco Surf to Spring Surf) but can be customized by overriding the Spring bean configuration (or by defining a new PresetsManager bean) in consuming applications.

 

If you do amend the search paths you must keep them somewhere under the 'classes' directory as they are located using a ClassPathStore (alternatively you can configure an alternative store and search path in the application context to use to locate the files). A correctly authored preset file contains a single 'presets' element containing zero or more 'preset' elements where each preset element can contain definitions for zero or more 'pages', 'template-instances' and 'components'. A basic skeleton structure could look something like this:

 

<?xml version='1.0' encoding='UTF-8'?>
<presets>
   <preset id='MyPreset'>
      <components>
         <component id='${componentId}'>
            ...
         </component>
      </components>
      <template-instances>
         <template-instance id='${templateId}'>
            ...
         </template-instance>
      </template-instances>
      <pages>
         <page id='${pageId}'>
            ...
         </page>
      </pages>
   </preset>
</presets>


The Share application contains a single Presets configuration file (WEB-INF/classes/alfresco/site-data/presets/presets.xml) that define a variety of different presets (including the default user dashboard configuration).

 

Instantiating Presets

You simply need to obtain your PresetsManager bean and call its 'constructPresets' method to make use of the configured Presets. This method requires the ID of the preset to use and a java.util.Map of key/value pair tokens to apply to the parameters. The method will search through the list of configured Preset XML files looking for a <preset> element with a matching 'id' attribute. The token map is applied to a matching definition to substitute the configurable parameters with actual values. Each defined ModelObject is then instantiated and persisted using the ModelObjectService bean. The resulting Pages, Template-Instances and Components are then available for use as though they were statically configured Spring Surf artefacts.

 

An alternative method for constructing presets is to use the custom Spring Surf JSP tags 'constructPreset' and 'presetToken'. These can be used as follows:

<%@ taglib prefix='surf' uri='http://www.springframework.org/tags/surf' %>
<surf:constructPreset preset='myPreset'>
   <surf:presetToken key='pageId' value='MyPage'/>
   <surf:presetToken key='templateId' value='MyTemplate'/>
   <surf:presetToken key='componentId' value='MyComponent'/>
</surf:constructPreset>


A preset is only used to provide an initial definition for objects that you wish to create multiple instances of. There is little point in having a preset if you only wish to use it once (you may as well just create a normal configuration file) and the parameterisation only allows simple String substitution rather than any kind of complex logical processing (that you could achieve with FreeMarker or JSP processing of model properties).

 

Since the purpose is to create multiple instances of the same combinations of ModelObject it is vitally important that page and template instance 'id' attributes are parameterised and that any scoped component definitions have their 'source-id' attribute configured to match the page or template instance id. For example, if you define a template and a page then their IDs should be parameterised and the pages 'template-type' attribute should used the same parameter as the template instances  'template-type' attribute.

 

Alternative Presets Manager

If you wish to use presets as part of a Share customization or are writing a Spring Surf application that has access to an Alfresco installation then you can make use of the default application context and your constructed preset ModelObjects will be stored in the repository store. However, if you are using Spring Surf for an application independently of Alfresco you will need to override the default application context.

 

The following configuration should work as it is currently used in the Spring Surf FVT application (the purpose of each bean definition is explained in-line):

 

The PresetManager requires a ModelObjectService to create and save ModelObjects. The default PresetManager uses a MultiObjectService that does NOT include access to the local file system. A ModelObjectService requires a PersisterService which requires a Persister which requires a Store. It is the Store that controls where the preset constructed ModelObjects will ultimately be persisted and to write to the local file system we need to use the LocalFileSystemStore.

<bean id='presets.model.object.service'>
   <property name='objectPersistenceService' ref='presets.object.persistence.service'/>
</bean>

<bean id='presets.object.persistence.service' >
   <property name='persisterService' ref='presets.persister.service'/>
</bean>

<bean id='presets.persister.service' >
   <property name='persister' ref='presets.persister'/>
   <property name='webFrameworkConfig' ref='webframework.config.element'/>
</bean>

<bean id='presets.persister' parent='webframework.sitedata.persister.abstract'>
   <property name='store' ref='presets.store'/>
   <property name='pathPrefix'><value>/</value></property>
</bean>

<bean id='presets.store' >
   <property name='root'><value>./WEB-INF</value></property>
   <property name='path'><value>/presetConstructs</value></property>
</bean>


We need to override the default PresetsManager (or we could define a new bean) and ensure that it references our ModelObjectService.

<bean id='webframework.presets.manager' >
   <property name='modelObjectService' ref='presets.model.object.service'/>
   <property name='searchPath' ref='webframework.presets.searchpath'/>
   <property name='files'>
      <list>
         <value>presets.xml</value>
      </list>
   </property>
</bean>


Finally we need to override the default Autowire service to add make our new persisters available (otherwise it won't be able to use any of the ModelObjects created from the PresetsManager).

<bean id='webframework.service.autowire' parent='webframework.service.autowire.abstract'>
   <property>
      <list>
         <ref bean='webframework.sitedata.persister.classpath'/>
         <ref bean='presets.persister'/>
      </list>
   </property>
</bean>

The end result of this configuration should be that when an object is instantiated from a preset it is stored on the local file system rather than in the Alfresco repository.

Introduction

At DevCon in San Diego last week I got asked a number of times how possible it would be to either radically customize Share or add entirely new pages to it. I got the opportunity to demonstrate how easy this was and realised that it would probably be a good basis for a blog. In this first post I'm going to demonstrate how to add a new page to Alfresco Share (and have it protected by the standard authentication mechanism) and make it the application landing page. In the next post I'll show how to replace the default login form and provide a link to a stripped down document library page.

Step 1: Add a new page

Adding a new page into Alfresco Share is almost trivially easy and can be done in a minimum of three files:

    • A Page definition file
    • A Template-Instance definition file
    • A FreeMarker template file


Stripping these files to the bare minimum content these should look as follows:

 

Page definition ('home-page.xml')

This file defines the page to 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.

<page>
   <template-instance>home-page</template-instance>
   <authentication>user</authentication>
</page>

 

Template-Instance definition ('home-page.xml')

This creates a mapping to the actual FreeMarker template that contains the content for the page.

<template-instance>
   <template-type>blog/demo/home-page</template-type>
</template-instance>

 

FreeMarker template file ('home-page.ftl')

This contains the actual page content. The example shown here is trivial but this could also contain regions for binding Spring Surf components if required.

<html>
   <head>
      <title>Blog Application</title>
   </head>
   <body>
      Welcome To Extreme Share Customization!
   </body>
</html>


The files should be build into a JAR file in the following structure:

    • /alfresco/site-data/pages/home-page.xml
    • /alfresco/site-data/template-instances/home-page.xml
    • /alfresco/templates/blog/demo/home-page.ftl


Assuming that you are running on Tomcat, copy the JAR file into either 'webapps/share/WEB-INF/lib' or '<tomcat-home>/shared/lib'. If you're not running on Tomcat then you'll need to adjust the location to suit your specific web server. You'll need to ensure that your Tomcat server is correctly configured if you wish to use '<tomcat-home>/shared/lib' and you should be aware that the page you have defined will be available to all Spring Surf based applications running on that web server!!

 

Restart your web server and then open the following location in your browser to 'http://localhost:8080/share/page/home-page' (adjust the port as necessary for your application configuration!). You will be prompted with the standard Alfresco Share login screen - this is because the page has specified and authentication level of 'user'. If you want only your administrator to access the page then you could set this as 'admin' and if you want the page to be public you could set it to 'none'.

 

 

The key thing to note here is that although your page effectively belongs to the Alfresco Share application your FreeMarker template can contain any HTML/JavaScript/CSS that you wish - you are not tied into using YUI2 as is used in the implementation of Alfresco Share - should you wish to write a page a page in JQuery or Dojo or pure HTML  (or anything else) then you are quite at liberty to do so.

 

Also, as well as being able to re-use the standard Alfresco Share authentication mechanism you are also able to easily access all the WebScripts available on both the Web and Repository tiers. This means you can easily build your own UI around existing Alfresco functionality.

 

When bringing in resources (such as images, JavaScript and CSS files) it's important to remember that you should place them under the META-INF folder in your JAR and use the '/res' prefix on the requests to access them (e.g. request the file 'META-INF/blog/demo/example.css' using the URL '/share/res/blog/demo/example.css'.

 

Step 2: Make the new page the landing page

PLEASE NOTE: Whilst writing this section I discovered a couple of bugs in the Spring Surf code which have been fixed. In order to make sure this example works you will either need to build the latest version of Alfresco or wait for version 4.0.c

 

Spring Surf supports the notion of a default page which is defined in the site configuration (that's site as in 'Web Site', rather than the Alfresco Share concept of site!). By default the default page is the 'site-index.jsp' which works out and redirects to the authenticated users dashboard page. A the site default 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 (e.g. '/share/page'). This is also configured as the 'welcome-file' in the Share 'web.xml' file.

 

In order to change the landing page for the application you can simply 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.

 

To customize Alfresco Share to use our new landing page you simply need to create a file called 'slingshot.site.configuration.xml' containing the following:

 

<configuration>
   <source-id>site</source-id>
   <properties>
      <root-page>home-page</root-page>
   </properties>
</configuration>


Build the file into your JAR at the following location:

    • /alfresco/web-extension/site-data/configurations


Note that the file needs to under the 'web-extension' path in order that it gets resolved before the Alfresco Share default!

 

After deploying your JAR file and restarting the web server you should find that when you point your browser at 'http://localhost:8080/share' you will be taken to your new page by default (after you login of course!)

Filter Blog

By date: By tag: