Wednesday, June 20, 2012

A simple pinboard dashlet

0.) Preamble

This example shows how you could develop simple Alfresco Dashlets. So here some requirements:

  • A dashlet is required to see and post short messages
  • Everybody should be able to post messages to the specific site where the dashlet is available.
1.) Define the Content Model 

At first we need to define the content model for our pinboard entry type. The content model looks as the following:


<?xml version="1.0" encoding="UTF-8"?>

<!-- Model definition -->
<model name="ecg:pinboardmodel" xmlns="http://www.alfresco.org/model/dictionary/1.0">

    <!-- Optional meta-data about the model -->
    <description> The pinboard model </description>
    <author> David Maier </author>
    <version> 1.0 </version>

    <!-- Imports are required to reference definitions in other models -->
    <imports>
           <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" />
           <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm" />
    </imports>

    <!-- The name space of our model -->
    <namespaces>
           <namespace uri="http://www.ecmgeek.de/model/content/1.0" prefix="ecg" />
    </namespaces>
   

    <!-- Content types -->
    <types>
   
           <!-- Default types -->
          <type name="ecg:document">
            <title>Document</title>
            <parent>cm:content</parent>
         </type>
        
         <type name="ecg:folder">
            <title>Folder</title>
            <parent>cm:folder</parent>        
         </type>

           <!-- Specific types -->
           <type name="ecg:pinboardentry">
              <title>PinboardEntry</title>
              <parent>ecg:document</parent>
              <properties>
                 <property name="ecg:subject">
                    <type>d:text</type>
                 </property>
                 <property name="ecg:description">
                    <type>d:text</type>
                 </property>
              </properties>
           </type>         
</types>
</model>

You can see that we just extended the default types by deriving the type 'ecg:pinboardentry'.

I deployed the model by just adding it to the Data Dictionary.

2.) Prepare the site 

The next step is to prepare the site which you would like to use. This I just created a subfolder 'pinBoard' inside the site's folder.

3.) Create Data Web Scripts

Two Web Scripts are required. One to add a pinboard entry to a specific site and another one to get all pinboard entries from a specific site.

3.1.) Add entry Web Script 

Here the descriptor:
pinboardadd.get.desc.xml
<?xml version="1.0" encoding="UTF-8"?>
<webscript>
    <shortname>Add Pinboard entry</shortname>
    <description>To add a pinboard entry</description>
    <url>/alfintra/pinboard/add?name={nameArgument}&amp;desc={descArgument}&amp;site={siteArgument}</url>
    <format default="xml">extension</format>
    <authentication>user</authentication>
    <transaction>required</transaction>
</webscript>

And the Java Script controller:

pinboardadd.get.js
/**
 * Arguments
 */
//The target site name
var siteName = args["site"];

//The name of the future entry
var entryName = args["name"];

//The description of the future entry
var entryDesc = args["desc"];


/**
 * Argument validation
 */
var siteFolder = companyhome.childByNamePath("Sites/" + siteName + "/pinBoard" );

if (siteFolder == undefined )
{
   status.code = 404;
   logger.log(status.code);
   status.message = "The site's pinBoard folder was not found. Did you prepare your site in order to use the pinboard?";
   logger.log(status.message);
   status.redirect = true;
}
else
{
    addPinboardEntry(siteFolder, entryName, entryDesc);
}


/**
 * Script logic
 */
function addPinboardEntry(siteFolder, entryName, entryDesc)
{
    logger.log("Entering addPinboardEntry");
   
    logger.log("Setting properties");
      var props = new Array();
    props["ecg:subject"] = entryName;
    props["ecg:description"]= entryDesc;
       
    logger.log("Creating node");
      siteFolder.createNode(entryName,"ecg:pinboardentry", props);

    logger.log("Setting model");
     model.created = "true";

    logger.log("Leaving addPinboardEntry");
}

Finally the presentation template. This template shows nothing. It just redirects to the page from which the html page was called.

