Customising the Upload File(s) dialog in Alfresco Share
Martin Bergljung, Consultant, Tuesday 13th September 2011
In a recent project that I was involved in I was asked to customize the Upload File(s) dialog in Alfresco Share. This is the dialog where you can upload one or more documents into the Document Library via Flash. The dialog looks as follows (if you have not seen it before):

What the customer wanted was an extra list in the dialog, populated with all the existing files in current folder. And when new files were selected for upload one should be able to associate them with one or more of the existing files. The customized dialog that I implemented looks like this:

There is also a comment box at the bottom of the dialog where you can make a comment about the relationship between the uploaded files and the existing files.
When a new file is selected for upload and associated with an existing file, a peer to peer relationship is created between the files. This relationship is implemented by a custom association in a new content model that we will create.
The upload component that uses flash to select and upload multiple files is supported by a Web Script called flash-upload and it is stored in the tomcat\webapps\share\WEB-INF\classes\alfresco\site-webscripts\org\alfresco\components\upload directory. This Web Script is in turn loading (i.e. via flash-upload.get.head.ftl) a JavaScript file called flash-upload.js that builds the actual YUI (Yahoo User Interface library) dialog for the multi file upload. The JavaScript file is located in the tomcat\webapps\share\components\upload directory and we will customize it to show the new list and comment box.
There are quite a few customizations required by this solution that you might not come across in an “every day” Alfresco project, some of them are:
- Overriding client side Java Script files
- Updating a YUI dialog with a new Data Table and text box
- Making an AJAX call from the customized YUI Dialog
- Handle authentication for the AJAX call
Setup the build project
This article assumes that you are using the build project structure presented in my previous blog. You can download the build project from my download site, which is accessible via a link on the right side of this page. If you want to just follow along with a build project that has all the code from this blog already completed then download the code for this blog only.
Remember to setup the build.properties file with your local application paths.
Implementing the custom content model
Let’s start with the custom content model and get that out of the way as it is not the focus of this article. We are going to need to define a new association that we can setup between a new file and an existing file. Let’s call it derivedWork. In the build project we can add a new content model to the content-model.xml file located in the _alfresco\config\alfresco\module\com_mycompany_module_cms\model directory:
<?xml version="1.0" encoding="UTF-8"?>
<model name="mc:contentModel" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<!-- Optional meta-data about the model -->
<description>My Company Custom Document Content Model</description>
<author>someone</author>
<version>1.0</version>
<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>
<namespaces>
<namespace uri="http://www.mycompany.com/model/content/1.0" prefix="mc"/>
</namespaces>
<aspects>
<aspect name="mc:derivedWork">
<title>Derived Work</title>
<properties>
<property name="mc:derivedWorkComments">
<title>Derived work comment</title>
<type>d:text</type>
</property>
</properties>
<associations>
<association name="mc:derivedWork">
<source>
<mandatory>true</mandatory>
<many>true</many>
</source>
<target>
<class>cm:content</class>
<mandatory>false</mandatory>
<many>true</many>
</target>
</association>
</associations>
</aspect>
</aspects>
</model>
The derived work association is defined inside an aspect together with a comments field. As usual we import the Alfresco data dictionary model to get to the data types that we have available out of the box, such as d:text, and we also import the standard Alfresco content model to get to the cm:content type that is used in the association.
All the definitions are specified inside a new custom namespace http://www.mycompany.com/model/content/1.0. You should really update this URL to reflect your company and domain.
To deploy this new content model run the deploy-alfresco-amp target when Alfresco Tomcat is stopped (read more in the build project blog about this).
Updating the flash-upload Web Script template to include new labels, new list, and comment markup
The flash-upload Web Script that is described in the flash-upload.get.desc.xml file and located in the tomcat\webapps\share\WEB-INF\classes\alfresco\site-webscripts\org\alfresco\components\upload directory needs to be updated with the new list and comment box markup. We do this by overriding the original version of the flash-upload.get.html.ftl template with our own template implementation.
This is a Spring Surf Web Script that lives in the Alfresco Share web application. To override an existing Spring Surf Web Script we first create the same directory structure under the _share\config\alfresco\web-extension\site-webscripts directory and then copy the original version of the template or controller file into this directory. We can then update this copy to do whatever we want it to do. Surf Web Script files under the web-extension\site-webscripts directory in a Share extension JAR file will be read after the original versions of the Web Script files and override them.
In our case we create a new org\alfresco\components\upload directory structure and copy the original version of the flash-upload.get.html.ftl template file into this directory from the tomcat\webapps\share\WEB-INF\classes\alfresco\site-webscripts\org\alfresco\components\upload directory.
The template file is then updated as follows:
<#assign el=args.htmlid?html>
<div id="${el}-dialog" class="flash-upload hidden">
<div class="hd">
<span id="${el}-title-span"></span>
</div>
<div class="bd">
<div class="browse-wrapper">
<div class="center">
<div id="${el}-flashuploader-div" class="browse">${msg("label.noFlash")}</div>
<div class="label">${msg("label.browse")}</div>
</div>
</div>
<div class="tip-wrapper">
<span id="${el}-multiUploadTip-span">${msg("label.multiUploadTip")}</span>
<span id="${el}-singleUpdateTip-span">${msg("label.singleUpdateTip")}</span>
</div>
<div id="${el}-filelist-table" class="fileUpload-filelist-table"></div>
<!-- CUSTOMIZATIONS START HERE -->
<div class="existingFilesCaptionCenter">
<div class="label">Select the file or files from which your uploads are
derived.</div>
</div>
<div id="${el}-existingfilelist-table" class="existingFilelistTable"></div>
<div class="existingFilesCaptionCenter">
<div class="label">Describe the work you did to create the files you are
uploading...</div>
</div>
<div class="existingFilelistTable">
<textarea id="${el}-derivedWorkComment-textarea" name="derivedWorkComment"
rows="4" tabindex="0"></textarea>
</div>
<!-- CUSTOMIZATIONS END HERE -->
<div class="status-wrapper">
<span id="${el}-status-span" class="status"></span>
</div>
<div id="${el}-versionSection-div">
<div class="yui-g">
<h2>${msg("section.version")}</h2>
</div>
<div class="yui-gd">
<div class="yui-u first">
<span>${msg("label.version")}</span>
</div>
<div class="yui-u">
<input id="${el}-minorVersion-radioButton" type="radio" name="majorVersion" checked="checked"
tabindex="0"/>
<label for="${el}-minorVersion-radioButton"
id="${el}-minorVersion">${msg("label.minorVersion")}</label>
</div>
</div>
<div class="yui-gd">
<div class="yui-u first">
</div>
<div class="yui-u">
<input id="${el}-majorVersion-radioButton" type="radio" name="majorVersion" tabindex="0"/>
<label for="${el}-majorVersion-radioButton"
id="${el}-majorVersion">${msg("label.majorVersion")}</label>
</div>
</div>
<div class="yui-gd">
<div class="yui-u first">
<label for="${el}-description-textarea">${msg("label.comments")}</label>
</div>
<div class="yui-u">
<textarea id="${el}-description-textarea" name="description" cols="80" rows="4"
tabindex="0"></textarea>
</div>
</div>
</div>
<!-- Templates for a file row -->
<div style="display:none">
<div id="${el}-left-div" class="fileupload-left-div">
<span class="fileupload-percentage-span hidden"> </span>
<#if (contentTypes?size == 1)>
<input class="fileupload-contentType-input" type="hidden" value="${contentTypes[0].id}"/>
<#elseif (contentTypes?size > 1)>
<select class="fileupload-contentType-select" tabindex="0">
<#if (contentTypes?size > 0)>
<#list contentTypes as contentType>
<option value="${contentType.id}">${msg(contentType.value)}</option>
</#list>
</#if>
</select>
</#if>
</div>
<div id="${el}-center-div" class="fileupload-center-div">
<span class="fileupload-progressSuccess-span"> </span>
<img src="${url.context}/res/components/images/generic-file-32.png" class="fileupload-docImage-img"
alt="file"/>
<span class="fileupload-progressInfo-span"></span>
</div>
<div id="${el}-right-div" class="fileupload-right-div">
<span class="fileupload-fileButton-span">
<button class="fileupload-file-button" value="Remove" disabled="true"
tabindex="0">${msg("button.remove")}</button>
</span>
</div>
</div>
<div class="bdft">
<input id="${el}-upload-button" type="button" value="${msg("button.upload")}" tabindex="0"/>
<input id="${el}-cancelOk-button" type="button" value="${msg("button.cancel")}" tabindex="0"/>
</div>
</div>
</div>
<script type="text/javascript">//<![CDATA[
new Alfresco.FlashUpload("${el}").setMessages(
${messages}
);
//]]></script>
The first part of the added markup will display a label with information about the new existing files list, telling the user to select files that the uploaded files should be derived from. We use the out-of-the-box label CSS class but we enclose the label in a div with a new CSS class called existingFilesCaptionCenter. We will need to add the new CSS class later on.
Next we add a div that will hold the existing files list and it is identified with the CSS id ${el}-existingfilelist-table. We will use the id later on when inserting the YUI Data Table into this div. The ${el} expression will be resolved as the id of the element that is enclosing the Upload dialog. We style the list with a new CSS class called existingFilelistTable.
After the list div is a label for the comment box. The comments box is then going to be added as a textarea with id {el}-derivedWorkComment-textarea.
Now run the deploy-share-jar ant target to deploy the Share JAR extension, no need to stop Alfresco if tomcat context.xml is setup to detect changes in Share JAR extension file (read more in the build project blog about this).
If you look at the dialog now it will be displayed as follows:

