customizing the document library
DESCRIPTION
The Share Document Library provides a number of out-of-the-box default actions and displays basic, essential metadata for documents and folders. This session will show you how to add custom metadata and status indicators, modify the available actions and wire-up new filters. We'll also look at how the Document Library was extended for the DoD 5015.2 Records Management Fileplan browser. You will need to be familiar with basic Surf concepts as well as JavaScript and Freemarker to follow the webscript customization. Familiarity with YUI 2.x and CSS will aid understanding during this session.TRANSCRIPT
1
Customizing the Document Library
Mike HatfieldSenior Software Engineer, Alfresco
twitter: @mikehatfield
2
Customizing the Document Library
What We’ll Cover
• Architecture• Challenges to customization and extension
• Plans for improvement
• Case Study: the DoD 5015.2 Extensions• Extension Example
• Status Indicators• Custom Actions• Custom Metadata• Filters
3
Architecture
4
Architecture
Heavy Use of YUI for “Web 2.0” Experience
• Many JavaScript frameworks evaluated• Only YUI had a full-time staff, full API documentation, supported
UI widgets
• Many YUI widgets and modules used• DataTable• Treeview• Buttons, Menus, Containers• History Manager for “AJAX back button” support• XHR wrappers, JSON parser
• Most HTML rendering performed client-side• yuilibrary.com is your friend!
5
Architecture
Initial Page Load
• Create all YUI controls• Register event handlers• Extract URL arguments• Bubbling Event “changeFilter”• onChangeFilter: updateDocList()• Webscript URL (via proxy) YUI DataSource• Repository webscripts execute for given filter & params• Bubbling Event “filterChanged”• Render JSON response using DataTable cell renderers
6
Architecture
Subsequent Navigations (e.g Folder)
• Bubbling Event “changeFilter” [new filter params]• onChangeFilter: Build data webscript URL• History Manager multiNavigate()• onHistoryManagerFilterChanged: updateDocList()• Webscript URL (via proxy) YUI DataSource• Repository webscripts execute for given filter & params• Bubbling Event “filterChanged”• Render JSON response using DataTable cell renderers
7
Architecture
YUI DataTable Renderers
• One renderer per data column• Selected file / folder• Status indicator• Thumbnail / icon• Metadata description• Actions
• Render XSS-safe HTML from parsed JSON• elCell.innerHTML = “<div>…</div>”
• YUI wraps output in <table><tr><td> tags
8
Architecture
Actions
• Currently defined in webscript config xml files• Separate configs for browse page, details pages
• Action Sets based on repository business logic• evaluator.lib.js• “document”, “folder”, “locked”, “workingCopyOwner”, etc.
• Two action types• “simple-link” URL via getActionUrls()
• {downloadUrl}, {viewUrl}, {folderDetailsUrl}• “action-link” JavaScript function “id” attribute
• ID attribute defines action icon and JavaScript function
9
Architecture
Actions
• Permissions• Comma-separated list• User AND asset must have ALL permissions for action to appear• “Virtual” permissions as well as role-based
• create, edit, delete, permissions, cancel-checkout• inline-edit, simple-approve, googledocs-edit
• Negative permission via tilde (~)• ~filter-path, ~portlet, ~googledocs-edit
• Label attribute for I18N message
10
Current Extension Points
New Actions
New Actions
New Actions
New Actions
Custom UI
Custom UI
Custom UI
New Filters
New Filters
New Filters
11
Plans for Improvements
• Consolidate scattered action configuration• share-config-custom.xml instead of webscript config• Still possible to restrict & specialise actions on details pages
• New actions via configuration where practicable• jar file for client-side UI assets, I18N• CSS and JS dependencies via config (see Forms & Header)
• Leverage Repository Actions & scripts• Custom Views
• Web-tier rendering• Open CMIS
12
Case Study: DoD 5015.2 Extensions
13
Case Study: DoD 5015.2 Extensions
Custom Actions
Numerous new and overridden actions to support the DoD requirements specification.
14
Case Study: DoD 5015.2 Extensions
Custom Toolbar
Sensitive to current folder type. New and overridden actions.
Custom Filters
Removed unsuitable filters (user filters, tags). One static, one dynamic (populated from list of saved searches on the Repository).
15
Case Study: DoD 5015.2 Extensions
“documentLibrary” container type determines components
16
Case Study: DoD 5015.2 Extensions
17
Template: documentlibrary
documentlibrary.js
connector.get("/slingshot/doclib/container/" + siteId + "/" + containerId);"dod:filePlan"
model.doclibType = fromRepoType("dod:filePlan");“dod5015”
documentlibrary.ftl
<@region id=doclibType + "documentlist" scope="template" protected=true />
Surf Component Binding
template.dod5015-documentlist.documentlibrary.xml
<url>/components/documentlibrary/dod5015/documentlist</url>
18
Creating the Container
DoD 5015.2 Method
documentlibrary.js
page = sitedata.getPage("site/" + siteId + "/dashboard");pageMeta = eval('(' + p.properties.pageMetadata + ')');contentType = doclibMeta.type;
connector.get("/slingshot/doclib/container/" + siteId + "/" + containerId + “?type=“ + toRepoType(contentType));
”dod:filePlan”
presets.xml
<preset id="rm-site-dashboard"> <page id="site/${siteid}/dashboard"> <properties> <pageMetadata>{"documentlibrary":{…, "type":"dod5015"}}</pageMetadata>
19
Creating the Container
Web QuickStart Method
LoadWebSiteDataGet.java
NodeRef docLib = siteService.getContainer(siteId, COMPONENT_DOCUMENT_LIBRARY);
siteService.createContainer(siteId, COMPONENT_DOCUMENT_LIBRARY, WebSiteModel.TYPE_WEBSITE_CONTAINER, null);
ornodeService.setType(docLib, WebSiteModel.TYPE_WEBSITE_CONTAINER);
dashlet
connector.get("/api/loadwebsitedata?site=" + siteId);
20
Case Study: DoD 5015.2 Extensions
YUI Helps
YUI developers added a number of helper functions to allow OO-style JavaScript modules.
• Notice:• constructor• superclass• extend• augment• etc…
21
Component Replacement Approach
• Full override / replacement control on all tiers.
• Your code can be almost completely independent of Alfresco’s.
Pros
• Mandatory component mapping , even for “native” components.
• Still have to copy/paste where <include> cannot be used, e.g. I18N.
• Repository folder type to component prefix issue.
• Not a 100% “clean” override mechanism.
Cons
22
Mandatory Component Mapping
Big Development Overhead
template.dod5015-actions-common.documentlibrary.xml
template.dod5015-documentlist.documentlibrary.xml
template.dod5015-file-upload.documentlibrary.xml
template.dod5015-fileplan.documentlibrary.xml
template.dod5015-flash-upload.documentlibrary.xml
template.dod5015-html-upload.documentlibrary.xml
template.dod5015-navigation.documentlibrary.xml
template.dod5015-savedsearch.documentlibrary.xml
template.dod5015-title.documentlibrary.xml
template.dod5015-toolbar.documentlibrary.xml
template.dod5015-tree.documentlibrary.xml
…
And that’s just the browse page!
23
Extension Example
• Overview • Customisations
• Status Indicators• Custom Metadata• New Filters• Custom Action
• Based on Alfresco Community 3.4.b• Need to use AMP on the Repository until refactoring work is complete• Share extensions via .jar and web-extension folder
24
Extension Example: Photography
Overview
25
Demo
Out-of-the-box Share
26
Extension: Status Indicators
Provide the user a quick indication of the current status of a folder or document, e.g. aspects applied.
Calculated in evaluator.lib.js
• Repository• evaluator.lib.js
• Share• I18N messages• Indicator images
27
Repository: Override evaluator.lib.js
/* Exif metadata */if (node.hasAspect("exif:exif")){ status["exif"] = true;}
/* Geographic */if (node.hasAspect("cm:geographic")){ status["geographic"] = true;}
28
Share: Indicator images
status[”exif"] = true;status["geographic"] = true;
share.jar!/META-INF/components/documentlibrary/images1. exif-indicator-16.png2. geographic-indicator-16.png
29
Share: Add I18N messages
1. share.jar!/org/springframework/extensions/surf/custom-slingshot-geographic-context.xml<bean id="geographic.custom.resources" class="org.springframework.extensions.surf.util.ResourceBundleBootstrapComponent">
<property name="resourceBundles"> <list> <value>alfresco.messages.geographic</value> </list> </property></bean>
2. share.jar!/alfresco/messages/geographic.propertiestip.geographic=Geo Locationtip.exif=EXIF Metadata
30
Extension: Custom Metadata
Rendered entirely by the web browser from JSON data.
• Repository• item.lib.ftl• Maybe also JavaScript logic
• Share• I18N messages• Override cell renderer
31
Repository: Override item.lib.ftl
1. alfresco.amp!/WEB-INF/classes/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/item.lib.ftl
<#if node.hasAspect("cm:geographic")>“geolocation”:{ "latitude": ${(node.properties["cm:latitude"]!0)?c}, "longitude": ${(node.properties["cm:longitude"]!0)?c}},</#if>
<#if node.hasAspect(”exif:exif")>“exif”:{ …},</#if>
32
Share: Reference extension JavaScript
1. shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary/actions-common.get.head.ftl
<@script type="text/javascript" src="${page.url.context}/res/components/geographic/geographic-extension.js"></@script>
33
Share: Override cell renderer
1. Override fnRenderCellDescription
share.jar!/META-INF/components/geographic/geographic-extension.js
YAHOO.util.Event.onContentReady("alf-hd", function(){ if (Alfresco.DocumentList) { Alfresco.DocumentList.prototype.fnRenderCellDescription = function
DL_fnRenderCellDescription() { … if (record.exif) { desc += scope.msg(“detail.exposure”) + record.exif.exposureTime;
} … } }}
34
Extension: New Filter
Allow easy filtering by any Repository logic, most commonly a Lucene or Alfresco FTS search query.
• Repository• filters.lib.js
• Share• I18N messages• Filter webscript config
35
Share: Filter webscript config
1. shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary/filter.get.config.xml
<filters> … <filter id="geo" label="link.geo-located" /> <filter id="exif" label="link.exif" /> …</filters>
36
Repository: Filter webscript override
1. alfresco.amp!/WEB-INF/classes/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/filters.lib.js
case "geo": filterQuery = "+PATH:\"" + parsedArgs.rootNode.qnamePath + "//*\""; filterQuery += "+ASPECT:\"cm:geographic\""; filterParams.query = filterQuery break;
case "exif": filterQuery = "+PATH:\"" + parsedArgs.rootNode.qnamePath + "//*\""; filterQuery += "+ASPECT:\"exif:exif\""; filterParams.query = filterQuery break;
37
Extension: Custom Action
Can be configured to only appear if a folder or document is in a particular state and/or the user has the correct permission(s) and/or the page is within a Site context and/or the action is on the browse or details page.
• Repository• evaluator.lib.js• Action processing (optional)
• Share• I18N messages• Action configuration• Client-side logic & images
38
Repository: Override evaluator.lib.js
/* Geographic */if (node.hasAspect("cm:geographic")){ status["geographic"] = true; permissions["geographic"] = true;}
39
Share: Action configuration
1. shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary/documentlist.get.config.xml
2. shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/document-details/document-actions.get.config.xml
3. etc…
<actionSet id="document"> … <action type="simple-link"
id="onActionGeographic" permission="geographic" href="{geographicUrl}" label="actions.document.geographic" />
</actionSet>
40
Share: CSS and JS dependencies
1. shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary/actions-common.get.head.ftl
<@link rel="stylesheet" type="text/css" href="${page.url.context}/res/components/geographic/geographic-extension.css" />
2. share.jar!/META-INF/components/geographic/geographic-extension.css
.doclist .onActionGeographic a{ background-image: url(pin.png) !important;}
41
Share: Proxy JavaScript function
var override = Alfresco.DocumentList || Alfresco.DocumentActions;
// Store reference to getActionUrls() function to allow extension.var getActionUrls_geo = override.prototype.getActionUrls;
override.prototype.getActionUrls = function(recordData){ var actionUrls = getActionUrls_geo.apply(this, arguments);
actionUrls["geographicUrl"] = Alfresco.util.siteURL( "geographic-map?nodeRef=" + recordData.nodeRef);
return actionUrls;};
42
Demo
New Extensions
43
The End Result
44
Roadmap
• Consolidate scattered action configuration• New actions via configuration where practicable• Remove references to non-core Share code• Leverage Repository Actions & scripts• Custom Views
45
Q & A
• Feedback
46
Learn Morewiki.alfresco.com/wiki/Shareblogs.alfresco.com/wp/mikeh/