mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
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:
@@ -29,7 +29,8 @@ import java.io.Writer;
|
||||
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
|
||||
*/
|
||||
|
@@ -25,8 +25,11 @@
|
||||
package org.alfresco.web.bean.ajax;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.faces.context.FacesContext;
|
||||
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.FileInfo;
|
||||
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.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
@@ -58,6 +63,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
*/
|
||||
public class PickerBean
|
||||
{
|
||||
private static final String MSG_CATEGORIES = "categories";
|
||||
private static final String ID_URL = "url";
|
||||
private static final String ID_ICON = "icon";
|
||||
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_ID = "id";
|
||||
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/";
|
||||
|
||||
@@ -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)
|
||||
public void getCategoryNodes() throws Exception
|
||||
{
|
||||
@@ -124,7 +139,7 @@ public class PickerBean
|
||||
Collection<ChildAssociationRef> childRefs;
|
||||
NodeRef parentRef = null;
|
||||
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)
|
||||
{
|
||||
childRefs = this.categoryService.getRootCategories(
|
||||
@@ -147,7 +162,7 @@ public class PickerBean
|
||||
if (parentRef == null)
|
||||
{
|
||||
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_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)
|
||||
public void getFolderNodes() throws Exception
|
||||
{
|
||||
@@ -197,7 +219,7 @@ public class PickerBean
|
||||
NodeRef companyHomeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc));
|
||||
NodeRef parentRef = null;
|
||||
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)
|
||||
{
|
||||
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)
|
||||
public void getFileFolderNodes() throws Exception
|
||||
{
|
||||
@@ -261,12 +295,13 @@ public class PickerBean
|
||||
tx.begin();
|
||||
|
||||
DictionaryService dd = Repository.getServiceRegistry(fc).getDictionaryService();
|
||||
ContentService cs = Repository.getServiceRegistry(fc).getContentService();
|
||||
|
||||
List<ChildAssociationRef> childRefs;
|
||||
NodeRef companyHomeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc));
|
||||
NodeRef parentRef = null;
|
||||
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)
|
||||
{
|
||||
parentRef = companyHomeRef;
|
||||
@@ -276,6 +311,20 @@ public class PickerBean
|
||||
{
|
||||
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);
|
||||
|
||||
JSONWriter out = new JSONWriter(fc.getResponseWriter());
|
||||
@@ -294,29 +343,47 @@ public class PickerBean
|
||||
out.startValue(ID_CHILDREN);
|
||||
out.startArray();
|
||||
|
||||
// filter out those children that are not spaces
|
||||
for (FileInfo item : items)
|
||||
{
|
||||
if (dd.isSubClass(this.internalNodeService.getType(item.getNodeRef()), ContentModel.TYPE_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);
|
||||
out.writeValue(ID_ICON, FOLDER_IMAGE_PREFIX + (icon != null ? icon + "-16.gif" : BrowseBean.SPACE_SMALL_DEFAULT + ".gif"));
|
||||
out.writeValue(ID_SELECTABLE, false);
|
||||
out.endObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
// must be a file
|
||||
boolean validFile = true;
|
||||
if (mimetypes != null)
|
||||
{
|
||||
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);
|
||||
if (dd.isSubClass(this.internalNodeService.getType(item.getNodeRef()), ContentModel.TYPE_FOLDER))
|
||||
{
|
||||
// found a folder
|
||||
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_SELECTABLE, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// must be a file
|
||||
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.endArray();
|
||||
out.endValue();
|
||||
|
@@ -49,6 +49,14 @@ import org.alfresco.web.ui.common.Utils;
|
||||
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
|
||||
*/
|
||||
public abstract class BaseAjaxItemPicker extends UIInput
|
||||
@@ -121,7 +129,7 @@ public abstract class BaseAjaxItemPicker extends UIInput
|
||||
this.initialSelectionId,
|
||||
this.disabled,
|
||||
this.height};
|
||||
return (values);
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,6 +276,12 @@ public abstract class BaseAjaxItemPicker extends UIInput
|
||||
{
|
||||
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("window.addEvent('domready', init" + divId + ");");
|
||||
out.write("</script>");
|
||||
@@ -371,6 +385,14 @@ public abstract class BaseAjaxItemPicker extends UIInput
|
||||
*/
|
||||
protected abstract String getDefaultIcon();
|
||||
|
||||
/**
|
||||
* @return custom request properties optional for some specific picker implementations
|
||||
*/
|
||||
protected String getRequestAttributes()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Strongly typed component property accessors
|
||||
|
@@ -25,6 +25,8 @@
|
||||
package org.alfresco.web.ui.repo.component;
|
||||
|
||||
/**
|
||||
* JSF Ajax object picker for navigating through and selecting categories.
|
||||
*
|
||||
* @author Kevin Roast
|
||||
*/
|
||||
public class UIAjaxCategoryPicker extends BaseAjaxItemPicker
|
||||
|
@@ -24,17 +24,52 @@
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public class UIAjaxFilePicker extends BaseAjaxItemPicker
|
||||
{
|
||||
/** list of mimetypes to restrict the available file list */
|
||||
private String mimetypes = null;
|
||||
|
||||
@Override
|
||||
public String getFamily()
|
||||
{
|
||||
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
|
||||
protected String getServiceCall()
|
||||
{
|
||||
@@ -47,4 +82,51 @@ public class UIAjaxFilePicker extends BaseAjaxItemPicker
|
||||
// none required - we always return an icon name in the service call
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,8 @@
|
||||
package org.alfresco.web.ui.repo.component;
|
||||
|
||||
/**
|
||||
* JSF Ajax object picker for navigating through and selecting folders.
|
||||
*
|
||||
* @author Kevin Roast
|
||||
*/
|
||||
public class UIAjaxFolderPicker extends BaseAjaxItemPicker
|
||||
|
@@ -27,6 +27,8 @@
|
||||
*/
|
||||
package org.alfresco.web.ui.repo.tag;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
|
||||
|
||||
/**
|
||||
* @author Kevin Roast
|
||||
@@ -40,4 +42,36 @@ public class AjaxFileSelectorTag extends AjaxItemSelectorTag
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
@@ -2418,6 +2418,12 @@
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
|
||||
<attribute>
|
||||
<name>mimetypes</name>
|
||||
<required>false</required>
|
||||
<rtexprvalue>true</rtexprvalue>
|
||||
</attribute>
|
||||
</tag>
|
||||
|
||||
</taglib>
|
||||
|
@@ -107,6 +107,7 @@ function updateButtonState()
|
||||
value="#{DialogManager.bean.personPhotoRef}"
|
||||
label="#{msg.select_avatar_prompt}"
|
||||
initialSelection="#{DialogManager.bean.personProperties.homeFolder}"
|
||||
mimetypes="image/gif,image/jpeg,image/png"
|
||||
styleClass="selector" />
|
||||
|
||||
</h:panelGrid>
|
||||
|
@@ -66,6 +66,9 @@ var AlfPicker = new Class(
|
||||
/* initial display style of the outer div */
|
||||
initialDisplayStyle: null,
|
||||
|
||||
/* addition service request attributes if any */
|
||||
requestAttributes: null,
|
||||
|
||||
initialize: function(id, varName, service, formClientId, singleSelect)
|
||||
{
|
||||
this.id = id;
|
||||
@@ -95,6 +98,11 @@ var AlfPicker = new Class(
|
||||
this.preselected = Json.evaluate(jsonString);
|
||||
},
|
||||
|
||||
setRequestAttributes: function(attrs)
|
||||
{
|
||||
this.requestAttributes = attrs;
|
||||
},
|
||||
|
||||
showSelector: function()
|
||||
{
|
||||
// init selector state
|
||||
@@ -135,7 +143,11 @@ var AlfPicker = new Class(
|
||||
this.hidePanels();
|
||||
// pop the parent off - peek for the grandparent
|
||||
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);
|
||||
},
|
||||
|
||||
@@ -344,7 +356,7 @@ var AlfPicker = new Class(
|
||||
else
|
||||
{
|
||||
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
|
||||
@@ -472,7 +484,9 @@ var AlfPicker = new Class(
|
||||
var picker = this;
|
||||
|
||||
// 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',
|
||||
async: false,
|
||||
@@ -489,6 +503,15 @@ var AlfPicker = new Class(
|
||||
$(picker.id + '-results-list').setStyle('visibility', 'visible');
|
||||
$(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)
|
||||
{
|
||||
@@ -497,7 +520,14 @@ var AlfPicker = new Class(
|
||||
},
|
||||
|
||||
sortByName: function(a, b)
|
||||
{
|
||||
if (a.selectable == b.selectable)
|
||||
{
|
||||
return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (a.selectable == false) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user