diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 0a2d687628..ef90f28ee0 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -1816,6 +1816,9 @@ click_to_edit=click to edit # File Picker go_up=Go up +# Tag picker +click_to_select_tag=Click to select tags + # Category browsing category_browser_plugin_label=Categories category_browser_plugin_description=Category Browsing diff --git a/config/alfresco/templates/webscripts/org/alfresco/collaboration/tagActions.post.html.ftl b/config/alfresco/templates/webscripts/org/alfresco/collaboration/tagActions.post.html.ftl index da081ef8cd..e2a6dff774 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/collaboration/tagActions.post.html.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/collaboration/tagActions.post.html.ftl @@ -1,4 +1,5 @@ { "statusString":"${tagActions.resultString}", - "statusCode":${tagActions.resultCode?string} + "statusCode":${tagActions.resultCode?string}, + "newTag":"${tagActions.newTag?string}" } \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/collaboration/tagActions.post.js b/config/alfresco/templates/webscripts/org/alfresco/collaboration/tagActions.post.js index 09792e92d1..8be0f518ae 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/collaboration/tagActions.post.js +++ b/config/alfresco/templates/webscripts/org/alfresco/collaboration/tagActions.post.js @@ -1,128 +1,140 @@ -var action = args.a; - -/* Debug Inputs */ -if (args["add"] != null) {action = "add";} -else if (args["remove"] != null) {action = "remove";} - -model.tagActions = tagActions(action, args.n, args.t); +model.tagActions = tagActions(args.a, args.n, args.t); function tagActions(action, nodeId, tagName) { var resultString = "Action failed"; var resultCode = false; + var node = null; + var newTag = null; + var newTagNodeRef = ""; - if ((nodeId != "") && (nodeId != null) && - (tagName != "") && (tagName != null)) + if ((tagName != "") && (tagName != null)) { - var node = search.findNode("workspace://SpacesStore/" + nodeId); tagName = tagName.toLowerCase(); - - if (node != null) + if (action == "add") { - try + // Make sure the tag is in the repo + newTag = createTag(tagName); + if (newTag != null) { - var tags, newTag; - - if (action == "add") + resultString = "Tag added"; + resultCode = true; + newTag.nodeRef.toString(); + } + else + { + resultString = "Tag '" + tagName + "' not indexed"; + } + } + + // Adding the tag to a node? + if ((nodeId != "") && (nodeId != null)) + { + var node = search.findNode("workspace://SpacesStore/" + nodeId); + + if (node != null) + { + try { - resultString = "Already tagged with '" + tagName + "'"; - tags = node.properties["cm:taggable"]; - if (tags == null) + var tags; + + if (action == "add") { - tags = new Array(); - } - // Check node doesn't already have this tag - var hasTag = false; - for each (tag in tags) - { - if (tag != null) + // Must have newTag node + if (newTag != null) { - if (tag.name == tagName) + resultString = "Already tagged with '" + tagName + "'"; + tags = node.properties["cm:taggable"]; + if (tags == null) { - hasTag = true; - break; + tags = new Array(); + } + // Check node doesn't already have this tag + var hasTag = false; + for each (tag in tags) + { + if (tag != null) + { + if (tag.name == tagName) + { + hasTag = true; + break; + } + } + } + if (!hasTag) + { + // Add it to our node + tags.push(newTag); + tagsArray = new Array(); + tagsArray["cm:taggable"] = tags; + node.addAspect("cm:taggable", tagsArray); + + resultString = "Document tagged"; + resultCode = true; } } } - if (!hasTag) + else if (action == "remove") { - // Make sure the tag is in the repo - newTag = createTag(tagName); - if (newTag != null) + resultString = "Could not remove tag"; + var oldTags = node.properties["cm:taggable"]; + if (oldTags == null) { - // Add it to our node - tags.push(newTag); - tagsArray = new Array(); - tagsArray["cm:taggable"] = tags; - node.addAspect("cm:taggable", tagsArray); + oldTags = new Array(); + } + tags = new Array(); + // Find this tag + for each (tag in oldTags) + { + if (tag != null) + { + if (tag.name != tagName) + { + tags.push(tag); + } + } + } + // Removed tag? + if (oldTags.length > tags.length) + { + if (tags.length == 0) + { + node.addAspect("cm:taggable"); + } + else + { + tagsArray = new Array(); + tagsArray["cm:taggable"] = tags; + node.addAspect("cm:taggable", tagsArray); + } - resultString = "Tag added"; + resultString = "Tag removed"; resultCode = true; } else { - resultString = "Tag '" + tagName + "' not indexed"; + resultString = "Not tagged with '" + tagName + "'"; } } - } - else if (action == "remove") - { - resultString = "Could not remove tag"; - var oldTags = node.properties["cm:taggable"]; - if (oldTags == null) - { - oldTags = new Array(); - } - tags = new Array(); - // Find this tag - for each (tag in oldTags) - { - if (tag != null) - { - if (tag.name != tagName) - { - tags.push(tag); - } - } - } - // Removed tag? - if (oldTags.length > tags.length) - { - if (tags.length == 0) - { - node.addAspect("cm:taggable"); - } - else - { - tagsArray = new Array(); - tagsArray["cm:taggable"] = tags; - node.addAspect("cm:taggable", tagsArray); - } - - resultString = "Tag removed"; - resultCode = true; - } else { - resultString = "Not tagged with '" + tagName + "'"; + resultString = "Unknown action"; } } - else + catch(e) { - resultString = "Unknown action"; + resultString = "Action failed due to exception [" + e.toString() + "]"; } } - catch(e) - { - resultString = "Action failed due to exception [" + e.toString() + "]"; - } } } var result = { "resultString": resultString, - "resultCode": resultCode + "resultCode": resultCode, + "newTag": newTagNodeRef }; return result; } diff --git a/config/alfresco/web-client-config-properties.xml b/config/alfresco/web-client-config-properties.xml index a6af152bf0..50bbecb0af 100644 --- a/config/alfresco/web-client-config-properties.xml +++ b/config/alfresco/web-client-config-properties.xml @@ -743,7 +743,7 @@ - + diff --git a/source/java/org/alfresco/web/bean/ajax/PickerBean.java b/source/java/org/alfresco/web/bean/ajax/PickerBean.java index 5a83eb30f8..a3468f69e3 100644 --- a/source/java/org/alfresco/web/bean/ajax/PickerBean.java +++ b/source/java/org/alfresco/web/bean/ajax/PickerBean.java @@ -208,6 +208,78 @@ public class PickerBean * * The 16x16 pixel folder icon path is output as the 'icon' property for each child folder. */ + @InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML) + public void getTagNodes() 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(ID_PARENT); + if (strParentRef == null || strParentRef.length() == 0) + { + childRefs = this.categoryService.getRootCategories( + Repository.getStoreRef(), + ContentModel.ASPECT_TAGGABLE); + } + 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(ID_PARENT); + out.startObject(); + if (parentRef == null) + { + out.writeNullValue(ID_ID); + out.writeValue(ID_NAME, "Tags"); + out.writeValue(ID_ISROOT, true); + out.writeValue(ID_SELECTABLE, false); + } + else + { + out.writeValue(ID_ID, strParentRef); + out.writeValue(ID_NAME, Repository.getNameForNode(this.internalNodeService, parentRef)); + } + out.endObject(); + out.endValue(); + out.startValue(ID_CHILDREN); + out.startArray(); + for (ChildAssociationRef ref : childRefs) + { + NodeRef nodeRef = ref.getChildRef(); + out.startObject(); + out.writeValue(ID_ID, nodeRef.toString()); + out.writeValue(ID_NAME, Repository.getNameForNode(this.internalNodeService, nodeRef)); + out.endObject(); + } + out.endArray(); + out.endValue(); + out.endObject(); + + tx.commit(); + } + catch (Throwable err) + { + Utils.addErrorMessage("PickerBean exception in getTagNodes()", 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 { diff --git a/source/java/org/alfresco/web/bean/generator/AjaxTagPickerGenerator.java b/source/java/org/alfresco/web/bean/generator/AjaxTagPickerGenerator.java new file mode 100644 index 0000000000..8c6ddb4bbe --- /dev/null +++ b/source/java/org/alfresco/web/bean/generator/AjaxTagPickerGenerator.java @@ -0,0 +1,107 @@ +/* + * 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.generator; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; + +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.web.app.servlet.FacesHelper; +import org.alfresco.web.ui.repo.RepoConstants; +import org.alfresco.web.ui.repo.component.UIMultiValueEditor; +import org.alfresco.web.ui.repo.component.property.PropertySheetItem; +import org.alfresco.web.ui.repo.component.property.UIPropertySheet; + +/** + * Generates a category selector component. + * + * @author Mike Hatfield + */ +public class AjaxTagPickerGenerator extends BaseComponentGenerator +{ + public UIComponent generate(FacesContext context, String id) + { + UIComponent component = context.getApplication(). + createComponent(RepoConstants.ALFRESCO_FACES_AJAX_TAG_PICKER); + FacesHelper.setupComponentId(context, component, id); + + return component; + } + + @Override + @SuppressWarnings("unchecked") + protected UIComponent createComponent(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item) + { + // the category selector component is used whatever mode the property sheet is in + return generate(context, item.getName()); + } + + @Override + protected void setupMandatoryValidation(FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem item, UIComponent component, boolean realTimeChecking, String idSuffix) + { + if (component instanceof UIMultiValueEditor) + { + // Override the setup of the mandatory validation + // so we can send the _current_value id suffix. + // We also enable real time so the page load + // check disables the ok button if necessary, as the user + // adds or removes items from the multi value list the + // page will be refreshed and therefore re-check the status. + + super.setupMandatoryValidation(context, propertySheet, item, + component, true, "_current_value"); + } + else + { + // Override the setup of the mandatory validation + // so we can send the _selected id suffix. + // We also enable real time so the page load check disabled + // the ok button if necessary, as the control is used the + // page will be refreshed and therefore re-check the status. + + super.setupMandatoryValidation(context, propertySheet, item, + component, true, "_selected"); + } + } + + @Override + protected ControlType getControlType() + { + return ControlType.SELECTOR; + } + + @Override + protected UIComponent setupMultiValuePropertyIfNecessary( + FacesContext context, UIPropertySheet propertySheet, + PropertySheetItem property, PropertyDefinition propertyDef, + UIComponent component) + { + return component; + } + +} + diff --git a/source/java/org/alfresco/web/ui/repo/RepoConstants.java b/source/java/org/alfresco/web/ui/repo/RepoConstants.java index a655f5ec9a..62ab021dc4 100644 --- a/source/java/org/alfresco/web/ui/repo/RepoConstants.java +++ b/source/java/org/alfresco/web/ui/repo/RepoConstants.java @@ -51,6 +51,7 @@ public final class RepoConstants public static final String ALFRESCO_FACES_BOOLEAN_CONVERTER = "org.alfresco.faces.BooleanLabelConverter"; public static final String ALFRESCO_FACES_MLTEXT_RENDERER = "org.alfresco.faces.MultilingualText"; public static final String ALFRESCO_FACES_MLTEXTAREA_RENDERER = "org.alfresco.faces.MultilingualTextArea"; + public static final String ALFRESCO_FACES_AJAX_TAG_PICKER = "org.alfresco.faces.AjaxTagPicker"; public static final String GENERATOR_LABEL = "LabelGenerator"; public static final String GENERATOR_TEXT_FIELD = "TextFieldGenerator"; diff --git a/source/java/org/alfresco/web/ui/repo/component/BaseAjaxItemPicker.java b/source/java/org/alfresco/web/ui/repo/component/BaseAjaxItemPicker.java index 3717e72ed1..40e280ddd6 100644 --- a/source/java/org/alfresco/web/ui/repo/component/BaseAjaxItemPicker.java +++ b/source/java/org/alfresco/web/ui/repo/component/BaseAjaxItemPicker.java @@ -61,15 +61,15 @@ import org.springframework.web.jsf.FacesContextUtils; */ 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"; + protected static final String MSG_GO_UP = "go_up"; + protected static final String MSG_OK = "ok"; + protected static final String MSG_CANCEL = "cancel"; - private static final String ID_ID = "id"; - private static final String ID_NAME = "name"; - private static final String ID_ICON = "icon"; + protected static final String ID_ID = "id"; + protected static final String ID_NAME = "name"; + protected static final String ID_ICON = "icon"; - private static final String FOLDER_IMAGE_PREFIX = "/images/icons/"; + protected static final String FOLDER_IMAGE_PREFIX = "/images/icons/"; /** label to be displayed before an item is selected */ protected String label = null; @@ -407,6 +407,10 @@ public abstract class BaseAjaxItemPicker extends UIInput { this.label = (String)vb.getValue(getFacesContext()); } + else + { + this.label = ""; + } return this.label; } @@ -420,7 +424,7 @@ public abstract class BaseAjaxItemPicker extends UIInput } /** - * @return Returns the initial selecttion. + * @return Returns the initial selection. */ public String getInitialSelection() { diff --git a/source/java/org/alfresco/web/ui/repo/component/UIAjaxTagPicker.java b/source/java/org/alfresco/web/ui/repo/component/UIAjaxTagPicker.java new file mode 100644 index 0000000000..2ddadf7bca --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/component/UIAjaxTagPicker.java @@ -0,0 +1,302 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ApplicationModel; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * @author Mike Hatfield + */ +public class UIAjaxTagPicker extends BaseAjaxItemPicker +{ + private static final String MSG_CLICK_TO_SELECT_TAG = "click_to_select_tag"; + + @Override + public String getFamily() + { + return "org.alfresco.faces.AjaxTagPicker"; + } + + @Override + protected String getServiceCall() + { + return "PickerBean.getTagNodes"; + } + + @Override + protected String getDefaultIcon() + { + return "/images/icons/category_small.gif"; + } + + @Override + public Boolean getSingleSelect() + { + // Tag component is never in single select mode, but the base class needs to know this + return false; + } + + @Override + public String getLabel() + { + // Tagger label only retrieved from a value binding when null + if (this.label == null) + { + super.getLabel(); + } + return this.label; + } + + @Override + /** + * @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(); + + String formClientId = Utils.getParentForm(fc, this).getClientId(fc); + Map attrs = this.getAttributes(); + ResourceBundle msg = Application.getBundle(fc); + + // get values from submitted value or none selected + String selectedValues = null; + String selectedNames = null; + String selectedItems = null; + List submitted = null; + + submitted = (List)getSubmittedValue(); + if (submitted == null) + { + submitted = (List)getValue(); + } + // special case to submit empty lists on multi-select values + else if (submitted.equals("empty")) + { + submitted = null; + } + + if (submitted != null) + { + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(fc, true); + tx.begin(); + + StringBuilder nameBuf = new StringBuilder(128); + StringBuilder valueBuf = new StringBuilder(128); + StringBuilder itemBuf = new StringBuilder(256); + NodeService nodeService = (NodeService)FacesContextUtils.getRequiredWebApplicationContext( + fc).getBean("nodeService"); + for (NodeRef value : submitted) + { + String name = (String)nodeService.getProperty(value, ContentModel.PROP_NAME); + String icon = (String)nodeService.getProperty(value, ApplicationModel.PROP_ICON); + if (nameBuf.length() != 0) + { + nameBuf.append(", "); + valueBuf.append(","); + itemBuf.append(","); + } + nameBuf.append(name); + valueBuf.append(value.toString()); + itemBuf.append(getItemJson(value.toString(), name, icon)); + } + selectedNames = nameBuf.toString(); + selectedValues = valueBuf.toString(); + selectedItems = "[" + itemBuf.toString() + "]"; + + // commit the transaction + tx.commit(); + } + catch (Throwable err) + { + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + } + + // generate the Ids for our script object and containing DIV element + String divId = getId(); + String objId = divId + "Obj"; + + // generate the script to create and init our script object + String contextPath = fc.getExternalContext().getRequestContextPath(); + out.write(""); + + // generate the DIV structure for our component as expected by the script object + out.write("
") ; + out.write(" "); + // current selection displayed as link and message to launch the selector + out.write("
"); + if (isDisabled()) + { + out.write(" "); + if (selectedNames != null) + { + out.write(selectedNames); + } + out.write(" "); + } + else + { + out.write(" "); + if (selectedNames == null) + { + if (getLabel() == "") + { + setLabel(msg.getString(MSG_CLICK_TO_SELECT_TAG)); + } + out.write(getLabel()); + } + else + { + out.write(selectedNames); + } + 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(" "); + out.write(" Add a tag"); + out.write(" "); + out.write(" "); + out.write(" "); + out.write(" Add"); + out.write(" Cancel"); + out.write(" "); + out.write(" "); + out.write(" "); + out.write("
"); + out.write("
"); + // container for item selection + out.write("
"); + out.write("
"); + out.write("
"); + out.write("
"); + out.write("
"); + // controls (OK & Cancel buttons etc.) + out.write(" "); + out.write("
"); + // container for the selected items + out.write("
"); + out.write("
"); + } + +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/AjaxTagSelectorTag.java b/source/java/org/alfresco/web/ui/repo/tag/AjaxTagSelectorTag.java new file mode 100644 index 0000000000..9a9fb1dbe4 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/tag/AjaxTagSelectorTag.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 Mike Hatfield + */ +public class AjaxTagSelectorTag extends AjaxItemSelectorTag +{ + /** + * @see javax.faces.webapp.UIComponentTag#getComponentType() + */ + public String getComponentType() + { + return "org.alfresco.faces.AjaxTagPicker"; + } +} 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 d9b284d572..4d09627a5d 100644 --- a/source/java/org/alfresco/web/ui/repo/tag/PageTag.java +++ b/source/java/org/alfresco/web/ui/repo/tag/PageTag.java @@ -70,7 +70,8 @@ public class PageTag extends TagSupport // pop-up panel helper objects "/scripts/ajax/summary-info.js", // ajax pickers - "/scripts/ajax/picker.js" + "/scripts/ajax/picker.js", + "/scripts/ajax/tagger.js" }; private final static String[] CSS = diff --git a/source/web/WEB-INF/faces-config-beans.xml b/source/web/WEB-INF/faces-config-beans.xml index 3fd276035a..3b1a02e326 100644 --- a/source/web/WEB-INF/faces-config-beans.xml +++ b/source/web/WEB-INF/faces-config-beans.xml @@ -4327,6 +4327,15 @@ request + + + Bean that generates an ajax tag picker component + + AjaxTagPickerGenerator + org.alfresco.web.bean.generator.AjaxTagPickerGenerator + request + + Bean that generates an association component diff --git a/source/web/WEB-INF/faces-config-repo.xml b/source/web/WEB-INF/faces-config-repo.xml index 1bb9b4649c..b81741b284 100644 --- a/source/web/WEB-INF/faces-config-repo.xml +++ b/source/web/WEB-INF/faces-config-repo.xml @@ -224,6 +224,11 @@ org.alfresco.web.ui.repo.component.UIAjaxFilePicker + + org.alfresco.faces.AjaxTagPicker + org.alfresco.web.ui.repo.component.UIAjaxTagPicker + + diff --git a/source/web/WEB-INF/repo.tld b/source/web/WEB-INF/repo.tld index b0e6e4e3d8..23a4da97ef 100644 --- a/source/web/WEB-INF/repo.tld +++ b/source/web/WEB-INF/repo.tld @@ -2462,4 +2462,76 @@ + + ajaxTagSelector + org.alfresco.web.ui.repo.tag.AjaxTagSelectorTag + JSP + + + id + false + true + + + + binding + false + true + + + + rendered + false + true + + + + style + false + true + + + + styleClass + false + true + + + + value + false + true + + + + label + true + true + + + + initialSelection + false + true + + + + singleSelect + false + true + + + + disabled + false + true + + + + height + false + true + + + diff --git a/source/web/css/picker.css b/source/web/css/picker.css index 1fe583fc99..8d4c1ca7b0 100644 --- a/source/web/css/picker.css +++ b/source/web/css/picker.css @@ -99,6 +99,11 @@ div.pickerAjaxWait border-bottom: 1px solid #b2b2b2; } +.pickerNavControls a img, .pickerNavControls input +{ + outline: none; +} + img.pickerActionButton { margin-left: 2px; @@ -106,6 +111,7 @@ img.pickerActionButton div.pickerResultsRow { + clear: left; padding: 4px; height: auto; } @@ -177,6 +183,13 @@ div.pickerNavBreadcrumbPanel display: none; } +.pickerNavBreadcrumbText +{ + float: left; + height: 16px; + padding: 3px 0px; +} + div.pickerNavBreadcrumbItem { padding: 2px; @@ -193,4 +206,47 @@ div.pickerNavBreadcrumbItem .pickerNavUp img { vertical-align: -4px; -} \ No newline at end of file +} + +.pickerNavAddTag +{ + float: right; +} + +.pickerAddTagIcon +{ + background: url("../images/office/new_tag.gif") no-repeat; + float: left; + height: 16px; + width: 16px; + margin: 2px 4px; +} + +.pickerAddTagLinkContainer +{ + height: 16px; + margin: 3px 0px; +} + +.pickerAddTagFormContainer +{ + display: none; +} + +.pickerAddTagBox +{ + border: 1px solid #cccccc; + height: 16px; + margin: 0px; + padding: 1px; + color: #0073e6; + font-family: tahoma, sans-serif; + font-size: 12px; +} + +.pickerAddTagImage +{ + cursor: pointer; + margin: 0px 0px -2px; + outline: none; +} diff --git a/source/web/scripts/ajax/tagger.js b/source/web/scripts/ajax/tagger.js new file mode 100644 index 0000000000..b1c7272c15 --- /dev/null +++ b/source/web/scripts/ajax/tagger.js @@ -0,0 +1,591 @@ +/* + * 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 AlfTagger = new Class( +{ + /* id of the tagger */ + id: null, + + /* variable name being used */ + varName: null, + + /* form Id to submit when selection complete */ + formClientId: null, + + /* the item the tagger will start with */ + startId: null, + + /* list of items currently selected */ + selected: null, + + /* list of items pre-selected */ + preselected: 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, + + /* initial display style of the outer div */ + initialDisplayStyle: null, + + /* allow child selection navigation */ + allowChildNavigation: true, + + initialize: function(id, varName, service, formClientId) + { + this.id = id; + this.varName = varName; + this.service = service; + this.formClientId = formClientId; + this.selected = []; + this.preselected = []; + + // Add click event handlers for the add and cancel buttons + $(this.id + "-addTag-ok").addEvent("click", function() + { + this.addTag($(this.id + "-addTag-box").value); + }.bind(this)); + + $(this.id + "-addTag-cancel").addEvent("click", function() + { + this.hideAddTagForm(); + }.bind(this)); + + // Capture Enter and Esc key events from the input box + $(this.id + "-addTag-box").addEvent("keydown", function(e) + { + var e = new Event(e); + if (e.key == "enter") + { + $(this.id + "-addTag-ok").fireEvent("click"); + e.stop(); + } + else if (e.key == "esc") + { + $(this.id + "-addTag-cancel").fireEvent("click"); + e.stop(); + } + }.bind(this)); + + // Cancel propagation of Enter keyup and keypress events to stop the JSF form submission + var fnEnterStop = function(e) + { + var e = new Event(e); + if (e.key == "enter") + { + e.stop(); + } + } + + $(this.id + "-addTag-box").addEvents( + { + "keyup": fnEnterStop, + "keypress": fnEnterStop + }); + }, + + setDefaultIcon: function(icon) + { + this.defaultIcon = icon; + }, + + setStartId: function(id) + { + this.startId = id; + }, + + setChildNavigation: function(allowed) + { + this.allowChildNavigation = allowed; + }, + + setSelectedItems: function(jsonString) + { + this.preselected = Json.evaluate(jsonString); + }, + + showSelector: function() + { + // init selector state + this.selected = []; + this.stack = []; + + this.initialDisplayStyle = $(this.id + "-noitems").getStyle("display"); + $(this.id + "-selector").setStyle("display", "block"); + $(this.id + "-selected").empty(); + $(this.id + "-selected").setStyle("display", "block"); + $(this.id + "-noitems").setStyle("display", "none"); + + this.preselected.each(function(item, i) + { + this.addSelectedItem(item); + }, this); + + this.hideAddTagForm(); + + // first ajax request for the children of the 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; + if (index != -1) + { + item = this.items[index]; + } + else + { + item = this.parent; + } + + this.addSelectedItem(item); + // hide the Add button as this item is now added + $(this.id + "-add-" + item.id).setStyle("display", "none"); + }, + + addSelectedItem: function(item) + { + // check item isn't already in the selected items list + for (var i = 0; i < this.selected.length; i++) + { + if (this.selected[i].id == item.id) + { + return; + } + } + + // add item to list of selected items + this.selected.push(item); + + // add the item to list outside 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() + 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']}); + }, + + delItem: function(itemId) + { + // remove item from the selected items list + for (i=0; i b.name) ? 1 : 0)); + }, + + // when in tag mode - show the ad-hoc add form + showAddTagForm: function() + { + $(this.id + "-addTag-linkContainer").setStyle("display", "none"); + $(this.id + "-addTag-formContainer").setStyle("display", "block"); + $(this.id + "-addTag-box").focus(); + }, + + // hide the new tag form + hideAddTagForm: function(clearInput) + { + $(this.id + "-addTag-formContainer").setStyle("display", "none"); + $(this.id + "-addTag-linkContainer").setStyle("display", "block"); + if (clearInput) + { + $(this.id + "-addTag-box").value = ""; + } + return false; + }, + + addTag: function(tagName) + { + // Create a new item + var item = + { + id: "", + name: tagName, + icon: this.defaultIcon + } + + // Inline function to enable easy binding to "this" + var fnAfterAjax = function(item) + { + this.addSelectedItem(item); + this.hideAddTagForm(true); + this.getChildData(this.parent.id, this.populateChildren); + }.bind(this); + + // execute ajax service call to retrieve list of child nodes as JSON response + new Ajax(getContextPath() + "/wcs/collaboration/tagActions?a=add&t=" + tagName, + { + method: 'post', + headers: {'If-Modified-Since': 'Sat, 1 Jan 2000 00:00:00 GMT'}, + async: false, + onComplete: function(r) + { + // Pull out the nodeRef of the tag + result = Json.evaluate(r); + if (result.statusCode == true) + { + item.id = result.newTag; + fnAfterAjax(item); + } + }, + onFailure: function (r) + { + } + }).request(); + } +}); \ No newline at end of file