Ajax picker improvements and fixes, support for mimetype restriction list in the Ajax File picker.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@7751 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Kevin Roast
2008-01-04 11:38:44 +00:00
parent 0027d6d0ad
commit b7965af43d
10 changed files with 266 additions and 19 deletions

View File

@@ -29,7 +29,8 @@ import java.io.Writer;
import java.util.Stack; import java.util.Stack;
/** /**
* Very simple JSON writer. Wraps a Writer to output a JSON stream. * Fast and simple JSON stream writer. Wraps a Writer to output a JSON object stream.
* No intermediate objects are created - writes are immediate to the underlying stream.
* *
* @author Kevin Roast * @author Kevin Roast
*/ */

View File

@@ -25,8 +25,11 @@
package org.alfresco.web.bean.ajax; package org.alfresco.web.bean.ajax;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.faces.context.FacesContext; import javax.faces.context.FacesContext;
import javax.transaction.UserTransaction; import javax.transaction.UserTransaction;
@@ -38,6 +41,8 @@ import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.FileTypeImageSize; import org.alfresco.service.cmr.repository.FileTypeImageSize;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
@@ -58,6 +63,7 @@ import org.apache.commons.logging.LogFactory;
*/ */
public class PickerBean public class PickerBean
{ {
private static final String MSG_CATEGORIES = "categories";
private static final String ID_URL = "url"; private static final String ID_URL = "url";
private static final String ID_ICON = "icon"; private static final String ID_ICON = "icon";
private static final String ID_CHILDREN = "children"; private static final String ID_CHILDREN = "children";
@@ -66,6 +72,8 @@ public class PickerBean
private static final String ID_NAME = "name"; private static final String ID_NAME = "name";
private static final String ID_ID = "id"; private static final String ID_ID = "id";
private static final String ID_PARENT = "parent"; private static final String ID_PARENT = "parent";
private static final String PARAM_PARENT = "parent";
private static final String PARAM_MIMETYPES = "mimetypes";
private static final String FOLDER_IMAGE_PREFIX = "/images/icons/"; private static final String FOLDER_IMAGE_PREFIX = "/images/icons/";
@@ -110,6 +118,13 @@ public class PickerBean
} }
/**
* Return the JSON objects representing a list of categories.
*
* IN: "parent" - null for root categories, else the parent noderef of the categories to retrieve.
*
* The pseudo root node 'Categories' is not selectable.
*/
@InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML) @InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML)
public void getCategoryNodes() throws Exception public void getCategoryNodes() throws Exception
{ {
@@ -124,7 +139,7 @@ public class PickerBean
Collection<ChildAssociationRef> childRefs; Collection<ChildAssociationRef> childRefs;
NodeRef parentRef = null; NodeRef parentRef = null;
Map params = fc.getExternalContext().getRequestParameterMap(); Map params = fc.getExternalContext().getRequestParameterMap();
String strParentRef = (String)params.get(ID_PARENT); String strParentRef = (String)params.get(PARAM_PARENT);
if (strParentRef == null || strParentRef.length() == 0) if (strParentRef == null || strParentRef.length() == 0)
{ {
childRefs = this.categoryService.getRootCategories( childRefs = this.categoryService.getRootCategories(
@@ -147,7 +162,7 @@ public class PickerBean
if (parentRef == null) if (parentRef == null)
{ {
out.writeNullValue(ID_ID); out.writeNullValue(ID_ID);
out.writeValue(ID_NAME, "Categories"); out.writeValue(ID_NAME, Application.getMessage(fc, MSG_CATEGORIES));
out.writeValue(ID_ISROOT, true); out.writeValue(ID_ISROOT, true);
out.writeValue(ID_SELECTABLE, false); out.writeValue(ID_SELECTABLE, false);
} }
@@ -182,6 +197,13 @@ public class PickerBean
} }
} }
/**
* Return the JSON objects representing a list of cm:folder nodes.
*
* IN: "parent" - null for Company Home child folders, else the parent noderef of the folders to retrieve.
*
* The 16x16 pixel folder icon path is output as the 'icon' property for each child folder.
*/
@InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML) @InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML)
public void getFolderNodes() throws Exception public void getFolderNodes() throws Exception
{ {
@@ -197,7 +219,7 @@ public class PickerBean
NodeRef companyHomeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc)); NodeRef companyHomeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc));
NodeRef parentRef = null; NodeRef parentRef = null;
Map params = fc.getExternalContext().getRequestParameterMap(); Map params = fc.getExternalContext().getRequestParameterMap();
String strParentRef = (String)params.get(ID_PARENT); String strParentRef = (String)params.get(PARAM_PARENT);
if (strParentRef == null || strParentRef.length() == 0) if (strParentRef == null || strParentRef.length() == 0)
{ {
parentRef = companyHomeRef; parentRef = companyHomeRef;
@@ -249,6 +271,18 @@ public class PickerBean
} }
} }
/**
* Return the JSON objects representing a list of cm:folder and cm:content nodes.
*
* IN: "parent" - null for Company Home child nodes, else the parent noderef of the folders to retrieve.
* IN: "mimetypes" (optional) - if set, a comma separated list of mimetypes to restrict the file list.
*
* It is assumed that only files should be selectable, all cm:folder nodes will be marked with the
* 'selectable:false' property. Therefore the parent (which is a folder) is not selectable.
*
* The 16x16 pixel node icon path is output as the 'icon' property for each child, in addition each
* cm:content node has an property of 'url' for content download.
*/
@InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML) @InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML)
public void getFileFolderNodes() throws Exception public void getFileFolderNodes() throws Exception
{ {
@@ -261,12 +295,13 @@ public class PickerBean
tx.begin(); tx.begin();
DictionaryService dd = Repository.getServiceRegistry(fc).getDictionaryService(); DictionaryService dd = Repository.getServiceRegistry(fc).getDictionaryService();
ContentService cs = Repository.getServiceRegistry(fc).getContentService();
List<ChildAssociationRef> childRefs; List<ChildAssociationRef> childRefs;
NodeRef companyHomeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc)); NodeRef companyHomeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc));
NodeRef parentRef = null; NodeRef parentRef = null;
Map params = fc.getExternalContext().getRequestParameterMap(); Map params = fc.getExternalContext().getRequestParameterMap();
String strParentRef = (String)params.get(ID_PARENT); String strParentRef = (String)params.get(PARAM_PARENT);
if (strParentRef == null || strParentRef.length() == 0) if (strParentRef == null || strParentRef.length() == 0)
{ {
parentRef = companyHomeRef; parentRef = companyHomeRef;
@@ -276,6 +311,20 @@ public class PickerBean
{ {
parentRef = new NodeRef(strParentRef); parentRef = new NodeRef(strParentRef);
} }
// look for mimetype restriction parameter
Set<String> mimetypes = null;
String mimetypeParam = (String)params.get(PARAM_MIMETYPES);
if (mimetypeParam != null && mimetypeParam.length() != 0)
{
// convert to a set of mimetypes to test each file against
mimetypes = new HashSet<String>();
for (StringTokenizer t = new StringTokenizer(mimetypeParam, ","); t.hasMoreTokens(); /**/)
{
mimetypes.add(t.nextToken());
}
}
List<FileInfo> items = this.fileFolderService.list(parentRef); List<FileInfo> items = this.fileFolderService.list(parentRef);
JSONWriter out = new JSONWriter(fc.getResponseWriter()); JSONWriter out = new JSONWriter(fc.getResponseWriter());
@@ -294,28 +343,46 @@ public class PickerBean
out.startValue(ID_CHILDREN); out.startValue(ID_CHILDREN);
out.startArray(); out.startArray();
// filter out those children that are not spaces
for (FileInfo item : items) for (FileInfo item : items)
{ {
out.startObject();
out.writeValue(ID_ID, item.getNodeRef().toString());
String name = (String)item.getProperties().get(ContentModel.PROP_NAME);
out.writeValue(ID_NAME, name);
if (dd.isSubClass(this.internalNodeService.getType(item.getNodeRef()), ContentModel.TYPE_FOLDER)) if (dd.isSubClass(this.internalNodeService.getType(item.getNodeRef()), ContentModel.TYPE_FOLDER))
{ {
// found a folder // found a folder
out.startObject();
out.writeValue(ID_ID, item.getNodeRef().toString());
String name = (String)item.getProperties().get(ContentModel.PROP_NAME);
out.writeValue(ID_NAME, name);
String icon = (String)item.getProperties().get(ApplicationModel.PROP_ICON); String icon = (String)item.getProperties().get(ApplicationModel.PROP_ICON);
out.writeValue(ID_ICON, FOLDER_IMAGE_PREFIX + (icon != null ? icon + "-16.gif" : BrowseBean.SPACE_SMALL_DEFAULT + ".gif")); out.writeValue(ID_ICON, FOLDER_IMAGE_PREFIX + (icon != null ? icon + "-16.gif" : BrowseBean.SPACE_SMALL_DEFAULT + ".gif"));
out.writeValue(ID_SELECTABLE, false); out.writeValue(ID_SELECTABLE, false);
out.endObject();
} }
else else
{ {
// must be a file // must be a file
String icon = Utils.getFileTypeImage(fc, name, FileTypeImageSize.Small); boolean validFile = true;
out.writeValue(ID_ICON, icon); if (mimetypes != null)
out.writeValue(ID_URL, DownloadContentServlet.generateBrowserURL(item.getNodeRef(), name)); {
validFile = false;
ContentReader reader = cs.getReader(item.getNodeRef(), ContentModel.PROP_CONTENT);
if (reader != null)
{
String mimetype = reader.getMimetype();
validFile = (mimetype != null && mimetypes.contains(mimetype));
}
}
if (validFile)
{
out.startObject();
out.writeValue(ID_ID, item.getNodeRef().toString());
String name = (String)item.getProperties().get(ContentModel.PROP_NAME);
out.writeValue(ID_NAME, name);
String icon = Utils.getFileTypeImage(fc, name, FileTypeImageSize.Small);
out.writeValue(ID_ICON, icon);
out.writeValue(ID_URL, DownloadContentServlet.generateBrowserURL(item.getNodeRef(), name));
out.endObject();
}
} }
out.endObject();
} }
out.endArray(); out.endArray();

View File

@@ -49,6 +49,14 @@ import org.alfresco.web.ui.common.Utils;
import org.springframework.web.jsf.FacesContextUtils; import org.springframework.web.jsf.FacesContextUtils;
/** /**
* Base class for the JSP components representing Ajax object pickers.
*
* Handles the JSF lifecycle for the Ajax component. The Ajax calls themselves
* are processed via the class <code>org.alfresco.web.bean.ajax.PickerBean</code>.
*
* The derived components are only responsible for specifing the ajax service call
* to make, plus any defaults for icons etc.
*
* @author Kevin Roast * @author Kevin Roast
*/ */
public abstract class BaseAjaxItemPicker extends UIInput public abstract class BaseAjaxItemPicker extends UIInput
@@ -121,7 +129,7 @@ public abstract class BaseAjaxItemPicker extends UIInput
this.initialSelectionId, this.initialSelectionId,
this.disabled, this.disabled,
this.height}; this.height};
return (values); return values;
} }
/** /**
@@ -268,6 +276,12 @@ public abstract class BaseAjaxItemPicker extends UIInput
{ {
out.write(" window." + objId + ".setSelectedItems('" + selectedItems + "');"); out.write(" window." + objId + ".setSelectedItems('" + selectedItems + "');");
} }
// write any addition custom request attributes required by specific picker implementations
String requestProps = getRequestAttributes();
if (requestProps != null)
{
out.write(" window." + objId + ".setRequestAttributes('" + requestProps + "');");
}
out.write("}"); out.write("}");
out.write("window.addEvent('domready', init" + divId + ");"); out.write("window.addEvent('domready', init" + divId + ");");
out.write("</script>"); out.write("</script>");
@@ -371,6 +385,14 @@ public abstract class BaseAjaxItemPicker extends UIInput
*/ */
protected abstract String getDefaultIcon(); protected abstract String getDefaultIcon();
/**
* @return custom request properties optional for some specific picker implementations
*/
protected String getRequestAttributes()
{
return null;
}
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
// Strongly typed component property accessors // Strongly typed component property accessors