You will not at this stage see any existing files list/data table as it is supposed to be inserted via new YUI code in the customized flash-upload.js file.
Implementing a Web Script that returns existing files as JSON
This is the Web Script that we will be calling from the YUI code in the customized flash-upload-custom.js file to populate the existing files list. It will return a JSON structure with information about the files in passed in folder node reference. The descriptor, controller, and template for this Web script should be put in the trunk\_alfresco\config\alfresco\extension\templates\webscripts\com\mycompany\existingfiles directory.
Let’s start with the descriptor file existingfiles.get.desc.xml:
<webscript>
<shortname>Existing Files</shortname>
<description>Returns existing files in current folder
To test: curl -v -u admin:admin http://localhost:8080/alfresco/service/mycompany/existingfiles?folderNodeRef=workspace://SpacesStore/48e0d368-a90d-4692-8c48-8765a80678c0
</description>
<url>/mycompany/existingfiles?folderNodeRef={folderNodeRef}</url>
<format default="json"/>
<authentication>user</authentication>
<transaction>required</transaction>
</webscript>
There is not much special about the descriptor it just says that the Web Script can be accessed via the /mycompany/existingfiles URL and that it requires both user authentication and a transaction to be active.
Next up is the controller which is stored in the existingfiles.get.js file:
var existingFileNodes = new Array();
var folder = search.findNode(args.folderNodeRef);
if (folder != null) {
var allNodesInFolder = folder.children;
for each (node in allNodesInFolder) {
if (node.isDocument) {
existingFileNodes.push(node);
}
}
}
model.existingFiles = existingFileNodes;
The controller will first find the node for passed in folder node reference and then look up all the child nodes for it. It then loops through all these child nodes and if they are documents then they are added to existingFileNodes array.
If we wanted to we could just select files of a certain type by changing the node.isDocument to for example node.isSubType("ws:article"), which would filter out anything that is not an website article.
This array is then passed on to the FreeMarker template existingfiles.get.json.ftl, which will construct the JSON to be returned:
{
"existingFiles" : [
<#list existingFiles as existingFile>
{
"nodeRef": "${existingFile.nodeRef}",
"name": "${jsonUtils.encodeJSONString(existingFile.name)}",
"type": "${existingFile.type}"
}<#if existingFile_has_next>,</#if>
</#list>
]
}
Here we use the FreeMarker directive list to loop through the existing files array and then we extract the three properties nodeRef, name, and type.
This completes our Web Script and we can now deploy this new Web Script by running the deploy-alfresco-amp target. Make sure that Alfresco Tomcat is stopped (read more in the build project blog about this).
To test that it works as expected we can use curl as follows:
X:\tools\curl7.21>curl -v -u admin:admin http://localhost:8080/alfresco/service/mycompany/existingfiles?folderNodeRef=workspace://SpacesStore/a2507c86-25f2-475c-b698-8a0cc3f4e25e
* About to connect() to localhost port 8080 (#0)
* Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'admin'
> GET /alfresco/service/mycompany/existingfiles?folderNodeRef=workspace://SpacesStore/a2507c86-25f2-475c-b698-8a0cc3f4e25e HTTP/1.1
> Authorization: Basic YWRtaW46YWRtaW4=
> User-Agent: curl/7.21.1 (i386-pc-win32) libcurl/7.21.1 OpenSSL/0.9.8o zlib/1.2.5
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Set-Cookie: JSESSIONID=234735A271EF1E7CE01C7D97E7785896; Path=/alfresco
< Cache-Control: no-cache
< Pragma: no-cache
< Content-Type: application/json;charset=UTF-8
< Content-Length: 1199
< Date: Tue, 16 Aug 2011 17:41:55 GMT
<
{
"existingFiles" : [
{
"nodeRef": "workspace://SpacesStore/43cdf8c6-c3ea-47e1-9f09-8648d6638305",
"name": "Alfresco_Enterprise_Edition_3_3_Windows_Simple_Install.pdf",
"type": "{http://www.alfresco.org/model/content/1.0}content"
},
{
"nodeRef": "workspace://SpacesStore/9b6f4af7-0615-4f88-a2ce-15c220fbd60d",
"name": "CIFS Client Access and CIF Server config.docx",
"type": "{http://www.alfresco.org/model/content/1.0}content"
},
{
"nodeRef": "workspace://SpacesStore/03b0f64d-e43b-44c5-838f-ff0887346591",
"name": "Alfresco_Enterprise_Edition_3_3_Linux_Simple_Install.pdf",
"type": "{http://www.alfresco.org/model/content/1.0}content"
}
]
}
* Connection #0 to host localhost left intact
* Closing connection #0
The folderNodeRef parameter is set to a node reference that you have to lookup via Alfresco Explorer or Alfresco Share. Use a node reference for a folder that has a couple of documents in it. Note that we run curl with the –v switch to get more verbose logging during the call.
Updating the flash-upload Java Script to load the Existing Files list
The flash-upload.js file that builds the actual YUI dialog for the multi file upload is located in the tomcat\webapps\share\components\upload directory and we will customize it to show and handle the new list and handle the comment box.
This file cannot be overridden in the same way as a Web Script template or controller. We will copy it and rename it (so it’s clear that we are not trying to override the original version but replace it) to flash-upload-custom.js and put it in the _share\config\META-INF\components\upload directory. Anything put under META-INF in a Share extension JAR will be available outside WEB-INF directory in the web application. This is needed as this JavaScript that we are customizing is running in the browser and not on the server side like a Web Script controller Java Script.
The first thing we are going to do in the customized file is to lookup a property called dataTable. After this property we will add a new property to hold our existing files data table:
dataTable: null,
existingFilesDataTable: null,
Add one more property to hold the derived work comments:
derivedWorkComment: null,
Now look up the onReady: function FlashUpload_onReady() function a little bit further down in the file. In this function lookup the following line:
this.description = Dom.get(this.id + "-description-textarea");
After this line add the following line:
this.derivedWorkComment = Dom.get(this.id + "-derivedWorkComment-textarea");
This connects the YUI HTML Element object with the HTML text area element. The next thing we need to do is look up the show: function FlashUpload_show(config) function. This function can be called multiple times and will display the Uploader dialog in different ways depending on the passed in configuration parameters.
We can use this function to call a new function that will create and populate our existing files list with the help of the Web Script that we created earlier. Look for the following line in the show() function:
this._applyConfig();
After it add the following function call:
this._createPopulateExistingFilesDataTable();
This is a new function that we will create later. It will create the YUI data table object for the existing file list and populate it with files from current folder by calling the Web Script we just created. Next function that we will add code to is the _resetGUI: function FlashUpload__resetGUI(), which you will find just after the show() function. Add the following code at the end of it:
this.derivedWorkComment.value = "";
This will reset the comment about the derived work when the dialog is displayed.
Last thing we need to do in the flash-upload-custom.js file is to add the _createPopulateExistingFilesDataTable function that we earlier made a call to. Add it at the end of the file just after the FlashUpload__clear() function:
_createPopulateExistingFilesDataTable: function FlashUpload__createPopulateExistingDataTable() {
var url = Alfresco.constants.PROXY_URI + "mycompany/existingfiles?folderNodeRef=" +
this.showConfig.destination;
We start off by constructing the URL for the Web Script that we created earlier and that will return information about the existing files in current folder. What’s important here is to use the Alfresco repository proxy URL (for example when running on localhost the Alfresco.constants.PROXY_URI = http://localhost:8080/share/proxy/alfresco/) so authentication is handled automatically for us, otherwise a login dialog will popup.
Note that the service URL path element is not present as when you normally call Web Scripts (i.e. http://localhost:8080/alfresco/service/{web script url}).
After this we create a YUI XHR Data Source that we will use to call our custom remote Web Script via HTTP:
var myDataSource = new YAHOO.util.XHRDataSource(url);
myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
myDataSource.connXhrMode = "queueRequests";
myDataSource.responseSchema = {
resultsList: "existingFiles",
fields: [
{ key: "nodeRef" },
{ key: "name" },
{ key: "type" }
]
};
We setup the XHR Data Source so it expects JSON in the format returned from the Existing Files Web Script, the responseSchema variable is used for this. Next thing we do is to setup a column definition for the existing files table:
var myColumnDefs = [
{key:'select', label: "Select", formatter:'checkbox'},
{key:"name", label: "Name", className:"col-center"},
{key:"type", label: "Type", className:"col-right"}
];
The first column is just going to contain a check-box that we can click on to indicate that we want to setup a derivedWork association with this existing file. By specifying the key as select for the first column we indicate that this is a special column and it is not going to be populated from the JSON structure (the name does not have to be select). The second column is going to contain the name of the existing file and is populated from the name property in the JSON structure, and the third column is going to contain the type of the file and is populated from the type property in the JSON structure.
Next thing we are going to configure is the height, width etc for the existing files table:
var myConfigs = {
scrollable: true,
height: "200px",
width: "620px",
MSG_EMPTY: "No existing files to display, derivations cannot be setup", caption:
"Select the file or files from which your uploads are derived."
};
We also need to get a handle to the div that will contain the existing files data table, so we get that next:
var dataTableDiv = Dom.get(this.id + "-existingfilelist-table");
This is the div that we added earlier in the overridden flash-upload.get.html.ftl file. We now got all the information we need to create the YUI existing files data table object:
this.existingFilesDataTable = new YAHOO.widget.DataTable(dataTableDiv, myColumnDefs,
myDataSource, myConfigs);
Finally we need to also define an event handler for when users click the check-box in the existing file data table:
this.existingFilesDataTable.on('checkboxClickEvent', function (oArgs) {
var elCheckbox = oArgs.target;
var record = this.getRecord(elCheckbox);
record.setData("select", elCheckbox.checked);
});
When the user clicks any of the check-boxes in the existing files list we will set the select field to true or false based on the check-box control’s status.
These are the only updates we are going to make to the flash-upload-custom.js file for the moment. However, this JavaScript file is not going to be loaded unless we override the flash-upload Web Script and tell it about this new customized file.
We do this by copying the flash-upload.get.head.ftl file of the Web Script from the tomcat\webapps\share\WEB-INF\classes\alfresco\site-webscripts\org\alfresco\components\upload directory to the _share\config\alfresco\web-extension\site-webscripts\org\alfresco\components\upload directory.
The flash-upload.get.head.ftl Web Script file is used to put stuff into the HEAD section of the web page and currently it will load the original flash-upload.js file instead of our customized one. Open up the file and change the last line so it looks like this:
<@script type="text/javascript" src="${page.url.context}/res/components/upload/
flash-upload-custom.js"></@script>
We are now done except that it will still not work if we try and run it. The flash-upload-custom.js file is not the JavaScript file that the system will try and load. It will instead look for the flash-upload-custom-min.js file, which is a compressed version of the flash-upload-custom.js file (for more information see http://developer.yahoo.com/yui/compressor/). To try out our customization just copy the flash-upload-custom.js to flash-upload-custom-min.js. In the rest of this article it is probably easiest to just change the –min.js file when building the customization.
Now, let’s see how the Upload dialog looks like, stop Alfresco and then run the deploy-share-jar ant target.
The Upload dialog now looks something like this:

Here I am located in a folder with a couple of Alfresco related documents. We can see that the list and comment box is not laid out properly and needs some margin settings etc. We will fix this shortly.
If you cannot get this to work then it’s well worth installing FireBug in FireFox to see what’s happening on the browser side. Let’s say you forgot to make a –min.js copy of the JavaScript file, you would then see something like this error message in FireBug (have it turned on all the time):

If your changes still does not take effect then stop Alfresco and close down FireFox. Then start it up again. Make sure to refresh the page in FireFox.
Adding stuff to the flash upload style sheet (CSS)
The flash-upload.get.head.ftl Web Script file is also used to load extra CSS files and it loads the flash-upload.css file. We are going to add some CSS classes to this file so we first copy it from the tomcat\webapps\share\components\upload directory to the trunk\_share\config\META-INF\components\upload directory and then rename it to flash-upload-custom.css. Change the flash-upload.get.head.ftl file so it loads our custom CSS as follows:
<@link rel="stylesheet" type="text/css" href="${page.url.context}/res/components/upload/flash-upload-custom.css" />
Next step is to add the extra CSS classes that we used in the flash-upload.get.html.ftl file to the flash-upload-custom.css file. Add them at the end of the file as follows:
.existingFilesCaptionCenter
{
text-align: center;
padding-top: 0.5em;
line-height: 2em;
margin-left: auto;
margin-right: auto;
width: 36em;
}
.existingFilelistTable
{
padding: 0.8em;
}
The Upload dialog now looks as like this with the new styling (you need to deploy-share-jar and then refresh the page in the browser):

We are now ready to implement the code that will setup the derived work association between uploaded files and existing files.
Implementing a Web Script to setup the derived work association
This is the Web Script that we will be calling from the YUI code in the customized flash-upload-custom.js file to setup the derivedWork association between files about to be uploaded and existing files.
The descriptor, controller, and template for this Web script should be put in the trunk\_alfresco\config\alfresco\extension\templates\webscripts\com\mycompany\derivedwork directory.
Let’s start with the descriptor file createDerivedWorkAssoc.get.desc.xml:
<webscript>
<shortname>Creates a Derived Work Assoc</shortname>
<description>Creates a derived work association between an existing file and a
file that was just uploaded.
To test: curl -u admin:admin http://localhost:8080/alfresco/service/mycompany
/createDerivedAssoc?uploadedFileNodeRef=workspace://SpacesStore
/48e0d368-a90d-4692-8c48-8765a80678c0&existingFileNodeRef=workspace:
//SpacesStore/48e0d368-a90d-4692-8c48-8765a80678c0
</description>
<url>/mycompany/createDerivedAssoc?uploadedFileNodeRef={uploadedFileNodeRef}
&existingFileNodeRef={existingFileNodeRef}</url>
<format default="json"/>
<authentication>user</authentication>
<transction>required</transaction>
</webscript>
There descriptor is pretty standard and says that the Web Script can be accessed via the /mycompany/createDerivedAssoc URL and that it requires both user authentication and a transaction to be active.
Next up is the controller which is stored in the createDerivedWorkAssoc.get.js file:
var uploadedFileNodeRef = args.uploadedFileNodeRef;
var existingFileNodeRef = args.existingFileNodeRef;
if (uploadedFileNodeRef == null || existingFileNodeRef == null) {
model.message = "Could not setup Derived Work association, uploadedFileNodeRef is
null or existingFileNodeRef is null";
} else {
var uploadedFileNode = search.findNode(uploadedFileNodeRef);
var existingFileNode = search.findNode(existingFileNodeRef);
uploadedFileNode.createAssociation(existingFileNode, "mc:derivedWork");
model.message = "Successfully setup Derived Work association between
uploadedFileNodeRef (" + uploadedFileNodeRef + ") and existingFileNodeRef (" +
existingFileNodeRef + ")";
}
The controller will first check that the parameters with existing and uploaded file node references have been specified. Then it will look up the nodes for these references. After this the controller uses the createAssociation function call to setup a Derived Work association from the uploaded file to the existing file. The Derived Work association (mc:derivedWork) is the one we previously defined in our custom content model.
The controller sets up a message with information about the outcome of the operation and passes it on the FreeMarker template createDerivedWorkAssoc.get.json.ftl, which will just print out the message (useful if the Web Script is executed from the browser or the command line):
<#assign messageString = message />
{
"message": "${messageString}"
}
Here we use the FreeMarker directive assign to setup a variable with the message that is then printed onto the screen.
This completes our Web Script and we can now deploy this new Web Script by running the deploy-alfresco-amp target. Make sure that Alfresco Tomcat is stopped (read more in the build project blog about this).
To test that it works as expected we can use curl. First upload two documents and make a note of their node references. Then setup the association between them as follows (note. If you want verbose output use –v):
X:\tools\curl7.21>curl -u admin:admin "http://localhost:8080/alfresco/service/mycompany
/createDerivedAssoc?uploadedFileNodeRef=workspace://SpacesStore/cc79be68-bc74-42df-947b-54bcd90030ae&
existingFileNodeRef=workspace://SpacesStore/7fe67397-c116-49c7-8ef8-14e1cc1f9166"
{
"message": "Successfully setup Derived Work association between uploadedFileNodeRef
(workspace://SpacesStore/cc79be68-bc74-42df-947b-54bcd90030ae) and existingFileNodeRef
(workspace://SpacesStore/7fe67397-c116-49c7-8ef8-14e1cc1f9166)"
}
To see that the association has really been setup you can login via Alfresco Explorer and the Node Browser and check it out. You should see something like this for the uploaded file:

Implementing a Web Script to setup the derived work aspect and comments
This Web Script will also be called from the YUI code in the customized flash-upload-custom.js file to setup the derivedWork aspect and comments between files about to be uploaded and existing files. The descriptor, controller, and template for this Web script should be put in the trunk\_alfresco\config\alfresco\extension\templates\webscripts\com\mycompany\derivedwork directory.
Let’s start with the descriptor file setDerivedWorkAssocComment.get.desc.xml:
<webscript>
<shortname>Set Comment for Derived Work Assoc</shortname>
<description>Applies the mc:derivedWork aspect and its
mc:derivedWorkComments to an uploaded file.
To test: curl -v -u admin:admin "http://localhost:8080/alfresco/service/mycompany
/setDerivedWorkAssocComment?uploadedFileNodeRef=workspace:
//SpacesStore/48e0d368-a90d-4692-8c48-8765a80678c0&comments=some comment"
</description>
<url>/mycompany/setDerivedWorkAssocComment?uploadedFileNodeRef=
{uploadedFileNodeRef}&comments={comments}</url>
<format default="json"/>
<authentication>user</authentication>
<transaction>required</transaction>
</webscript>
There descriptor is pretty standard and says that the Web Script can be accessed via the /mycompany/setDerivedWorkAssocComments URL and that it requires both user authentication and a transaction to be active.
Next up is the controller which is stored in the setDerivedWorkAssocComment.get.js file:
var uploadedFileNodeRef = args.uploadedFileNodeRef;
var comment = args.comments;
if (uploadedFileNodeRef == null || comments == null) {
model.message = "Could not setup derived work aspect with comments,
uploadedFileNodeRef is null or comments is null";
} else {
var uploadedFileNode = search.findNode(uploadedFileNodeRef);
var properties = new Array(1);
properties["mc:derivedWorkComments"] = comments;
uploadedFileNode.addAspect("mc:derivedWork", properties);
model.message = "Successfully setup derived work aspect with comments for
uploadedFileNodeRef (" + uploadedFileNodeRef + ")";
}
The controller will first get the parameters and then check that they are not null. Then it will look up the uploaded file node based on passed in node reference. After this the controller uses the addAspect function call to setup the comments via the derived work aspect. The Derived Work aspect (mc:derivedWork) is the one we previously defined in our custom content model.
The controller sets up a message with information about the outcome of the operation and passes it on the FreeMarker template setDerivedWorkAssocComment.get.json.ftl, which will just print out the message (useful if the Web Script is executed from the browser or the command line):
<#assign messageString = message />
{
"message": "${messageString}"
}
This completes our Web Script and we can now deploy this new Web Script by running the deploy-alfresco-amp target. Make sure that Alfresco Tomcat is stopped (read more in the build project blog about this).
To test that it works as expected we can use curl. First upload a document and make a note of its node reference. Then setup the aspect and comments like this:
X:\tools\curl7.21>curl -v -u admin:admin "http://localhost:8080/alfresco/service/mycompany
/setDerivedWorkAssocComment?uploadedFileNodeRef=workspace:
//SpacesStore/cc79be68-bc74-42df-947b-54bcd90030ae&comments=somecomments"
{
"message": "Successfully setup derived work aspect with comments for uploadedFileNodeRef
(workspace://SpacesStore/cc79be68-bc74-42df-947b-54bcd90030ae)"
}
To see that the aspect and property has been setup you can login via Alfresco Explorer and the Node Browser and check it out. You should see something like this:

Configuring the Share UI to show the Derived Work aspect
Now, it would be good if we could see the Derived Work aspect information in the Alfresco Share UI when we look at a document that has it setup. This can easily be handled by adding the following configuration to the share-config-custom.xml file located in the _share\config\alfresco\web-extension directory:
<config evaluator="aspect" condition="mc:derivedWork">
<forms>
<form>
<field-visibility>
<show id="mc:derivedWork"/>
<show id="mc:derivedWorkComments"/>
</field-visibility>
<appearance>
<set id="mcMetadata" appearance="bordered-panel" label-id="mc.content.metadata.header"/>
<field id="mc:derivedWork" label-id="wc.metadata.derivedFrom" set="mcMetadata">
<control template="/org/alfresco/components/form/controls/association.ftl">
<control-param name="displayMode">list</control-param>
<control-param name="compactMode">true</control-param>
<control-param name="showTargetLink">true</control-param>
</control>
</field>
<field id="mc:derivedWorkComments" label-id="wc.metadata.derivationsComments" set="mcMetadata">
<control template="/org/alfresco/components/form/controls/textarea.ftl"/>
</field>
</appearance>
</form>
</forms>
</config>
To deploy this configuration run the deploy-share-jar ant target, no restart of Alfresco should be necessary if you have setup necessary configuration in tomcat/conf/context.xml (see the build project blog for more information).
When we now look at a document with the derived work aspect setup we should see something like this in Share:

Updating the flash upload JavaScript so it sets up the derived work association
We can now update the upload YUI JavaScript so it sets up the derived work association. In the flash-upload-custom.js file look up the onUploadCompleteData: function FlashUpload_onUploadCompleteData(event) function, which is fired by YUI’s Uploader when transfer is completed for a file. At the end of this function add the function call that will setup derived file associations:
this._setupDerivedWorkAssociations(fileInfo);
The fileInfo object contains information about the file that was just uploaded, such as the file name and the Alfresco node reference.
Now let’s implement the _setupDerivedWorkAssociations function, add it at the end of the file:
_setupDerivedWorkAssociations: function FlashUpload__setupDerivedWorkAssociations(fileInfo) {
var uploadedFileNodeRef = fileInfo.nodeRef;
var uploadedFileName = fileInfo.fileName;
if (this.derivedWorkComment.value == null ||
this.derivedWorkComment.value == "") {
this.derivedWorkComment.value = "no comments"
}
var setDerivedWorkCommentCallback = {
success: function(o) {
// Do nothing
},
failure: function(o) {
window.alert("FAILED to setup derived work aspect and
comment for uploadedFileNodeRef (" +
uploadedFileNodeRef + ")");
}
};
var setDerivedWorkCommentUrl = Alfresco.constants.PROXY_URI +
"mycompany/setDerivedWorkAssocComment?uploadedFileNodeRef=" +
uploadedFileNodeRef + "&comments=" + this.derivedWorkComment.value;
var transaction = YAHOO.util.Connect.asyncRequest('GET', setDerivedWorkCommentUrl,
setDerivedWorkCommentCallback, null);
The first thing we do is get the node reference and name of the file that has just been uploaded. Then we check if there is a derived work comment specified, if not we setup a default comment.
We will start of by making a call to the /mycompany/setDerivedWorkAssocComment Web Script to setup the mc:derivedWork aspect with the comment. This AJAX call will be made with the YUI Connection object and the asyncRequest() method, which we pass the URL to call (which uses the Alfresco Repo proxy URL as we talked about before) and a call back function (i.e. setDerivedWorkCommentCallback) to execute when the call returns.
The next thing to do is to loop through all existing files that have been selected and setup derived work associations between them and the uploaded file:
var createDerivedWorkCallback = {
success: function(o) {
// Do nothing
},
failure: function(o) {
window.alert("FAILED to setup derived work association between uploaded
file (" + uploadedFileName + ") and existing file (" +
existingFileNodeRef + ")");
}
};
for (var i = 0; i < this.existingFilesDataTable.getRecordSet().getLength(); i++) {
var record = this.existingFilesDataTable.getRecordSet().getRecord(i);
var selected = record.getData("select");
if (selected) {
var existingFileNodeRef = record.getData("nodeRef");
var createDerivedWorkUrl = Alfresco.constants.PROXY_URI +
"mycompany/createDerivedAssoc?uploadedFileNodeRef=" +
uploadedFileNodeRef + "&existingFileNodeRef=" +
existingFileNodeRef;
var transaction2 = YAHOO.util.Connect.asyncRequest('GET',
createDerivedWorkUrl, createDerivedWorkCallback, null);
}
}
Before we loop through anything we setup a call back function (i.e. createDerivedWorkCallback) that the AJAX request can call on response. We do not do anything in the case of success, but you could display a message, which we do in case of an error. Then we loop through the record set of the existing files YUI Data Table. The first property we get for an existing file is the select property which tells us if it has been selected or not. In case it has been selected we also get the nodeRef property. We then make the AJAX call to the /mycompany/createDerivedAssoc Web Script to setup the derived work association beteen the uploaded file and current existing file.
To deploy this run the deploy-share-jar ant target, no restart of Alfresco should be necessary. Remember to start testing this with all browser caches cleared. We are now almost done with the exercise. However, the first screen shot of the customized Upload File(s) dialog also showed you the possibility to select type for the uploaded file. We will see how this can be done in the next section.
Updating the flash-upload Web Script to be able to set Type during upload
When we upload files we also want to be able to immediately set what type they should have. This looks something like this:

To achieve this we first need to add a few new types that we can load the selection box with. Add the following types in the content-model.xml file located in the _alfresco\config\alfresco\module\com_mycompany_module_cms\model directory:
<types>
<type name="mc:publication">
<title>Publication File</title>
<parent>cm:content</parent>
</type>
<type name="mc:manual">
<title>Manual File</title>
<parent>cm:content</parent>
</type>
</types>
To have these types displayed in the Upload File(s) dialog we need to override the flash-upload Spring Web Script controller called flash-upload.get.js. Create this file in the _share\config\alfresco\web-extension\site-webscripts\org\alfresco\components\upload directory and populate it as follows:
function getContentTypes()
{
var contentTypes = [
{ id: "cm:content", value: "cm_content" },
{ id: "mc:publication", value: "type.mc_publication" },
{ id: "mc:manual", value: "type.mc_manual" }
];
return contentTypes;
}
model.contentTypes = getContentTypes();
The last thing we need to do is add texts for the type labels in the custom.properties file located in the _share/config/alfresco/messages directory:
type.mc_publication=Publication
type.mc_manual=Manual
mc.content.metadata.header=Derived Info
wc.metadata.derivationsComments=Comments
wc.metadata.derivedFrom=Derived From
To deploy these changes stop Alfresco and then run the deploy-alfresco-amp and deploy-share-jar ant targets. When we now use the Upload File(s) dialog and select files for uploading we should be presented with selection boxes where we can choose the type for each file we are uploading.
A word on page refresh...
When customizing most Java Script files it is enough to do a page refresh in the browser for the latest changes to take effect. This is not the case with the Upload File(s) dialog. If you do a page refresh on for example the document library page it will still not mean that you got the latest flash-upload Java Script stuff.
One way you can easily get around this is to have one browser installation that always clears all caches when you exit it. Then you can always test with this browser when things behave weirdly. For example, you might have changed something and it still does not seem to take any effect in FireFox (which you might not have setup to clear all caches when you exit the browser). Then you can for example have an IE installation which clears all caches each time you exit it and then test with it just to make sure what the behaviour is with the latest code.







Comments
Comment 1
Shahid said:
Can I also add Tags in the Upload file dialog... ?
kindly help me how it is possible.
I want to restrict the user to add tags must, before saving the selected files in Upload file dialog.
Posted: 09:03pm on 20 Oct 2011
Comment 2
Chris Perkins said:
Hi Martin,
I'm pretty much a beginner at customizing Alfresco and Share. I've purchased your Business Solutions book and I'd like to let you know that it's helped me to no end to get my feet firmly under me. I appreciate all of the contributions you've made. My life is easier because of them. I'm now customizing our upload dialog per your examples and am kind of stuck on one thing. I want to include the native share tag picker in the dialog but I'm kind of at a loss as to where to start. Could you point me in the right direction?
Thanks!
Posted: 08:41pm on 2 Mar 2012
Add your comment