From 47e90ec3f1485a216159b8c159be5a85e7a56df8 Mon Sep 17 00:00:00 2001 From: Kevin Roast Date: Fri, 23 Nov 2007 19:58:42 +0000 Subject: [PATCH] First pass of Ajax object picker framework and JSF UI component wrappers. Ajax folder picker implementation. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@7429 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/web/bean/ajax/JSONWriter.java | 131 ++++++ .../alfresco/web/bean/ajax/PickerBean.java | 237 ++++++++++ .../web/ui/common/component/UIMenu.java | 2 +- .../common/renderer/ActionLinkRenderer.java | 2 +- .../ui/common/renderer/ModeListRenderer.java | 2 +- .../ui/repo/component/BaseAjaxItemPicker.java | 328 ++++++++++++++ .../ui/repo/component/UIAjaxFolderPicker.java | 43 ++ .../ui/repo/tag/AjaxFolderSelectorTag.java | 43 ++ .../web/ui/repo/tag/AjaxItemSelectorTag.java | 147 +++++++ .../org/alfresco/web/ui/repo/tag/PageTag.java | 25 +- source/web/WEB-INF/faces-config-beans.xml | 25 ++ source/web/WEB-INF/faces-config-repo.xml | 51 ++- source/web/WEB-INF/repo.tld | 54 +++ source/web/css/picker.css | 199 +++++++++ source/web/scripts/ajax/picker.js | 407 ++++++++++++++++++ 15 files changed, 1664 insertions(+), 32 deletions(-) create mode 100644 source/java/org/alfresco/web/bean/ajax/JSONWriter.java create mode 100644 source/java/org/alfresco/web/bean/ajax/PickerBean.java create mode 100644 source/java/org/alfresco/web/ui/repo/component/BaseAjaxItemPicker.java create mode 100644 source/java/org/alfresco/web/ui/repo/component/UIAjaxFolderPicker.java create mode 100644 source/java/org/alfresco/web/ui/repo/tag/AjaxFolderSelectorTag.java create mode 100644 source/java/org/alfresco/web/ui/repo/tag/AjaxItemSelectorTag.java create mode 100644 source/web/css/picker.css create mode 100644 source/web/scripts/ajax/picker.js diff --git a/source/java/org/alfresco/web/bean/ajax/JSONWriter.java b/source/java/org/alfresco/web/bean/ajax/JSONWriter.java new file mode 100644 index 0000000000..c8e8f36f08 --- /dev/null +++ b/source/java/org/alfresco/web/bean/ajax/JSONWriter.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing + */ +package org.alfresco.web.bean.ajax; + +import java.io.IOException; +import java.io.Writer; +import java.util.Stack; + +/** + * Very simple JSON writer. Wraps a Writer to output a JSON stream. + * + * @author Kevin Roast + */ +public class JSONWriter +{ + private Writer out; + private Stack stack = new Stack(); + + public JSONWriter(Writer out) + { + this.out = out; + stack.push(Boolean.FALSE); + } + + public void startArray() throws IOException + { + if (stack.peek() == true) out.write(", "); + out.write("["); + stack.pop(); + stack.push(Boolean.TRUE); + stack.push(Boolean.FALSE); + } + + public void endArray() throws IOException + { + out.write("]"); + stack.pop(); + } + + public void startObject() throws IOException + { + if (stack.peek() == true) out.write(", "); + out.write("{"); + stack.pop(); + stack.push(Boolean.TRUE); + stack.push(Boolean.FALSE); + } + + public void endObject() throws IOException + { + out.write("}"); + stack.pop(); + } + + public void startValue(String name) throws IOException + { + if (stack.peek() == true) out.write(", "); + out.write(name); + out.write(": "); + stack.pop(); + stack.push(Boolean.TRUE); + stack.push(Boolean.FALSE); + } + + public void endValue() + { + stack.pop(); + } + + public void writeValue(String name, String value) throws IOException + { + if (stack.peek() == true) out.write(", "); + out.write(name); + out.write(": \""); + out.write(value); + out.write('"'); + stack.pop(); + stack.push(Boolean.TRUE); + } + + public void writeValue(String name, int value) throws IOException + { + if (stack.peek() == true) out.write(", "); + out.write(name); + out.write(": "); + out.write(Integer.toString(value)); + stack.pop(); + stack.push(Boolean.TRUE); + } + + public void writeValue(String name, boolean value) throws IOException + { + if (stack.peek() == true) out.write(", "); + out.write(name); + out.write(": "); + out.write(Boolean.toString(value)); + stack.pop(); + stack.push(Boolean.TRUE); + } + + public void writeNullValue(String name) throws IOException + { + if (stack.peek() == true) out.write(", "); + out.write(name); + out.write(": null"); + stack.pop(); + stack.push(Boolean.TRUE); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/web/bean/ajax/PickerBean.java b/source/java/org/alfresco/web/bean/ajax/PickerBean.java new file mode 100644 index 0000000000..80b85afd82 --- /dev/null +++ b/source/java/org/alfresco/web/bean/ajax/PickerBean.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing + */ +package org.alfresco.web.bean.ajax; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +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.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.servlet.ajax.InvokeCommand; +import org.alfresco.web.bean.BrowseBean; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Bean backing the ajax requests for the ajax based picker components. + * + * @author Kevin Roast + */ +public class PickerBean +{ + private static Log logger = LogFactory.getLog(PickerBean.class); + + private CategoryService categoryService; + private NodeService nodeService; + private NodeService internalNodeService; + private FileFolderService fileFolderService; + + + /** + * @param categoryService The categoryService to set + */ + public void setCategoryService(CategoryService categoryService) + { + this.categoryService = categoryService; + } + + /** + * @param nodeService The nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param internalNodeService The internalNodeService to set. + */ + public void setInternalNodeService(NodeService internalNodeService) + { + this.internalNodeService = internalNodeService; + } + + /** + * @param fileFolderService the FileFolderService to set + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + + @InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML) + public void getCategoryNodes() throws Exception + { + FacesContext fc = FacesContext.getCurrentInstance(); + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); + tx.begin(); + + Collection childRefs; + NodeRef parentRef = null; + Map params = fc.getExternalContext().getRequestParameterMap(); + String strParentRef = (String)params.get("parent"); + if (strParentRef == null || strParentRef.length() == 0) + { + childRefs = this.categoryService.getRootCategories( + Repository.getStoreRef(), + ContentModel.ASPECT_GEN_CLASSIFIABLE); + } + else + { + parentRef = new NodeRef(strParentRef); + childRefs = this.categoryService.getChildren( + parentRef, + CategoryService.Mode.SUB_CATEGORIES, + CategoryService.Depth.IMMEDIATE); + } + + JSONWriter out = new JSONWriter(fc.getResponseWriter()); + out.startObject(); + out.startValue("parent"); + out.startObject(); + if (parentRef == null) + { + out.writeNullValue("id"); + out.writeValue("name", "Categories"); + } + else + { + out.writeValue("id", strParentRef); + out.writeValue("name", Repository.getNameForNode(this.internalNodeService, parentRef)); + } + out.endObject(); + out.endValue(); + out.startValue("children"); + out.startArray(); + for (ChildAssociationRef ref : childRefs) + { + NodeRef nodeRef = ref.getChildRef(); + out.startObject(); + out.writeValue("id", nodeRef.toString()); + out.writeValue("name", Repository.getNameForNode(this.internalNodeService, nodeRef)); + out.endObject(); + } + out.endArray(); + out.endValue(); + out.endObject(); + + tx.commit(); + } + catch (Throwable err) + { + Utils.addErrorMessage("PickerBean exception in getCategoryRootNodes()", err); + fc.getResponseWriter().write("ERROR: " + err.getMessage()); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + } + + @InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML) + public void getFolderNodes() throws Exception + { + FacesContext fc = FacesContext.getCurrentInstance(); + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); + tx.begin(); + + List childRefs; + NodeRef parentRef = null; + Map params = fc.getExternalContext().getRequestParameterMap(); + String strParentRef = (String)params.get("parent"); + if (strParentRef == null || strParentRef.length() == 0) + { + parentRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc)); + } + else + { + parentRef = new NodeRef(strParentRef); + } + List folders = this.fileFolderService.listFolders(parentRef); + + JSONWriter out = new JSONWriter(fc.getResponseWriter()); + out.startObject(); + out.startValue("parent"); + out.startObject(); + if (strParentRef == null || strParentRef.length() == 0) + { + out.writeNullValue("id"); + out.writeValue("name", Repository.getNameForNode(this.internalNodeService, parentRef)); + } + else + { + out.writeValue("id", strParentRef); + out.writeValue("name", Repository.getNameForNode(this.internalNodeService, parentRef)); + } + out.endObject(); + out.endValue(); + out.startValue("children"); + out.startArray(); + + // filter out those children that are not spaces + for (FileInfo folder : folders) + { + out.startObject(); + out.writeValue("id", folder.getNodeRef().toString()); + out.writeValue("name", (String)folder.getProperties().get(ContentModel.PROP_NAME)); + String icon = (String)folder.getProperties().get(ApplicationModel.PROP_ICON); + out.writeValue("icon", (icon != null ? icon + "-16.gif" : BrowseBean.SPACE_SMALL_DEFAULT + ".gif")); + out.endObject(); + } + + out.endArray(); + out.endValue(); + out.endObject(); + + tx.commit(); + } + catch (Throwable err) + { + Utils.addErrorMessage("PickerBean exception in getFolderNodes()", err); + fc.getResponseWriter().write("ERROR: " + err.getMessage()); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/web/ui/common/component/UIMenu.java b/source/java/org/alfresco/web/ui/common/component/UIMenu.java index 212a6842f1..2292563841 100644 --- a/source/java/org/alfresco/web/ui/common/component/UIMenu.java +++ b/source/java/org/alfresco/web/ui/common/component/UIMenu.java @@ -81,7 +81,7 @@ public class UIMenu extends SelfRenderingComponent // output image if (getAttributes().get("image") != null) { - out.write(Utils.buildImageTag(context, (String)getAttributes().get("image"), null, "middle")); + out.write(Utils.buildImageTag(context, (String)getAttributes().get("image"), null, "-4px")); } out.write(""); diff --git a/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java b/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java index dfc83485b2..007ad1056b 100644 --- a/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java +++ b/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java @@ -142,7 +142,7 @@ public class ActionLinkRenderer extends BaseRenderer renderActionLinkAnchor(context, out, link); } - out.write(Utils.buildImageTag(context, image, (String)link.getValue(), "middle")); + out.write(Utils.buildImageTag(context, image, (String)link.getValue(), "-4px")); if (link.getShowLink() == false) { diff --git a/source/java/org/alfresco/web/ui/common/renderer/ModeListRenderer.java b/source/java/org/alfresco/web/ui/common/renderer/ModeListRenderer.java index 8bd3d219d3..d2f7ec8cc5 100644 --- a/source/java/org/alfresco/web/ui/common/renderer/ModeListRenderer.java +++ b/source/java/org/alfresco/web/ui/common/renderer/ModeListRenderer.java @@ -214,7 +214,7 @@ public class ModeListRenderer extends BaseRenderer // output image if (list.getMenuImage() != null) { - out.write(Utils.buildImageTag(context, list.getMenuImage(), null, "middle")); + out.write(Utils.buildImageTag(context, list.getMenuImage(), null, "-4px")); } out.write(""); diff --git a/source/java/org/alfresco/web/ui/repo/component/BaseAjaxItemPicker.java b/source/java/org/alfresco/web/ui/repo/component/BaseAjaxItemPicker.java new file mode 100644 index 0000000000..0356052349 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/component/BaseAjaxItemPicker.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing + */ +package org.alfresco.web.ui.repo.component; + +import java.io.IOException; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.component.UIInput; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.ValueBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.FacesEvent; + +import org.alfresco.web.app.Application; + +/** + * @author Kevin Roast + */ +public abstract class BaseAjaxItemPicker extends UIInput +{ + private static final String MSG_GO_UP = "go_up"; + private static final String MSG_OK = "ok"; + private static final String MSG_CANCEL = "cancel"; + + /** label to be displayed before an item is selected */ + protected String label = null; + + /** id of the initially selected item, if value is not set */ + protected String initialSelectionId = null; + + /** flag to show whether the component is disabled */ + protected Boolean disabled; + + /** True for single select mode, false for multi-select mode */ + protected Boolean singleSelect; + + protected static int ACTION_DONE = 0; + protected static int ACTION_CANCEL = 1; + + + public BaseAjaxItemPicker() + { + setRendererType(null); + } + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + @Override + public abstract String getFamily(); + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.label = (String)values[1]; + this.singleSelect = (Boolean)values[2]; + this.initialSelectionId = (String)values[3]; + this.disabled = (Boolean)values[4]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[] { + // standard component attributes are saved by the super class + super.saveState(context), + this.label, + this.singleSelect, + this.initialSelectionId, + this.disabled}; + return (values); + } + + /** + * @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext) + */ + public void decode(FacesContext context) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + String fieldId = getHiddenFieldName(); + String value = (String)requestMap.get(fieldId); + + + } + + /** + * @see javax.faces.component.UIInput#broadcast(javax.faces.event.FacesEvent) + */ + public void broadcast(FacesEvent event) throws AbortProcessingException + { + super.broadcast(event); + } + + /** + * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext fc) throws IOException + { + if (isRendered() == false) + { + return; + } + + ResponseWriter out = fc.getResponseWriter(); + + ResourceBundle msg = Application.getBundle(fc); + + // TODO: from submitted value or 'none' + String selection = "none"; + + // TODO: output images with context correctly + + String divId = getId(); + String objId = divId + "Obj"; + String contextPath = fc.getExternalContext().getRequestContextPath(); + out.write(""); + + out.write("
") ; + out.write(" "); + out.write("
"); + out.write(" <" + selection + ">"); + out.write(" "); + out.write(msg.getString(getLabel())); + out.write(""); + out.write("
"); + // container for item navigation + out.write("
"); + out.write("
"); + out.write("
"); + out.write("
"); + out.write(" "); + out.write(" ");
+      out.write(msg.getString(MSG_GO_UP));
+      out.write(""); + out.write(" "); + out.write(" "); + out.write("
"); + out.write(" "); + out.write("
"); + out.write("
"); + out.write("
"); + // container for item selection + out.write("
"); + out.write("
"); + out.write("
"); + out.write("
"); + out.write("
"); + out.write("
"); + out.write(" "); + // TODO: Cancel button + out.write("
"); + out.write("
"); + // container for selected items + out.write("
"); + out.write("
"); + } + + /** + * @return the ajax service bean call, for instance 'PickerBean.getFolderNodes' + */ + protected abstract String getServiceCall(); + + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * @return Returns the label. + */ + public String getLabel() + { + ValueBinding vb = getValueBinding("label"); + if (vb != null) + { + this.label = (String)vb.getValue(getFacesContext()); + } + + return this.label; + } + + /** + * @param label The label to set. + */ + public void setLabel(String label) + { + this.label = label; + } + + /** + * @return Returns the initial selecttion. + */ + public String getInitialSelection() + { + ValueBinding vb = getValueBinding("initialSelection"); + if (vb != null) + { + this.initialSelectionId = (String)vb.getValue(getFacesContext()); + } + + return this.initialSelectionId; + } + + /** + * @param initialSelection The initial selection to set. + */ + public void setInitialSelection(String initialSelection) + { + this.initialSelectionId = initialSelection; + } + + /** + * Determines whether the component should be rendered in a disabled state + * + * @return Returns whether the component is disabled + */ + public boolean isDisabled() + { + if (this.disabled == null) + { + ValueBinding vb = getValueBinding("disabled"); + if (vb != null) + { + this.disabled = (Boolean)vb.getValue(getFacesContext()); + } + } + + return this.disabled == null ? Boolean.FALSE : this.disabled; + } + + /** + * Determines whether the component should be rendered in a disabled state + * + * @param disabled true to disable the component + */ + public void setDisabled(boolean disabled) + { + this.disabled = disabled; + } + + /** + * @return true is single select mode, false for multi-select + */ + public Boolean getSingleSelect() + { + if (this.singleSelect == null) + { + ValueBinding vb = getValueBinding("singleSelect"); + if (vb != null) + { + this.singleSelect = (Boolean)vb.getValue(getFacesContext()); + } + } + + return this.singleSelect == null ? Boolean.TRUE : this.singleSelect; + } + + /** + * @param singleSelect true for single select mode, false for multi-select + */ + public void setSingleSelect(Boolean singleSelect) + { + this.singleSelect = singleSelect; + } + + + // ------------------------------------------------------------------------------ + // Protected helpers + + /** + * We use a unique hidden field name based on our client Id. + * This is on the assumption that there won't be many selectors on screen at once! + * Also means we have less values to decode on submit. + * + * @return hidden field name + */ + protected String getHiddenFieldName() + { + return this.getClientId(getFacesContext()); + } +} diff --git a/source/java/org/alfresco/web/ui/repo/component/UIAjaxFolderPicker.java b/source/java/org/alfresco/web/ui/repo/component/UIAjaxFolderPicker.java new file mode 100644 index 0000000000..2301f2526d --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/component/UIAjaxFolderPicker.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing + */ +package org.alfresco.web.ui.repo.component; + +/** + * @author Kevin Roast + */ +public class UIAjaxFolderPicker extends BaseAjaxItemPicker +{ + @Override + public String getFamily() + { + return "org.alfresco.faces.AjaxFolderPicker"; + } + + @Override + protected String getServiceCall() + { + return "PickerBean.getFolderNodes"; + } +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/AjaxFolderSelectorTag.java b/source/java/org/alfresco/web/ui/repo/tag/AjaxFolderSelectorTag.java new file mode 100644 index 0000000000..e73b008d28 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/tag/AjaxFolderSelectorTag.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +/* + * Created on 25-May-2005 + */ +package org.alfresco.web.ui.repo.tag; + + +/** + * @author Kevin Roast + */ +public class AjaxFolderSelectorTag extends AjaxItemSelectorTag +{ + /** + * @see javax.faces.webapp.UIComponentTag#getComponentType() + */ + public String getComponentType() + { + return "org.alfresco.faces.AjaxFolderPicker"; + } +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/AjaxItemSelectorTag.java b/source/java/org/alfresco/web/ui/repo/tag/AjaxItemSelectorTag.java new file mode 100644 index 0000000000..88d2c260bc --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/tag/AjaxItemSelectorTag.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +/* + * Created on 25-May-2005 + */ +package org.alfresco.web.ui.repo.tag; + +import javax.faces.component.UIComponent; + +import org.alfresco.web.ui.common.tag.BaseComponentTag; + +/** + * Base class for the item selector tag + * + * @author Kevin Roast + */ +public abstract class AjaxItemSelectorTag extends BaseComponentTag +{ + /** the value */ + private String value; + + /** the label */ + private String label; + + /** the id of initial selection */ + private String initialSelection; + + /** Whether the component is single or multi-select */ + private String singleSelect; + + /** Whether the component is disabled */ + private String disabled; + + /** + * @see javax.faces.webapp.UIComponentTag#getComponentType() + */ + public abstract String getComponentType(); + + /** + * @see javax.faces.webapp.UIComponentTag#getRendererType() + */ + public String getRendererType() + { + return null; + } + + /** + * @see javax.faces.webapp.UIComponentTag#setProperties(javax.faces.component.UIComponent) + */ + protected void setProperties(UIComponent component) + { + super.setProperties(component); + + setStringBindingProperty(component, "value", this.value); + setStringBindingProperty(component, "initialSelection", this.initialSelection); + setStringProperty(component, "label", this.label); + setBooleanProperty(component, "singleSelect", this.singleSelect); + setBooleanProperty(component, "disabled", this.disabled); + } + + /** + * @see org.alfresco.web.ui.common.tag.HtmlComponentTag#release() + */ + public void release() + { + super.release(); + + this.value = null; + this.label = null; + this.singleSelect = null; + this.initialSelection = null; + this.disabled = null; + } + + /** + * Set the value + * + * @param value the value + */ + public void setValue(String value) + { + this.value = value; + } + + /** + * Set the label + * + * @param label the label + */ + public void setLabel(String label) + { + this.label = label; + } + + /** + * Set the singleSelect + * + * @param singleSelect the singleSelect + */ + public void setSingleSelect(String singleSelect) + { + this.singleSelect = singleSelect; + } + + /** + * Sets the id of the item to be initially selected, this is overridden + * however if a value is supplied + * + * @param initialSelection The id of the initial selected item + */ + public void setInitialSelection(String initialSelection) + { + this.initialSelection = initialSelection; + } + + /** + * Sets whether the component should be rendered in a disabled state + * + * @param disabled true to render the component in a disabled state + */ + public void setDisabled(String disabled) + { + this.disabled = disabled; + } +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/PageTag.java b/source/java/org/alfresco/web/ui/repo/tag/PageTag.java index 20d0fb5c0f..90d77c4a96 100644 --- a/source/java/org/alfresco/web/ui/repo/tag/PageTag.java +++ b/source/java/org/alfresco/web/ui/repo/tag/PageTag.java @@ -45,9 +45,9 @@ public class PageTag extends TagSupport private static final long serialVersionUID = 8142765393181557228L; private final static String SCRIPTS_START = "\n"; + private final static String SCRIPTS_END = "\">\n"; private final static String STYLES_START = "\n"; + private final static String STYLES_MAIN = "\" type=\"text/css\">\n"; private final static String[] SCRIPTS = { @@ -66,7 +66,15 @@ public class PageTag extends TagSupport // common Alfresco util methods "/scripts/ajax/common.js", // pop-up panel helper objects - "/scripts/ajax/summary-info.js" + "/scripts/ajax/summary-info.js", + // ajax pickers + "/scripts/ajax/picker.js" + }; + + private final static String[] CSS = + { + "/css/main.css", + "/css/picker.css" }; /** @@ -219,10 +227,15 @@ public class PageTag extends TagSupport } // CSS style includes - out.write(STYLES_START); - out.write(reqPath); - out.write(STYLES_MAIN); + for (final String css : PageTag.CSS) + { + out.write(STYLES_START); + out.write(reqPath); + out.write(css); + out.write(STYLES_MAIN); + } + // JavaScript includes for (final String s : PageTag.SCRIPTS) { out.write(SCRIPTS_START); diff --git a/source/web/WEB-INF/faces-config-beans.xml b/source/web/WEB-INF/faces-config-beans.xml index 4a28d6c516..0dbf65dfaa 100644 --- a/source/web/WEB-INF/faces-config-beans.xml +++ b/source/web/WEB-INF/faces-config-beans.xml @@ -4196,6 +4196,31 @@ org.alfresco.web.bean.ajax.PresenceProxyBean request + + + + Bean backing the ajax requests for the ajax based picker components + + PickerBean + org.alfresco.web.bean.ajax.PickerBean + request + + categoryService + #{CategoryService} + + + nodeService + #{NodeService} + + + internalNodeService + #{nodeService} + + + fileFolderService + #{FileFolderService} + + diff --git a/source/web/WEB-INF/faces-config-repo.xml b/source/web/WEB-INF/faces-config-repo.xml index 6b44845152..cf037024a2 100644 --- a/source/web/WEB-INF/faces-config-repo.xml +++ b/source/web/WEB-INF/faces-config-repo.xml @@ -172,11 +172,11 @@ org.alfresco.faces.WorkflowSummary org.alfresco.web.ui.repo.component.UIWorkflowSummary - - - - org.alfresco.faces.WorkflowHistory - org.alfresco.web.ui.repo.component.UIWorkflowHistory + + + + org.alfresco.faces.WorkflowHistory + org.alfresco.web.ui.repo.component.UIWorkflowHistory @@ -208,6 +208,11 @@ org.alfresco.faces.CategoryBrowser org.alfresco.web.ui.repo.component.UICategoryBrowser + + + org.alfresco.faces.AjaxFolderPicker + org.alfresco.web.ui.repo.component.UIAjaxFolderPicker + @@ -291,24 +296,24 @@ org.alfresco.faces.Tree org.alfresco.faces.Yahoo org.alfresco.web.ui.repo.renderer.YahooTreeRenderer - - - - javax.faces.Input - org.alfresco.faces.MultilingualText - org.alfresco.web.ui.repo.renderer.MultilingualTextRenderer - - - - javax.faces.Input - org.alfresco.faces.MultilingualTextArea - org.alfresco.web.ui.repo.renderer.MultilingualTextAreaRenderer - - - - javax.faces.Output - org.alfresco.faces.MultilingualText - org.alfresco.web.ui.repo.renderer.MultilingualTextRenderer + + + + javax.faces.Input + org.alfresco.faces.MultilingualText + org.alfresco.web.ui.repo.renderer.MultilingualTextRenderer + + + + javax.faces.Input + org.alfresco.faces.MultilingualTextArea + org.alfresco.web.ui.repo.renderer.MultilingualTextAreaRenderer + + + + javax.faces.Output + org.alfresco.faces.MultilingualText + org.alfresco.web.ui.repo.renderer.MultilingualTextRenderer diff --git a/source/web/WEB-INF/repo.tld b/source/web/WEB-INF/repo.tld index 20f6f97aaa..caae7980f4 100644 --- a/source/web/WEB-INF/repo.tld +++ b/source/web/WEB-INF/repo.tld @@ -2204,4 +2204,58 @@ + + ajaxFolderSelector + org.alfresco.web.ui.repo.tag.AjaxFolderSelectorTag + JSP + + + id + false + true + + + + binding + false + true + + + + rendered + false + true + + + + value + false + true + + + + label + true + true + + + + initialSelection + false + true + + + + singleSelect + false + true + + + + disabled + false + true + + + diff --git a/source/web/css/picker.css b/source/web/css/picker.css new file mode 100644 index 0000000000..640028a0cd --- /dev/null +++ b/source/web/css/picker.css @@ -0,0 +1,199 @@ +.pickerNoSelectedItems +{ + margin-top: 2px +} + +.pickerSelectedItems +{ + padding-left: 6px; + vertical-align: top; +} + +.pickerSelectedItemsFocus +{ + background-color: #faf7ce; +} + +.pickerSelectedItemsActions div +{ + padding: 4px; +} + +.pickerSelectedItemsActions div img +{ + vertical-align: -4px; +} + +div.pickerSelectedItem +{ + background-repeat: no-repeat; + background-position: 2px 2px; + margin: 2px; + padding: 2px; + border: 1px dashed #e7e7e7; + height: 16px; +} + +span.pickerSelectedItemText +{ + float: left; + margin-left: 18px; +} + +span.pickerSelectedItemAction +{ + float: right; +} + +img.pickerSelectedIcon +{ + margin-left: 4px; + vertical-align: -4px; + cursor: pointer; +} + +div.pickerSelector +{ + width: 300px; + border: 1px solid #e7e7e7; + background-color: #DFE6ED; + padding: 4px; + margin-top: 5px; + display: none; +} + +div.pickerResultsList +{ + *width: 300px; + height: 120px; + overflow: auto; + clear: both; +} + +div.pickerResults +{ + background-color: white; + border: 1px solid #e7e7e7; + padding: 3px 3px 5px 3px; + margin-bottom: 4px; + -moz-border-radius: 8px; +} + +div.pickerResultsHeader +{ + margin-bottom: 2px; + float: left; +} + +div.pickerAjaxWait +{ + background-image: url(../images/icons/ajax_anim.gif); + background-position: center; + background-repeat: no-repeat; + background-position: 50% 65%; + width: 292px; + height: 120px; + overflow: hidden; + display: none; + position: absolute; +} + +.pickerNavControls +{ + float: left; + border-bottom: 1px solid #b2b2b2; +} + +img.pickerActionButton +{ + margin-left: 2px; +} + +div.pickerResultsRow +{ + padding: 4px; + height: 16px; +} + +.pickerResultsOddRow +{ + background-color: #f1f1f1; +} + +.pickerResultsEvenRow +{ + background-color: #ffffff; +} + +.pickerResultIcon +{ + float: left; + margin-top: -2px; +} + +.pickerResultName +{ + margin-left: 6px; + padding-top: 2px; +} + +.pickerResultActions +{ + float: right; +} + +div.pickerDoneButton +{ + margin: 0 auto; + width: 50px; + padding: 3px; + background-color: #f1f1f1; + border: 1px solid #cccccc; + text-align: center; +} + +div.pickerFinishControls +{ + text-align: center; +} + +.pickerNavBreadcrumb +{ + float: left; + margin-left: 10px; +} + +.pickerNavBreadcrumb img +{ + vertical-align: -7px; + padding-left: 2px; +} + +div.pickerNavBreadcrumbPanel +{ + position: absolute; + max-width: 250px; + padding: 2px; + margin-top: 20px; + background-color: #f1f1f1; + border: 1px solid #cccccc; + display: none; +} + +div.pickerNavBreadcrumbItem +{ + padding: 2px; +} + +.pickerNavUp +{ + float: right; + margin-top: 2px; + margin-right: 15px; + text-align: right: +} + +.pickerNavUp img +{ + vertical-align: -4px; +} \ No newline at end of file diff --git a/source/web/scripts/ajax/picker.js b/source/web/scripts/ajax/picker.js new file mode 100644 index 0000000000..0c85a6946b --- /dev/null +++ b/source/web/scripts/ajax/picker.js @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * Prerequisites: common.js + * mootools.v1.11.js + */ + +// Picker class definition +var AlfPicker = new Class( +{ + /* id of the picker */ + id: null, + + /* variable name being used */ + varName: null, + + /* the item the picker will start with */ + startId: null, + + /* list of items currently selected */ + selected: null, + + /* the current parent being shown */ + parent: null, + + /* the list of items currently displayed */ + items: [], + + /* parent stack for the Navigate Up action*/ + stack: [], + + /* row type toggle */ + oddRow: true, + + /* ajax service call to retrieve data */ + service: null, + + /* default icon to use if not provided by the associated service */ + defaultIcon: null, + + /* single selection mode flag */ + singleSelect: false, + + initialize: function(id, varName, service, singleSelect) + { + this.id = id; + this.varName = varName; + this.parent = this.startId; + this.selected = []; + this.service = service; + if (singleSelect != undefined) + { + this.singleSelect = singleSelect; + } + }, + + setDefaultIcon: function(icon) + { + this.defaultIcon = icon; + }, + + setStartId: function(id) + { + this.startId = id; + }, + + showSelector: function() + { + $(this.id + "-selector").setStyle("display", "block"); + $(this.id + "-selected").setStyle("display", "block"); + $(this.id + "-noitems").setStyle("display", "none"); + if (this.singleSelect) + { + $(this.id + "-finish").setStyle("display", "none"); + } + + // simulate an ajax request for children of start item + this.getChildData(this.startId, this.populateChildren); + }, + + childClicked: function(index) + { + this.hidePanels(); + var item = this.items[index]; + // add an extra property to record the scroll position for this item + item.scrollpos = $(this.id + "-results-list").scrollTop; + this.stack.push(item); // ready for the breadcrumb redraw after the child data request + this.getChildData(item.id, this.populateChildren) + }, + + upClicked: function() + { + this.hidePanels(); + // pop the parent off - peek for the grandparent + var parent = this.stack.pop(); + var grandParent = this.stack[this.stack.length-1]; + this.getChildData(grandParent != null ? grandParent.id : null, this.populateChildren, parent.scrollpos); + }, + + addItem: function(index) + { + var item = this.items[index]; + + // add item to list of selected items + this.selected.push(item); + + if (this.singleSelect) + { + this.doneClicked(); + } + else + { + // add the item to list above the selector + var itemId = this.id + "-sel-" + item.id; + var itemDiv = new Element("div", {"id": itemId, "class": "pickerSelectedItem"}); + + var itemSpan = new Element("span", {"class": "pickerSelectedItemText"}); + itemSpan.appendText(item.name); + itemSpan.injectInside(itemDiv); + + var actionSpan = new Element("span", {"class": "pickerSelectedItemAction"}); + var actionScript = "javascript:" + this.varName + ".delItem('" + item.id + "');"; + var actionLink = new Element("a", {"href": actionScript}); + var deleteIcon = new Element("img", {"src": getContextPath() + "/images/icons/minus.gif", "class": "pickerSelectedIcon", + "border": 0, "title": "Remove", "alt": "Remove"}); + deleteIcon.injectInside(actionLink); + actionLink.injectInside(actionSpan); + actionSpan.injectInside(itemDiv); + + // add mouse enter/leave enter to toggle delete icon (and toggle margin on outer div) + itemDiv.addEvent('mouseenter', function(e) { + $E('.pickerSelectedIcon', itemDiv).setStyle("opacity", 1); + }); + itemDiv.addEvent('mouseleave', function(e) { + $E('.pickerSelectedIcon', itemDiv).setStyle("opacity", 0); + }); + // add the item to the main selected item div + itemDiv.injectInside($(this.id + "-selected")); + + // set the background image now the itemdiv has been added to the DOM (for IE) + itemDiv.setStyle("background-image", "url(" + getContextPath() + "/images/icons/" + item.icon + ")"); + + // set opacity the style now the item has been added to the DOM (for IE) + $E('.pickerSelectedIcon', itemDiv).setStyle("opacity", 0); + + // apply the effect + var fx = new Fx.Styles(itemDiv, {duration: 1000, wait: false, transition: Fx.Transitions.Quad.easeOut}); + fx.start({'background-color': ['#faf7ce', '#ffffff']}); + + // hide the Add button as this item is now added + $(this.id + "-add-" + item.id).setStyle("display", "none"); + } + }, + + delItem: function(itemId) + { + // remove item from the selected items list + for (i=0; i b.name) ? 1 : 0)); + } +}); \ No newline at end of file