View File

@@ -25,6 +25,8 @@
package org.alfresco.web.ui.repo.component; package org.alfresco.web.ui.repo.component;
/** /**
* JSF Ajax object picker for navigating through and selecting categories.
*
* @author Kevin Roast * @author Kevin Roast
*/ */
public class UIAjaxCategoryPicker extends BaseAjaxItemPicker public class UIAjaxCategoryPicker extends BaseAjaxItemPicker

View File

@@ -24,16 +24,51 @@
*/ */
package org.alfresco.web.ui.repo.component; package org.alfresco.web.ui.repo.component;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.web.ui.common.Utils;
/** /**
* JSF Ajax object picker for navigating through folders and selecting a file.
*
* @author Kevin Roast * @author Kevin Roast
*/ */
public class UIAjaxFilePicker extends BaseAjaxItemPicker public class UIAjaxFilePicker extends BaseAjaxItemPicker
{ {
/** list of mimetypes to restrict the available file list */
private String mimetypes = null;
@Override @Override
public String getFamily() public String getFamily()
{ {
return "org.alfresco.faces.AjaxFilePicker"; return "org.alfresco.faces.AjaxFilePicker";
} }
/**
* @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object)
*/
public void restoreState(FacesContext context, Object state)
{
Object values[] = (Object[])state;
super.restoreState(context, values[0]);
this.mimetypes = (String)values[1];
}
/**
* @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[] {
super.saveState(context),
this.mimetypes};
return values;
}
@Override @Override
protected String getServiceCall() protected String getServiceCall()
@@ -47,4 +82,51 @@ public class UIAjaxFilePicker extends BaseAjaxItemPicker
// none required - we always return an icon name in the service call // none required - we always return an icon name in the service call
return null; return null;
} }
@Override
protected String getRequestAttributes()
{
String mimetypes = getMimetypes();
if (mimetypes != null)
{
try
{
return "mimetypes=" + URLEncoder.encode(mimetypes, "UTF-8");
}
catch (UnsupportedEncodingException e)
{
throw new AlfrescoRuntimeException("Unsupported encoding.", e);
}
}
else
{
return null;
}
}
// ------------------------------------------------------------------------------
// Strongly typed component property accessors
/**
* @return Returns the mimetypes to restrict the file list.
*/
public String getMimetypes()
{
ValueBinding vb = getValueBinding("mimetypes");
if (vb != null)
{
this.mimetypes = (String)vb.getValue(getFacesContext());
}
return this.mimetypes;
}
/**
* @param mimetypes The mimetypes restriction list to set.
*/
public void setMimetypes(String mimetypes)
{
this.mimetypes = mimetypes;
}
} }

