From cb80c7501487232f9a4e44df155a1b43c1d0900a Mon Sep 17 00:00:00 2001 From: Ariel Backenroth Date: Tue, 12 Dec 2006 03:13:23 +0000 Subject: [PATCH] first pass at add content from xforms ui - added an add content panel to the file picker which enables browsing the local disk to select content to upload. the file is uploaded without requiring a browser refresh by targeting a hidden iframe. the server response includes a js call which notifies the form that the upload is complete. - added annotations to ajax methods to enable specifying the response mime type of the method. needed for uploadFile which returns html rather than xml - minor cleanup in schemaformbuilder - added mapping between xsl-fo mimetypes and those in mimetype map git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4576 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../web-client-application-context.xml | 6 - .../web/app/servlet/ajax/InvokeCommand.java | 140 ++++++--- .../alfresco/web/bean/wcm/AVMEditBean.java | 5 +- .../web/bean/wcm/CreateWebContentWizard.java | 39 ++- .../org/alfresco/web/forms/FormProcessor.java | 8 +- .../org/alfresco/web/forms/FormsService.java | 9 - .../alfresco/web/forms/RenderingEngine.java | 5 + .../web/forms/XSLFORenderingEngine.java | 41 ++- .../web/forms/xforms/SchemaFormBuilder.java | 8 +- .../alfresco/web/forms/xforms/XFormsBean.java | 170 ++++++++-- .../simple-test/components-test.xsd | 8 +- source/web/scripts/ajax/xforms.js | 296 +++++++++++++++--- 12 files changed, 559 insertions(+), 176 deletions(-) diff --git a/config/alfresco/web-client-application-context.xml b/config/alfresco/web-client-application-context.xml index 1a3c05c9b8..30cf4245b2 100644 --- a/config/alfresco/web-client-application-context.xml +++ b/config/alfresco/web-client-application-context.xml @@ -42,12 +42,6 @@ - - - - - - diff --git a/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java b/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java index b5da969d0a..ea3f979a2b 100644 --- a/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java +++ b/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java @@ -16,6 +16,9 @@ */ package org.alfresco.web.app.servlet.ajax; +import java.lang.annotation.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -25,7 +28,7 @@ import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.el.EvaluationException; -import javax.faces.el.MethodBinding; +import javax.faces.el.VariableResolver; import javax.faces.render.RenderKit; import javax.faces.render.RenderKitFactory; import javax.servlet.ServletException; @@ -51,59 +54,68 @@ import org.alfresco.web.bean.repository.Repository; */ public class InvokeCommand extends BaseAjaxCommand { - public void execute(final FacesContext facesContext, - final String expression, - final HttpServletRequest request, - final HttpServletResponse response) - throws ServletException, IOException - { - // setup the JSF response writer. - - // NOTE: it doesn't seem to matter what the content type of the response is (at least with Dojo), - // it determines it's behaviour from the mimetype specified in the AJAX call on the client, - // therefore, for now we will always return a content type of text/xml. - // In the future we may use annotations on the method to be called to specify what content - // type should be used for the response. - // NOTE: JSF only seems to support XML and HTML content types by default so this will - // also need to be addressed if other content types need to be returned i.e. JSON. - - OutputStream os = response.getOutputStream(); - UIViewRoot viewRoot = facesContext.getViewRoot(); - RenderKitFactory renderFactory = (RenderKitFactory)FactoryFinder. - getFactory(FactoryFinder.RENDER_KIT_FACTORY); - RenderKit renderKit = renderFactory.getRenderKit(facesContext, - viewRoot.getRenderKitId()); - ResponseWriter writer = renderKit.createResponseWriter( - new OutputStreamWriter(os), MimetypeMap.MIMETYPE_XML, "UTF-8"); - facesContext.setResponseWriter(writer); - // must be text/xml otherwise IE doesn't parse the response properly into responseXML - response.setContentType(MimetypeMap.MIMETYPE_XML); - // create the JSF binding expression - String bindingExpr = makeBindingExpression(expression); - - if (logger.isDebugEnabled()) - logger.debug("Invoking method represented by " + bindingExpr); - + ///////////////////////////////////////////////////////////////////////////// + + /** + * Annotation for a bean method that handles an ajax request. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface ResponseMimetype + { + public String value() default MimetypeMap.MIMETYPE_XML; + } + + ///////////////////////////////////////////////////////////////////////////// + + public void execute(final FacesContext facesContext, + final String expression, + final HttpServletRequest request, + final HttpServletResponse response) + throws ServletException, IOException + { + + UserTransaction tx = null; + ResponseWriter writer = null; try { - // create the method binding from the expression - MethodBinding binding = facesContext.getApplication().createMethodBinding( - bindingExpr, new Class[] {}); + final VariableResolver vr = facesContext.getApplication().getVariableResolver(); + + final int indexOfDot = expression.indexOf('.'); + final String variableName = expression.substring(0, indexOfDot); + final String methodName = expression.substring(indexOfDot + 1); + + if (logger.isDebugEnabled()) + logger.debug("Invoking method represented by " + expression + + " on variable " + variableName + + " with method " + methodName); + + final Object bean = vr.resolveVariable(facesContext, variableName); + final Method method = bean.getClass().getMethod(methodName); + + final String responseMimetype = + (method.isAnnotationPresent(ResponseMimetype.class) + ? method.getAnnotation(ResponseMimetype.class).value() + : MimetypeMap.MIMETYPE_XML); + + if (logger.isDebugEnabled()) + logger.debug("invoking method " + method + + " with repsonse mimetype " + responseMimetype); + writer = this.setupResponseWriter(responseMimetype, + response, + facesContext); + + // setup the transaction + tx = Repository.getUserTransaction(facesContext); + tx.begin(); - if (binding != null) - { - // setup the transaction - tx = Repository.getUserTransaction(facesContext); - tx.begin(); - - // invoke the method - binding.invoke(facesContext, new Object[] {}); - - // commit - tx.commit(); - } + // invoke the method + method.invoke(bean); + + // commit + tx.commit(); } catch (Throwable err) { @@ -118,6 +130,14 @@ public class InvokeCommand extends BaseAjaxCommand err = cause; } } + else if (err instanceof InvocationTargetException) + { + final Throwable cause = ((InvocationTargetException)err).getCause(); + if (cause != null) + { + err = cause; + } + } logger.error(err); throw new AlfrescoRuntimeException("Failed to execute method " + expression + @@ -127,4 +147,26 @@ public class InvokeCommand extends BaseAjaxCommand // force the output back to the client writer.close(); } + + /** setup the JSF response writer. */ + private ResponseWriter setupResponseWriter(final String mimetype, + final HttpServletResponse response, + final FacesContext facesContext) + throws IOException + { + final OutputStream os = response.getOutputStream(); + final UIViewRoot viewRoot = facesContext.getViewRoot(); + final RenderKitFactory renderFactory = (RenderKitFactory) + FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY); + final RenderKit renderKit = + renderFactory.getRenderKit(facesContext, viewRoot.getRenderKitId()); + final ResponseWriter writer = + renderKit.createResponseWriter(new OutputStreamWriter(os), + mimetype, + "UTF-8"); + facesContext.setResponseWriter(writer); + // must be text/xml otherwise IE doesn't parse the response properly into responseXML + response.setContentType(mimetype); + return writer; + } } diff --git a/source/java/org/alfresco/web/bean/wcm/AVMEditBean.java b/source/java/org/alfresco/web/bean/wcm/AVMEditBean.java index 719f77fec8..b4bed422a0 100644 --- a/source/java/org/alfresco/web/bean/wcm/AVMEditBean.java +++ b/source/java/org/alfresco/web/bean/wcm/AVMEditBean.java @@ -233,7 +233,7 @@ public class AVMEditBean { private final FormsService ts = FormsService.getInstance(); - public Document getContent() + public Document load() { try { @@ -247,7 +247,8 @@ public class AVMEditBean } } - public void setContent(final Document d) + public void save(final Document d, + final String[] uploadedFilePaths) { AVMEditBean.this.setEditorOutput(this.ts.writeXMLToString(d)); } diff --git a/source/java/org/alfresco/web/bean/wcm/CreateWebContentWizard.java b/source/java/org/alfresco/web/bean/wcm/CreateWebContentWizard.java index c9a78adc85..190f46962e 100644 --- a/source/java/org/alfresco/web/bean/wcm/CreateWebContentWizard.java +++ b/source/java/org/alfresco/web/bean/wcm/CreateWebContentWizard.java @@ -84,6 +84,7 @@ public class CreateWebContentWizard extends BaseContentWizard protected FormInstanceData formInstanceData = null; protected boolean formSelectDisabled = false; protected boolean startWorkflow = false; + protected String[] uploadedFilePaths = null; /** AVM service bean reference */ protected AVMService avmService; @@ -144,6 +145,7 @@ public class CreateWebContentWizard extends BaseContentWizard this.formName = null; this.mimeType = MimetypeMap.MIMETYPE_XML; this.formInstanceData = null; + this.uploadedFilePaths = null; this.renditions = null; this.startWorkflow = false; @@ -161,6 +163,14 @@ public class CreateWebContentWizard extends BaseContentWizard this.formSelectDisabled = true; } } + + // reset the preview layer + String path = this.avmBrowseBean.getCurrentPath(); + path = path.replaceFirst(AVMConstants.STORE_MAIN, AVMConstants.STORE_PREVIEW); + path = path.split(":")[0] + ":/" + AVMConstants.DIR_APPBASE; + if (LOGGER.isDebugEnabled()) + LOGGER.debug("reseting layer " + path); + this.avmSyncService.resetLayer(path); } @Override @@ -190,6 +200,7 @@ public class CreateWebContentWizard extends BaseContentWizard { LOGGER.debug("clearing form instance data"); this.formInstanceData = null; + this.uploadedFilePaths = null; this.renditions = null; } return super.back(); @@ -231,6 +242,23 @@ public class CreateWebContentWizard extends BaseContentWizard AVMConstants.STORE_MAIN), AVMDifference.NEWER)); } + + for (String path : this.uploadedFilePaths) + { + diffList.add(new AVMDifference(-1, path, + -1, path.replaceFirst(AVMConstants.STORE_PREVIEW, + AVMConstants.STORE_MAIN), + AVMDifference.NEWER)); + } + + if (LOGGER.isDebugEnabled()) + { + for (AVMDifference diff : diffList) + { + LOGGER.debug("updating " + AVMConstants.STORE_MAIN + + " with " + diff.getSourcePath()); + } + } this.avmSyncService.update(diffList, null, true, true, true, true, null, null); // reset all paths and structures to the main store @@ -411,11 +439,6 @@ public class CreateWebContentWizard extends BaseContentWizard fileName = sb[1]; } - if (LOGGER.isDebugEnabled()) - LOGGER.debug("reseting layer " + path.split(":")[0] + ":/" + AVMConstants.DIR_APPBASE); - - this.avmSyncService.resetLayer(path.split(":")[0] + ":/" + AVMConstants.DIR_APPBASE); - if (LOGGER.isDebugEnabled()) LOGGER.debug("creating all directories in path " + path); @@ -577,7 +600,7 @@ public class CreateWebContentWizard extends BaseContentWizard { private final FormsService ts = FormsService.getInstance(); - public Document getContent() + public Document load() { try { @@ -591,9 +614,11 @@ public class CreateWebContentWizard extends BaseContentWizard } } - public void setContent(final Document d) + public void save(final Document d, + final String[] uploadedFilePaths) { CreateWebContentWizard.this.setContent(ts.writeXMLToString(d)); + CreateWebContentWizard.this.uploadedFilePaths = uploadedFilePaths; } }; } diff --git a/source/java/org/alfresco/web/forms/FormProcessor.java b/source/java/org/alfresco/web/forms/FormProcessor.java index 25861d2c14..aa92b49a07 100644 --- a/source/java/org/alfresco/web/forms/FormProcessor.java +++ b/source/java/org/alfresco/web/forms/FormProcessor.java @@ -35,9 +35,11 @@ public interface FormProcessor */ public interface InstanceData { - public Document getContent(); - - public void setContent(final Document d); + + public Document load(); + + public void save(final Document d, + final String[] uploadedFilePaths); } ///////////////////////////////////////////////////////////////////////////// diff --git a/source/java/org/alfresco/web/forms/FormsService.java b/source/java/org/alfresco/web/forms/FormsService.java index f16dcd5d18..e39d8b755d 100644 --- a/source/java/org/alfresco/web/forms/FormsService.java +++ b/source/java/org/alfresco/web/forms/FormsService.java @@ -89,9 +89,6 @@ public final class FormsService private static FormsService INSTANCE; private static DocumentBuilder documentBuilder; - /** internal storage of forms, keyed by the form name */ - private HashMap forms = new HashMap(); - private static final RenderingEngine[] RENDERING_ENGINES = new RenderingEngine[] { new FreeMarkerRenderingEngine(), @@ -101,8 +98,6 @@ public final class FormsService private final ContentService contentService; private final NodeService nodeService; - private final FileFolderService fileFolderService; - private final DictionaryService dictionaryService; private final NamespaceService namespaceService; private final SearchService searchService; private final AVMService avmService; @@ -112,16 +107,12 @@ public final class FormsService /** instantiated using spring */ public FormsService(final ContentService contentService, final NodeService nodeService, - final FileFolderService fileFolderService, - final DictionaryService dictionaryService, final NamespaceService namespaceService, final SearchService searchService, final AVMService avmService) { this.contentService = contentService; this.nodeService = nodeService; - this.fileFolderService = fileFolderService; - this.dictionaryService = dictionaryService; this.namespaceService = namespaceService; this.searchService = searchService; this.avmService = avmService; diff --git a/source/java/org/alfresco/web/forms/RenderingEngine.java b/source/java/org/alfresco/web/forms/RenderingEngine.java index 922fa1d901..9797ca8524 100644 --- a/source/java/org/alfresco/web/forms/RenderingEngine.java +++ b/source/java/org/alfresco/web/forms/RenderingEngine.java @@ -37,6 +37,11 @@ public interface RenderingEngine extends Exception { + public RenderingException(final String msg) + { + super(msg); + } + public RenderingException(final Exception cause) { super(cause); diff --git a/source/java/org/alfresco/web/forms/XSLFORenderingEngine.java b/source/java/org/alfresco/web/forms/XSLFORenderingEngine.java index ff19168eeb..05e4738657 100644 --- a/source/java/org/alfresco/web/forms/XSLFORenderingEngine.java +++ b/source/java/org/alfresco/web/forms/XSLFORenderingEngine.java @@ -22,9 +22,6 @@ import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXResult; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.*; @@ -33,9 +30,7 @@ import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.Fop; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FopFactory; -import org.apache.fop.apps.FormattingResults; import org.apache.fop.apps.MimeConstants; -import org.apache.fop.apps.PageSequenceResults; /** * A rendering engine which uses xsl-fo templates to generate renditions of @@ -49,6 +44,33 @@ public class XSLFORenderingEngine private static final Log LOGGER = LogFactory.getLog(XSLFORenderingEngine.class); + private static final Map MIME_TYPES = + new HashMap(); + static + { + MIME_TYPES.put(MimeConstants.MIME_PDF, MimeConstants.MIME_PDF); + + MIME_TYPES.put(MimeConstants.MIME_POSTSCRIPT, MimeConstants.MIME_POSTSCRIPT); + MIME_TYPES.put(MimeConstants.MIME_EPS, MimeConstants.MIME_POSTSCRIPT); + + MIME_TYPES.put(MimeConstants.MIME_PLAIN_TEXT, MimeConstants.MIME_PLAIN_TEXT); + + MIME_TYPES.put(MimeConstants.MIME_RTF, MimeConstants.MIME_RTF); + MIME_TYPES.put(MimeConstants.MIME_RTF_ALT1, MimeConstants.MIME_RTF); + MIME_TYPES.put(MimeConstants.MIME_RTF_ALT2, MimeConstants.MIME_RTF); + + MIME_TYPES.put(MimeConstants.MIME_MIF, MimeConstants.MIME_MIF); + MIME_TYPES.put("application/x-mif", MimeConstants.MIME_MIF); + + MIME_TYPES.put(MimeConstants.MIME_SVG, MimeConstants.MIME_SVG); + MIME_TYPES.put("image/svg", MimeConstants.MIME_SVG); + + MIME_TYPES.put(MimeConstants.MIME_GIF, MimeConstants.MIME_GIF); + MIME_TYPES.put(MimeConstants.MIME_PNG, MimeConstants.MIME_PNG); + MIME_TYPES.put(MimeConstants.MIME_JPEG, MimeConstants.MIME_JPEG); + MIME_TYPES.put(MimeConstants.MIME_TIFF, MimeConstants.MIME_TIFF); + }; + public XSLFORenderingEngine() { super(); @@ -72,16 +94,21 @@ public class XSLFORenderingEngine RenderingEngine.RenderingException { Result result = null; + String mimetype = MIME_TYPES.get(ret.getMimetypeForRendition()); + if (mimetype == null) + { + throw new RenderingEngine.RenderingException("mimetype " + ret.getMimetypeForRendition() + + " is not supported by " + this.getName()); + } try { final FopFactory fopFactory = FopFactory.newInstance(); final FOUserAgent foUserAgent = fopFactory.newFOUserAgent(); - final Fop fop = fopFactory.newFop(ret.getMimetypeForRendition(), + final Fop fop = fopFactory.newFop(mimetype, foUserAgent, out); // Resulting SAX events (the generated FO) must be piped through to FOP result = new SAXResult(fop.getDefaultHandler()); - } catch (FOPException fope) { diff --git a/source/java/org/alfresco/web/forms/xforms/SchemaFormBuilder.java b/source/java/org/alfresco/web/forms/xforms/SchemaFormBuilder.java index 56dcde1a50..da0b8820ed 100644 --- a/source/java/org/alfresco/web/forms/xforms/SchemaFormBuilder.java +++ b/source/java/org/alfresco/web/forms/xforms/SchemaFormBuilder.java @@ -390,7 +390,6 @@ public class SchemaFormBuilder formContentWrapper, schema, rootElementDecl, - rootElementDecl.getTypeDefinition(), "/" + getElementName(rootElementDecl, xForm)); if (importedInstanceDocumentElement != null) { @@ -1058,9 +1057,9 @@ public class SchemaFormBuilder final Element formSection, final XSModel schema, final XSElementDeclaration elementDecl, - XSTypeDefinition controlType, - final String pathToRoot) { - + final String pathToRoot) + { + XSTypeDefinition controlType = elementDecl.getTypeDefinition(); if (controlType == null) { // TODO!!! Figure out why this happens... for now just warn... @@ -1664,7 +1663,6 @@ public class SchemaFormBuilder repeatContentWrapper, schema, element, - element.getTypeDefinition(), path); final SchemaUtil.Occurance elementOccurs = SchemaUtil.getOccurance(element); diff --git a/source/java/org/alfresco/web/forms/xforms/XFormsBean.java b/source/java/org/alfresco/web/forms/xforms/XFormsBean.java index ca94279ab3..a56a62c04e 100644 --- a/source/java/org/alfresco/web/forms/xforms/XFormsBean.java +++ b/source/java/org/alfresco/web/forms/xforms/XFormsBean.java @@ -16,50 +16,51 @@ */ package org.alfresco.web.forms.xforms; -import java.io.BufferedReader; -import java.io.IOException; -import java.util.Collection; -import java.util.Map; -import java.util.HashMap; -import java.util.LinkedList; - +import java.io.*; +import java.util.*; +import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; -import javax.faces.context.ExternalContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMService; -import org.alfresco.web.bean.wcm.AVMConstants; -import org.alfresco.web.forms.*; -import org.w3c.dom.Document; -import org.w3c.dom.Node; +import org.alfresco.util.TempFileProvider; +import org.alfresco.web.app.Application; import org.alfresco.web.app.servlet.FacesHelper; +import org.alfresco.web.app.servlet.ajax.InvokeCommand; +import org.alfresco.web.bean.FileUploadBean; import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.wcm.AVMBrowseBean; +import org.alfresco.web.bean.wcm.AVMConstants; +import org.alfresco.web.forms.*; import org.alfresco.web.ui.common.Utils; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.fileupload.servlet.ServletRequestContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.chiba.xml.xforms.ChibaBean; import org.chiba.xml.xforms.Instance; import org.chiba.xml.xforms.XFormsElement; import org.chiba.xml.xforms.connector.http.AbstractHTTPConnector; import org.chiba.xml.xforms.core.ModelItem; -import org.chiba.xml.xforms.exception.XFormsException; import org.chiba.xml.xforms.events.XFormsEvent; import org.chiba.xml.xforms.events.XFormsEventFactory; +import org.chiba.xml.xforms.exception.XFormsException; import org.chiba.xml.xforms.ui.BoundElement; import org.chiba.xml.xforms.ui.Upload; - +import org.springframework.util.FileCopyUtils; import org.w3c.dom.*; import org.w3c.dom.bootstrap.DOMImplementationRegistry; -import org.w3c.dom.ls.*; import org.w3c.dom.events.Event; import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventTarget; +import org.w3c.dom.ls.*; import org.xml.sax.SAXException; /** @@ -72,9 +73,10 @@ public class XFormsBean private Form form; private FormProcessor.InstanceData instanceData = null; - private ChibaBean chibaBean; + private final ChibaBean chibaBean = new ChibaBean(); private SchemaFormBuilder schemaFormBuilder = null; - private final LinkedList eventLog = new LinkedList(); + private final HashMap uploads = new HashMap(); + private final List eventLog = new LinkedList(); /** @return the form */ public Form getForm() @@ -105,7 +107,6 @@ public class XFormsBean { LOGGER.debug("initializing " + this + " with form " + this.form.getName()); } - this.chibaBean = new ChibaBean(); final FacesContext facesContext = FacesContext.getCurrentInstance(); final ExternalContext externalContext = facesContext.getExternalContext(); final HttpServletRequest request = (HttpServletRequest) @@ -139,7 +140,7 @@ public class XFormsBean final Document schemaDocument = this.form.getSchema(); this.rewriteInlineURIs(schemaDocument, cwdAVMPath); final Document xformsDocument = - this.schemaFormBuilder.buildXForm(instanceData.getContent(), + this.schemaFormBuilder.buildXForm(instanceData.load(), schemaDocument, this.form.getSchemaRootElementName()); @@ -290,20 +291,29 @@ public class XFormsBean * handles submits and sets the instance data. */ public void handleAction() - throws Exception { LOGGER.debug(this + ".handleAction"); - final FacesContext context = FacesContext.getCurrentInstance(); - final HttpServletRequest request = (HttpServletRequest) - context.getExternalContext().getRequest(); - final FormsService formsService = FormsService.getInstance(); - final Document result = formsService.parseXML(request.getInputStream()); - this.schemaFormBuilder.removePrototypeNodes(result.getDocumentElement()); - this.instanceData.setContent(result); + try + { + final FacesContext context = FacesContext.getCurrentInstance(); + final HttpServletRequest request = (HttpServletRequest) + context.getExternalContext().getRequest(); + final FormsService formsService = FormsService.getInstance(); + final Document result = formsService.parseXML(request.getInputStream()); + this.schemaFormBuilder.removePrototypeNodes(result.getDocumentElement()); - final ResponseWriter out = context.getResponseWriter(); - formsService.writeXML(result, out); - out.close(); + final String[] uploadedFilePaths = (String[]) + this.uploads.values().toArray(new String[0]); + this.instanceData.save(result, uploadedFilePaths); + + final ResponseWriter out = context.getResponseWriter(); + formsService.writeXML(result, out); + out.close(); + } + catch (Throwable t) + { + LOGGER.error(t); + } } /** @@ -329,6 +339,7 @@ public class XFormsBean /** * Provides data for a file picker widget. */ + @InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_XML) public void getFilePickerData() throws Exception { @@ -347,7 +358,10 @@ public class XFormsBean } else { - currentPath = AVMConstants.buildAbsoluteAVMPath(browseBean.getCurrentPath(), + final String previewStorePath = + browseBean.getCurrentPath().replaceFirst(AVMConstants.STORE_MAIN, + AVMConstants.STORE_PREVIEW); + currentPath = AVMConstants.buildAbsoluteAVMPath(previewStorePath, currentPath); } LOGGER.debug(this + ".getFilePickerData(" + currentPath + ")"); @@ -403,6 +417,94 @@ public class XFormsBean FormsService.getInstance().writeXML(result, out); out.close(); } + + @InvokeCommand.ResponseMimetype(value=MimetypeMap.MIMETYPE_HTML) + public void uploadFile() + throws Exception + { + LOGGER.debug(this + ".uploadFile()"); + final FacesContext facesContext = FacesContext.getCurrentInstance(); + final ExternalContext externalContext = facesContext.getExternalContext(); + final HttpServletRequest request = (HttpServletRequest) + externalContext.getRequest(); + final HttpSession session = (HttpSession) + externalContext.getSession(true); + final AVMBrowseBean browseBean = (AVMBrowseBean) + session.getAttribute("AVMBrowseBean"); + + final ServletFileUpload upload = + new ServletFileUpload(new DiskFileItemFactory()); + upload.setHeaderEncoding("UTF-8"); + final List fileItems = upload.parseRequest(request); + final FileUploadBean bean = new FileUploadBean(); + String uploadId = null; + String currentPath = null; + String filename = null; + InputStream fileInputStream = null; + for (FileItem item : fileItems) + { + LOGGER.debug("item = " + item); + if (item.isFormField() && item.getFieldName().equals("id")) + { + uploadId = item.getString(); + LOGGER.debug("uploadId is " + uploadId); + } + else if (item.isFormField() && item.getFieldName().equals("currentPath")) + { + final String previewStorePath = + browseBean.getCurrentPath().replaceFirst(AVMConstants.STORE_MAIN, + AVMConstants.STORE_PREVIEW); + currentPath = AVMConstants.buildAbsoluteAVMPath(previewStorePath, + item.getString()); + LOGGER.debug("currentPath is " + currentPath); + } + else + { + filename = item.getName(); + int idx = filename.lastIndexOf('\\'); + if (idx == -1) + { + idx = filename.lastIndexOf('/'); + } + if (idx != -1) + { + filename = filename.substring(idx + File.separator.length()); + } + fileInputStream = item.getInputStream(); + LOGGER.debug("parsed file " + filename); + } + } + + final ServiceRegistry serviceRegistry = + Repository.getServiceRegistry(facesContext); + final AVMService avmService = serviceRegistry.getAVMService(); + LOGGER.debug("saving file " + filename + " to " + currentPath); + + FileCopyUtils.copy(fileInputStream, + avmService.createFile(currentPath, filename)); + + this.uploads.put(uploadId, currentPath + "/" + filename); + + LOGGER.debug("upload complete. sending response"); + final FormsService formsService = FormsService.getInstance(); + final Document result = formsService.newDocument(); + final Element htmlEl = result.createElement("html"); + result.appendChild(htmlEl); + final Element bodyEl = result.createElement("body"); + htmlEl.appendChild(bodyEl); + + final Element scriptEl = result.createElement("script"); + bodyEl.appendChild(scriptEl); + scriptEl.setAttribute("type", "text/javascript"); + final Node scriptText = + result.createTextNode("window.parent.FilePickerWidget." + + "_upload_completeHandler('" + uploadId + "');"); + scriptEl.appendChild(scriptText); + + final ResponseWriter out = facesContext.getResponseWriter(); + formsService.writeXML(result, out); + out.close(); + } private void swapRepeatItems(final XFormsElement from, final XFormsElement to) diff --git a/source/test-resources/xforms/unit-tests/simple-test/components-test.xsd b/source/test-resources/xforms/unit-tests/simple-test/components-test.xsd index f8ad82d964..f193e1e0ad 100644 --- a/source/test-resources/xforms/unit-tests/simple-test/components-test.xsd +++ b/source/test-resources/xforms/unit-tests/simple-test/components-test.xsd @@ -2,9 +2,9 @@ - + - + 1 - eno @@ -55,8 +55,8 @@ - - + + diff --git a/source/web/scripts/ajax/xforms.js b/source/web/scripts/ajax/xforms.js index c0955f3f41..3eaf9a182d 100644 --- a/source/web/scripts/ajax/xforms.js +++ b/source/web/scripts/ajax/xforms.js @@ -237,8 +237,10 @@ dojo.declare("alfresco.xforms.FilePicker", render: function(attach_point) { this.domNode = document.createElement("div"); + attach_point.appendChild(this.domNode); this.domNode.setAttribute("id", this.id + "-widget"); this.domNode.style.width = "100%"; + this.domNode.widget = this; this.domNode.addEventListener("heightChanged", function(event) @@ -247,7 +249,6 @@ dojo.declare("alfresco.xforms.FilePicker", event.target.offsetHeight + "px"; }, false); - attach_point.appendChild(this.domNode); //XXXarielb support readonly and disabled this.widget = new FilePickerWidget(this.domNode, this.getInitialValue(), false); this.widget.render(); @@ -1853,14 +1854,18 @@ function _findElementById(node, id) // " in " + (node ? node.nodeName : null) + // "(" + (node ? node.getAttribute("id") : null) + ")"); if (node.getAttribute("id") == id) + { return node; + } for (var i = 0; i < node.childNodes.length; i++) { if (node.childNodes[i].nodeType == dojo.dom.ELEMENT_NODE) { var n = _findElementById(node.childNodes[i], id); if (n) + { return n; + } } } return null; @@ -1875,8 +1880,7 @@ function create_ajax_request(target, serverMethod, methodArgs, load, error) result.load = load; dojo.event.connect(result, "load", function(type, data, evt) { -// _hide_errors(); - ajax_request_load_handler(this); + ajax_request_load_handler(result); }); result.error = error || function(type, e) { @@ -1906,9 +1910,7 @@ function _show_error(msg) errorDiv.setAttribute("id", "alfresco-xforms-error"); dojo.html.setClass(errorDiv, "infoText statusErrorText"); errorDiv.style.padding = "2px"; - errorDiv.style.borderColor = "#003366"; - errorDiv.style.borderWidth = "1px"; - errorDiv.style.borderStyle = "solid"; + errorDiv.style.border = "1px solid #003366"; var alfUI = document.getElementById("alfresco-xforms-ui"); dojo.dom.prependChild(errorDiv, alfUI); } @@ -1971,20 +1973,19 @@ function ajax_loader_update_display() function ajax_request_load_handler(req) { - var ajaxLoader = _get_ajax_loader_element(); - var index = -1; - for (var i = 0; i < _ajax_requests.length; i++) - { - if (_ajax_requests[i] == req) - { - index = i; - break; - } - } - if (index == -1) + var index = _ajax_requests.indexOf(req); + if (index != -1) _ajax_requests.splice(index, 1); else - throw new Error("unable to find " + req.url); + { + var urls = []; + for (var i = 0; i < _ajax_requests.length; i++) + { + urls.push(_ajax_requests[i].url); + } + throw new Error("unable to find " + req.url + + " in [" + urls.join(", ") + "]"); + } ajax_loader_update_display(); } @@ -2078,6 +2079,61 @@ function FilePickerWidget(node, value, readonly) this.readonly = readonly || false; } +FilePickerWidget._uploads = []; +FilePickerWidget._handleUpload = function(id, fileInput, webappRelativePath, widget) +{ + id = id.substring(0, id.indexOf("-widget")); + var d = fileInput.ownerDocument; + var iframe = d.createElement("iframe"); + iframe.style.display = "none"; + iframe.name = id + "_upload_frame"; + iframe.id = iframe.name; + document.body.appendChild(iframe); + // makes it possible to target the frame properly in ie. + window.frames[id + "_upload_frame"].name = iframe.name; + + FilePickerWidget._uploads[id] = + { + widget:widget, + path: fileInput.value, + webappRelativePath: webappRelativePath, + fileName: fileInput.value.substring(fileInput.value.lastIndexOf("/") + 1) + }; + + var form = document.createElement("form"); + form.style.display = "none"; + d.body.appendChild(form); + form.id = id + "_upload_form"; + form.name = form.id; + form.method = "post"; + form.encoding = "multipart/form-data"; + form.enctype = "multipart/form-data"; + form.target = iframe.name; + form.action = WEBAPP_CONTEXT + "/ajax/invoke/XFormsBean.uploadFile"; + form.appendChild(fileInput.cloneNode(true)); + + var rp = d.createElement("input"); + form.appendChild(rp); + rp.type = "hidden"; + rp.name = "id"; + rp.value = id; + + var rp = d.createElement("input"); + form.appendChild(rp); + rp.type = "hidden"; + rp.name = "currentPath"; + rp.value = webappRelativePath; + + form.submit(); +} + +FilePickerWidget._upload_completeHandler = function(id) +{ + var upload = FilePickerWidget._uploads[id]; + upload.widget._upload_completeHandler(upload.fileName, + upload.webappRelativePath); +} + FilePickerWidget.prototype = { getValue: function() { @@ -2102,6 +2158,46 @@ render: function() { this._showSelectedValue(); }, +_showStatus: function(text) +{ + var d = this.node.ownerDocument; + if (!this.statusDiv || !this.statusDiv.parentNode) + { + this.statusDiv = d.createElement("div"); + this.node.insertBefore(this.statusDiv, this.node.firstChild); + dojo.html.setClass(this.statusDiv, "infoText"); + this.statusDiv.style.padding = "2px"; + this.statusDiv.style.border = "1px solid #003366"; + this.statusDiv.style.fontWeight = "bold"; + this.statusDiv.style.margin = "2px 5%"; + this.statusDiv.style.textAlign = "center"; + this.statusDiv.appendChild(d.createTextNode(text)); + this.node.style.height = (parseInt(this.node.style.height) + + parseInt(this.statusDiv.style.marginTop) + + parseInt(this.statusDiv.style.marginBottom) + + this.statusDiv.offsetHeight) + "px"; + var event = d.createEvent("UIEvents"); + event.initUIEvent("heightChanged", true, true, window, 0); + this.node.dispatchEvent(event); + } + else + { + this.statusDiv.firstChild.nodeValue = text; + } +}, +_hideStatus: function() +{ + if (this.statusDiv) + { + this.node.style.height = (parseInt(this.node.style.height) - + this.statusDiv.offsetHeight) + "px"; + dojo.dom.removeChildren(this.statusDiv); + dojo.dom.removeNode(this.statusDiv); + var event = d.createEvent("UIEvents"); + event.initUIEvent("heightChanged", true, true, window, 0); + this.node.dispatchEvent(event); + } +}, _showSelectedValue: function() { var d = this.node.ownerDocument; @@ -2110,23 +2206,31 @@ _showSelectedValue: function() this.node.style.height = "20px"; this.node.style.lineHeight = this.node.style.height; + this.node.style.position = "relative"; + this.node.style.whiteSpace = "nowrap"; + var event = d.createEvent("UIEvents"); event.initUIEvent("heightChanged", true, true, window, 0); this.node.dispatchEvent(event); - this.node.appendChild(d.createTextNode(this.value == null - ? "" - : this.value)); + this.selectedPathInput = d.createElement("input"); + this.node.appendChild(this.selectedPathInput); + this.selectedPathInput.type = "text"; + this.selectedPathInput.value = this.value == null ? "" : this.value; + dojo.event.connect(this.selectedPathInput, "onblur", this, this._selectPathInput_changeHandler); + this.selectButton = d.createElement("input"); this.node.appendChild(this.selectButton); this.selectButton.filePickerWidget = this; this.selectButton.type = "button"; this.selectButton.value = this.value == null ? "Select" : "Change"; this.selectButton.disabled = this.readonly; - this.selectButton.style.marginLeft = "10px"; - this.selectButton.style.position = "absolute"; - this.selectButton.style.right = "10px"; - this.selectButton.style.top = (.5 * this.node.offsetHeight) - (.5 * this.selectButton.offsetHeight) + "px"; + this.selectButton.style.margin = "0px 10px 0px 10px"; + this.selectedPathInput.style.width = (this.node.offsetWidth - + this.selectButton.offsetWidth - + parseInt(this.selectButton.style.marginRight) - + parseInt(this.selectButton.style.marginLeft)) + + "px"; dojo.event.connect(this.selectButton, "onclick", function(event) @@ -2134,6 +2238,11 @@ _showSelectedValue: function() var w = event.target.filePickerWidget; w._navigateToNode(w.getValue() || ""); }); + +}, +_selectPathInput_changeHandler: function(event) +{ + this.setValue(event.target.value); }, _navigateToNode: function(path) { @@ -2149,9 +2258,19 @@ _navigateToNode: function(path) }, _showPicker: function(data) { - dojo.dom.removeChildren(this.node); + while (this.node.hasChildNodes() && + this.node.lastChild != this.statusDiv) + { + this.node.removeChild(this.node.lastChild); + } + var d = this.node.ownerDocument; - this.node.style.height = "200px"; + this.node.style.height = (200 + + (this.statusDiv + ? (parseInt(this.statusDiv.style.height) + + parseInt(this.statusDiv.style.marginTop) + + parseInt(this.statusDiv.style.marginBottom)) + : 0) + "px"); var event = d.createEvent("UIEvents"); event.initUIEvent("heightChanged", true, true, window, 0); this.node.dispatchEvent(event); @@ -2221,13 +2340,37 @@ _showPicker: function(data) headerMenuTriggerImage.style.marginLeft = "4px"; headerMenuTriggerImage.align = "absmiddle"; - var headerRightLink = d.createElement("a"); - headerRightLink.setAttribute("webappRelativePath", currentPath); - headerRightLink.filePickerWidget = this; - headerRightLink.setAttribute("href", "javascript:void(0)"); + var headerRightDiv = d.createElement("div"); + + var addContentLink = d.createElement("a"); + headerRightDiv.appendChild(addContentLink); + addContentLink.setAttribute("webappRelativePath", currentPath); + addContentLink.filePickerWidget = this; + addContentLink.setAttribute("href", "javascript:void(0)"); + dojo.event.connect(addContentLink, + "onclick", + function(event) + { + var t = event.target; + t.filePickerWidget._showAddContentPanel(t, t.getAttribute("webappRelativePath")); + }); + + var addContentImage = d.createElement("img"); + addContentImage.style.borderWidth = "0px"; + addContentImage.style.margin = "0px 2px 0px 2px"; + addContentImage.align = "absmiddle"; + addContentImage.setAttribute("src", WEBAPP_CONTEXT + "/images/icons/add.gif"); + addContentLink.appendChild(addContentImage); + addContentLink.appendChild(d.createTextNode("Add Content")); + + var navigateToParentLink = d.createElement("a"); + headerRightDiv.appendChild(navigateToParentLink); + navigateToParentLink.setAttribute("webappRelativePath", currentPath); + navigateToParentLink.filePickerWidget = this; + navigateToParentLink.setAttribute("href", "javascript:void(0)"); if (currentPathName != "/") { - dojo.event.connect(headerRightLink, + dojo.event.connect(navigateToParentLink, "onclick", function(event) { @@ -2239,24 +2382,26 @@ _showPicker: function(data) w._navigateToNode(parentPath); }); } + var navigateToParentNodeImage = d.createElement("img"); navigateToParentNodeImage.style.borderWidth = "0px"; navigateToParentNodeImage.style.opacity = (currentPathName == "/" ? .3 : 1); - navigateToParentNodeImage.style.marginRight = "2px"; + navigateToParentNodeImage.style.margin = "0px 2px 0px 2px"; + navigateToParentNodeImage.align = "absmiddle"; navigateToParentNodeImage.setAttribute("src", WEBAPP_CONTEXT + "/images/icons/up.gif"); - headerRightLink.appendChild(navigateToParentNodeImage); - headerRightLink.appendChild(d.createTextNode("Go up")); + navigateToParentLink.appendChild(navigateToParentNodeImage); + navigateToParentLink.appendChild(d.createTextNode("Go up")); - headerRightLink.style.position = "absolute"; - headerRightLink.style.height = headerDiv.style.height; - headerRightLink.style.lineHeight = headerRightLink.style.height; - headerRightLink.style.top = "0px"; - headerRightLink.style.right = "0px"; - headerRightLink.style.paddingRight = "2px"; - headerDiv.appendChild(headerRightLink); + headerRightDiv.style.position = "absolute"; + headerRightDiv.style.height = headerDiv.style.height; + headerRightDiv.style.lineHeight = headerRightDiv.style.height; + headerRightDiv.style.top = "0px"; + headerRightDiv.style.right = "0px"; + headerRightDiv.style.paddingRight = "2px"; + headerDiv.appendChild(headerRightDiv); - var contentDiv = d.createElement("div"); - this.node.appendChild(contentDiv); + this.contentDiv = d.createElement("div"); + this.node.appendChild(this.contentDiv); var footerDiv = d.createElement("div"); this.node.appendChild(footerDiv); @@ -2278,11 +2423,11 @@ _showPicker: function(data) w._showSelectedValue(); }); - - contentDiv.style.height = (this.node.offsetHeight - - footerDiv.offsetHeight - - headerDiv.offsetHeight - 10) + "px"; - contentDiv.style.overflowY = "auto"; + this.contentDiv.style.height = (this.node.offsetHeight - + (this.statusDiv ? this.statusDiv.offsetHeight : 0) - + footerDiv.offsetHeight - + headerDiv.offsetHeight - 10) + "px"; + this.contentDiv.style.overflowY = "auto"; var childNodes = data.getElementsByTagName("child-node"); for (var i = 0; i < childNodes.length; i++) { @@ -2295,7 +2440,7 @@ _showPicker: function(data) var row = d.createElement("div"); row.setAttribute("id", name + "-row"); - contentDiv.appendChild(row); + this.contentDiv.appendChild(row); row.rowIndex = i; row.style.position = "relative"; row.style.height = "20px"; @@ -2368,6 +2513,57 @@ _showPicker: function(data) }); } }, +_showAddContentPanel: function(addContentLink, currentPath) +{ + var d = this.node.ownerDocument; + this.addContentDiv = d.createElement("div"); + this.contentDiv.style.opacity = .3; + this.node.insertBefore(this.addContentDiv, this.contentDiv); + this.addContentDiv.style.backgroundColor = "lightgrey"; + this.addContentDiv.style.position = "absolute"; + this.addContentDiv.style.left = "10%"; + this.addContentDiv.style.right = "10%"; + this.addContentDiv.style.width = "80%"; + this.addContentDiv.style.marginLeft = "4px"; + this.addContentDiv.style.lineHeight = "20px"; + var e = d.createElement("div"); + e.style.marginLeft = "4px"; + this.addContentDiv.appendChild(e); + e.appendChild(d.createTextNode("Upload a file to " + currentPath + " :")); + + var fileInputDiv = d.createElement("div"); + this.addContentDiv.appendChild(fileInputDiv); + fileInput = d.createElement("input"); + fileInputDiv.appendChild(fileInput); + fileInput.widget = this; + fileInput.style.margin = "4px 4px"; + fileInput.name = this.node.getAttribute("id") + "_file_input"; + fileInput.type = "file"; + fileInput.size = "35"; + fileInput.setAttribute("webappRelativePath", currentPath); + dojo.event.connect(fileInput, + "onchange", + function(event) + { + var w = event.target.widget; + FilePickerWidget._handleUpload(w.node.getAttribute("id"), + event.target, + event.target.getAttribute("webappRelativePath"), + w); + if (w.addContentDiv) + { + dojo.dom.removeChildren(w.addContentDiv); + dojo.dom.removeNode(w.addContentDiv); + w.addContentDiv = null; + } + }); +}, +_upload_completeHandler: function(fileName, webappRelativePath) +{ + this._showStatus("Successfully uploaded " + fileName + + " into " + webappRelativePath); + this._navigateToNode(webappRelativePath); +}, _closeParentPathMenu: function() { if (this.parentPathMenu)