pinboardadd.get.html.ftl
<script type="text/javascript">

  var ref = document.referrer;
  location.replace(ref);
</script>

3.2.) Get entries Web Script

Here the descriptor:

pinboardlist.get.desc.xml 
<?xml version="1.0" encoding="UTF-8"?>
<webscript>
    <shortname>List pinboard entries</shortname>
    <description>To list the pinboard entries of a site</description>
    <url>/alfintra/pinboard/list?site={siteArgument}</url>
    <format default="xml">extension</format>
    <authentication>user</authentication>
    <transaction>required</transaction>
</webscript>

 The Java Script controller:

pinboardlist.get.js
/**
 * Arguments
 */

//The target site name
var siteName = args["site"];

/**
 * Argument validation
 */
var siteFolder = companyhome.childByNamePath("Sites/" + siteName + "/pinBoard" );

if (siteFolder == undefined )
{
   status.code = 404;
   logger.log(status.code);
   status.message = "The site's pinBoard folder was not found.";
   logger.log(status.message);
   status.redirect = true;
}
else
{
    listPinboardEntries(siteFolder);
}

function listPinboardEntries(siteFolder)
{
    logger.log("Entering listPinboardEntries");
      logger.log("Getting all childs");
    model.entries = siteFolder.children;
    logger.log("Leaving listPinboardEntries");   
}

The JSON output:

pinboardlist.get.json.ftl
<#escape x as jsonUtils.encodeJSONString(x)>
{
"pinboard":
  {
   "entries":
    [
        <#list entries as node>
              <#if node.properties["ecg:subject"]?exists>
                   <#if node.properties["ecg:description"]?exists>  
        {"name":"${node.properties['ecg:subject']}", "desc":"${node.properties['ecg:description']}", "site":"${args.site}"}
       <#if node_has_next>,</#if>                  
                  </#if>
            </#if>
    </#list>
    ]
 }
}
</#escape>

4.) Create the Dashlet
The idea is now that that the controller of the Web Script accesses the data which is provided by the data web script. The controller passes the data to the model and the presentation template renders it inside a dashlet. The content is presented within the dashlet's body container.

To create a new entry the URL of the 'Add Entry' Web Script is called as a form action. Here we do not use AJAX, instead a simple HTML form is used to call the entry creating Web Script. After this Web Script is called, it just redirects back to the previous page. By using additional Java Script inside the presentation template, it should be also possible to perform such an action without the need to redirect to the previous called page by refreshing this one. However, the dashlet shows how the interaction with the Data Web Scripts can basically work. The form is presented within the dashlet' toolbar container.

So what we need at first is a Dashlet Descriptor:

.../site-webscripts/de/ecmgeek/components/dashlets/pinboardlist.get.desc.xml
 <webscript>
    <shortname>List pinboard entries</shortname>
    <description>To list pinboard entries</description>
    <family>site-dashlet</family>
    <url>/components/dashlets/pinboardlist</url>
</webscript>

We also need a controller which gets the data from our Data Web Script and passes it to the model:

.../site-webscripts/de/ecmgeek/components/dashlets/pinboardlist.get.js
var siteName = page.url.templateArgs.site;
var data = remote.call("/alfintra/pinboard/list.json?site=" + siteName);
var results = eval('(' + data + ')');

model.entries = results.pinboard.entries;
Finally we need to create the dashlet's UI. You could basically add any HTML which you want to the dashlet's html template. But to integrate better with Alfresco, you should at least define the following containers:
  • dashlet
    • toolbar
    • body
 If you want that your dashlet is resizable you should also instantiate an Alfresco Dashlet Resizer by using the following line inside a JavaScript block:
  •  new Alfresco.widget.DashletResizer("${args.htmlid}", "${instance.object.id}");)