View File

@@ -25,6 +25,8 @@
package org.alfresco.web.ui.repo.component; package org.alfresco.web.ui.repo.component;
/** /**
* JSF Ajax object picker for navigating through and selecting folders.
*
* @author Kevin Roast * @author Kevin Roast
*/ */
public class UIAjaxFolderPicker extends BaseAjaxItemPicker public class UIAjaxFolderPicker extends BaseAjaxItemPicker

View File

@@ -27,6 +27,8 @@
*/ */
package org.alfresco.web.ui.repo.tag; package org.alfresco.web.ui.repo.tag;
import javax.faces.component.UIComponent;
/** /**
* @author Kevin Roast * @author Kevin Roast
@@ -40,4 +42,36 @@ public class AjaxFileSelectorTag extends AjaxItemSelectorTag
{ {
return "org.alfresco.faces.AjaxFilePicker"; return "org.alfresco.faces.AjaxFilePicker";
} }
/**
* @see javax.faces.webapp.UIComponentTag#setProperties(javax.faces.component.UIComponent)
*/
protected void setProperties(UIComponent component)
{
super.setProperties(component);
setStringProperty(component, "mimetypes", this.mimetypes);
}
/**
* @see org.alfresco.web.ui.common.tag.HtmlComponentTag#release()
*/
public void release()
{
super.release();
this.mimetypes = null;
}
/**
* Set the mimetypes
*
* @param mimetypes the mimetypes
*/
public void setMimetypes(String mimetypes)
{
this.mimetypes = mimetypes;
}
/** the mimetypes */
private String mimetypes;
} }