Interesting to know is that Alfresco Share comes with a Proxy servlet which allows you to access the Data Web Scripts of the Alfresco Repository. This proxy can be also used to avoid problems with the same origin policy if using client side Java Script to access the Data Web Script.

Our dashlet code looks now as the following:

 .../site-webscripts/de/ecmgeek/components/dashlets/pinboardlist.get.html.ftl

<script type="text/javascript">//<![CDATA[
   
   //Make the dashlet resizeable
   new Alfresco.widget.DashletResizer("${args.htmlid}", "${instance.object.id}");
//]]>
</script>
 
<div class="dashlet">
    <div class="title">
        ${msg("title")}
    </div>
     
    <div class="toolbar">
        
           <form name="postToPinboardForm" method="get" action="/share/proxy/alfresco/alfintra/pinboard/add.html">
             <table>
                <tr>
                   <td> ${msg("subject")} </td>
                   <td>  ${msg("desc")} </td>
                   <td> &nbsp; </td>        
                </tr>
 
                <tr> 
                   <td> <input type="text" name="name"> </td>
                   <td> <input type="text" name="desc"> </td>
                   <td> <input type="hidden" name="site" value="${page.url.templateArgs.site}"></td>
                   <td> <input type="submit" value="Post"> </td>
                </tr>
             </table>
            </form>        
    </div>
     
 
    <div class="body  scrollableList" id="${args.htmlid}-body">
  
        <table>
       
        <#list entries as e>
              <tr> <td> <b> ${e.name} </b> </td> </tr>
              <tr> <td> ${e.desc} </td> </tr>
              <tr> <td> &nbsp; </td> </tr>
        </#list>
 
        </table>
     
    </div>
</div>

The template reads some properties from the following properties file (or message bundle):

 .../site-webscripts/de/ecmgeek/components/dashlets/pinboardlist.get.properties
title=Pinboard
subject=Subject
desc=Description

Even if this is not the most complicated dashlet, I hope that it may help you to get started with dashlet development. Finally the dashlet looks this way:








 

Tuesday, June 12, 2012

Theme it

Today I had to investigate how to add a custom theme to my Alfresco 4.x installation. Some parts were a bit tricky, but the most of it was quite simple. Here some useful steps:

  1. Navigate to $WEBAPPS/share/WEB-INF/classes/alfresco/site-data/themes . BTW: $WEBAPPS is the deployment folder of your servlet container.
  2. Copy the file yellowTheme.xml to myTheme.xml
  3. Edit the file myTheme.xml by setting the title and the id. The id has to be 'theme.myTheme'
  4. Navigate to $WEBAPPS/themes!
  5. Make a copy of the complete folder 'yellowTheme' by naming it to 'myTheme'.
  6. Naviagate to $WEBAPPS/themes/myTheme/images!
  7. Open all the images by using E.G. the G(nu) I(mage) M(anipulation) P(rogram) . The Command is 'gimp'. Then colorize the images to fit your required base color. I just colorized every image which had a yellow color before. The main loge image is named 'app_logo.png' and can be replaced by your prefered one.
  8. Navigate back to $WEBAPPS/themes/myTheme!
  9. Edit the file presentation.css by setting the required colors. Again, I just copied the color codes to GIMP by only replacing the yellow color tones with my prefered ones.
  10. Now the tricky part. There are some css dependencies those need to be changed inside this file. So just replace every occurrence of 'yellowTheme' with 'myTheme' inside this file.
  11. Naviagete to $WEBAPPS/themes/myTheme/yui/assets
  12. Inside the file 'skin.css', also replace every occurrence of 'yellowTheme' with 'myTheme'.
  13. Restart Alfresco
  14. Open Alfresco Share, create a new site and choose the entry 'myTheme' as the site theme.
  15. Perform some CSS changes by being able to test your changes immediately by refreshing the just created site.
  16. Open the Administration Console and set the theme 'myTheme' as the gloabl one.
I hope this article helps you to get quickly started with customizing your Alfresco theme.