View File

@@ -2418,6 +2418,12 @@
<required>false</required> <required>false</required>
<rtexprvalue>true</rtexprvalue> <rtexprvalue>true</rtexprvalue>
</attribute> </attribute>
<attribute>
<name>mimetypes</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag> </tag>
</taglib> </taglib>

View File

@@ -107,6 +107,7 @@ function updateButtonState()
value="#{DialogManager.bean.personPhotoRef}" value="#{DialogManager.bean.personPhotoRef}"
label="#{msg.select_avatar_prompt}" label="#{msg.select_avatar_prompt}"
initialSelection="#{DialogManager.bean.personProperties.homeFolder}" initialSelection="#{DialogManager.bean.personProperties.homeFolder}"
mimetypes="image/gif,image/jpeg,image/png"
styleClass="selector" /> styleClass="selector" />
</h:panelGrid> </h:panelGrid>

View File

@@ -66,6 +66,9 @@ var AlfPicker = new Class(
/* initial display style of the outer div */ /* initial display style of the outer div */
initialDisplayStyle: null, initialDisplayStyle: null,
/* addition service request attributes if any */
requestAttributes: null,
initialize: function(id, varName, service, formClientId, singleSelect) initialize: function(id, varName, service, formClientId, singleSelect)
{ {
this.id = id; this.id = id;
@@ -95,6 +98,11 @@ var AlfPicker = new Class(
this.preselected = Json.evaluate(jsonString); this.preselected = Json.evaluate(jsonString);
}, },
setRequestAttributes: function(attrs)
{
this.requestAttributes = attrs;
},
showSelector: function() showSelector: function()
{ {
// init selector state // init selector state
@@ -135,7 +143,11 @@ var AlfPicker = new Class(
this.hidePanels(); this.hidePanels();
// pop the parent off - peek for the grandparent // pop the parent off - peek for the grandparent
var parent = this.stack.pop(); var parent = this.stack.pop();
var grandParent = this.stack[this.stack.length-1]; var grandParent = null;
if (this.stack.length != 0)
{
grandParent = this.stack[this.stack.length-1];
}
this.getChildData(grandParent != null ? grandParent.id : null, this.populateChildren, parent.scrollpos); this.getChildData(grandParent != null ? grandParent.id : null, this.populateChildren, parent.scrollpos);
}, },
@@ -344,7 +356,7 @@ var AlfPicker = new Class(
else else
{ {
upLink.setStyle("display", "block"); upLink.setStyle("display", "block");
upLink.setProperty("href", "javascript:" + picker.varName + ".upClicked('" + picker.parent.id + "');"); upLink.setProperty("href", "javascript:" + picker.varName + ".upClicked();");
} }
// show what the parent next to the breadcrumb drop-down // show what the parent next to the breadcrumb drop-down
@@ -472,7 +484,9 @@ var AlfPicker = new Class(
var picker = this; var picker = this;
// execute ajax service call to retrieve list of child nodes as JSON response // execute ajax service call to retrieve list of child nodes as JSON response
new Ajax(getContextPath() + "/ajax/invoke/" + this.service + "?parent=" + (parent!=null ? parent : ""), new Ajax(getContextPath() + "/ajax/invoke/" + this.service +
"?parent=" + (parent!=null ? parent : "") +
(this.requestAttributes!=null ? ("&" + this.requestAttributes) : ""),
{ {
method: 'get', method: 'get',
async: false, async: false,
@@ -489,6 +503,15 @@ var AlfPicker = new Class(
$(picker.id + '-results-list').setStyle('visibility', 'visible'); $(picker.id + '-results-list').setStyle('visibility', 'visible');
$(picker.id + '-ajax-wait').setStyle('display', 'none'); $(picker.id + '-ajax-wait').setStyle('display', 'none');
} }
else
{
// display results list again and hide ajax wait panel
$(picker.id + '-results-list').setStyle('visibility', 'visible');
$(picker.id + '-ajax-wait').setStyle('display', 'none');
// display the error
alert(r);
}
}, },
onFailure: function (r) onFailure: function (r)
{ {
@@ -498,6 +521,13 @@ var AlfPicker = new Class(
sortByName: function(a, b) sortByName: function(a, b)
{ {
return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0)); if (a.selectable == b.selectable)
{
return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0));
}
else
{
return (a.selectable == false) ? -1 : 1;
}
} }
}); });